Initial Upload
This commit is contained in:
607
lib/Controller/Action.php
Normal file
607
lib/Controller/Action.php
Normal file
@@ -0,0 +1,607 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2023 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\ActionFactory;
|
||||
use Xibo\Factory\LayoutFactory;
|
||||
use Xibo\Factory\ModuleFactory;
|
||||
use Xibo\Factory\RegionFactory;
|
||||
use Xibo\Factory\WidgetFactory;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class Action
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class Action extends Base
|
||||
{
|
||||
|
||||
/**
|
||||
* @var ActionFactory
|
||||
*/
|
||||
private $actionFactory;
|
||||
|
||||
/** @var LayoutFactory */
|
||||
private $layoutFactory;
|
||||
|
||||
/** @var RegionFactory */
|
||||
private $regionFactory;
|
||||
|
||||
/** @var WidgetFactory */
|
||||
private $widgetFactory;
|
||||
|
||||
/** @var ModuleFactory */
|
||||
private $moduleFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param ActionFactory $actionFactory
|
||||
* @param LayoutFactory $layoutFactory
|
||||
* @param RegionFactory $regionFactory
|
||||
* @param WidgetFactory $widgetFactory
|
||||
* @param ModuleFactory $moduleFactory
|
||||
*/
|
||||
public function __construct($actionFactory, $layoutFactory, $regionFactory, $widgetFactory, $moduleFactory)
|
||||
{
|
||||
$this->actionFactory = $actionFactory;
|
||||
$this->layoutFactory = $layoutFactory;
|
||||
$this->regionFactory = $regionFactory;
|
||||
$this->widgetFactory = $widgetFactory;
|
||||
$this->moduleFactory = $moduleFactory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a Grid of Actions
|
||||
*
|
||||
* @SWG\Get(
|
||||
* path="/action",
|
||||
* operationId="actionSearch",
|
||||
* tags={"action"},
|
||||
* summary="Search Actions",
|
||||
* description="Search all Actions this user has access to",
|
||||
* @SWG\Parameter(
|
||||
* name="actionId",
|
||||
* in="query",
|
||||
* description="Filter by Action Id",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="ownerId",
|
||||
* in="query",
|
||||
* description="Filter by Owner Id",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="triggerType",
|
||||
* in="query",
|
||||
* description="Filter by Action trigger type",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="triggerCode",
|
||||
* in="query",
|
||||
* description="Filter by Action trigger code",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="actionType",
|
||||
* in="query",
|
||||
* description="Filter by Action type",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="source",
|
||||
* in="query",
|
||||
* description="Filter by Action source",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="sourceId",
|
||||
* in="query",
|
||||
* description="Filter by Action source Id",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="target",
|
||||
* in="query",
|
||||
* description="Filter by Action target",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="targetId",
|
||||
* in="query",
|
||||
* description="Filter by Action target Id",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="layoutId",
|
||||
* in="query",
|
||||
* description="Return all actions pertaining to a particular Layout",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="sourceOrTargetId",
|
||||
* in="query",
|
||||
* description="Return all actions related to a source or target with the provided ID",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/Action")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function grid(Request $request, Response $response) : Response
|
||||
{
|
||||
$parsedParams = $this->getSanitizer($request->getQueryParams());
|
||||
|
||||
$filter = [
|
||||
'actionId' => $parsedParams->getInt('actionId'),
|
||||
'ownerId' => $parsedParams->getInt('ownerId'),
|
||||
'triggerType' => $parsedParams->getString('triggerType'),
|
||||
'triggerCode' => $parsedParams->getString('triggerCode'),
|
||||
'actionType' => $parsedParams->getString('actionType'),
|
||||
'source' => $parsedParams->getString('source'),
|
||||
'sourceId' => $parsedParams->getInt('sourceId'),
|
||||
'target' => $parsedParams->getString('target'),
|
||||
'targetId' => $parsedParams->getInt('targetId'),
|
||||
'widgetId' => $parsedParams->getInt('widgetId'),
|
||||
'layoutCode' => $parsedParams->getString('layoutCode'),
|
||||
'layoutId' => $parsedParams->getInt('layoutId'),
|
||||
'sourceOrTargetId' => $parsedParams->getInt('sourceOrTargetId'),
|
||||
];
|
||||
|
||||
$actions = $this->actionFactory->query(
|
||||
$this->gridRenderSort($parsedParams),
|
||||
$this->gridRenderFilter($filter, $parsedParams)
|
||||
);
|
||||
|
||||
foreach ($actions as $action) {
|
||||
$action->setUnmatchedProperty('widgetName', null);
|
||||
$action->setUnmatchedProperty('regionName', null);
|
||||
|
||||
if ($action->actionType === 'navWidget' && $action->widgetId != null) {
|
||||
try {
|
||||
$widget = $this->widgetFactory->loadByWidgetId($action->widgetId);
|
||||
$module = $this->moduleFactory->getByType($widget->type);
|
||||
|
||||
// dynamic field to display in the grid instead of widgetId
|
||||
$action->setUnmatchedProperty('widgetName', $widget->getOptionValue('name', $module->name));
|
||||
} catch (NotFoundException $e) {
|
||||
// Widget not found, leave widgetName as null
|
||||
}
|
||||
}
|
||||
|
||||
if ($action->target === 'region' && $action->targetId != null) {
|
||||
try {
|
||||
$region = $this->regionFactory->getById($action->targetId);
|
||||
|
||||
// dynamic field to display in the grid instead of regionId
|
||||
$action->setUnmatchedProperty('regionName', $region->name);
|
||||
} catch (NotFoundException $e) {
|
||||
// Region not found, leave regionName as null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->actionFactory->countLast();
|
||||
$this->getState()->setData($actions);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new Action
|
||||
*
|
||||
* @SWG\Post(
|
||||
* path="/action",
|
||||
* operationId="actionAdd",
|
||||
* tags={"action"},
|
||||
* summary="Add Action",
|
||||
* description="Add a new Action",
|
||||
* @SWG\Parameter(
|
||||
* name="layoutId",
|
||||
* in="formData",
|
||||
* description="LayoutId associted with this Action",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="actionType",
|
||||
* in="formData",
|
||||
* description="Action type, next, previous, navLayout, navWidget",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="target",
|
||||
* in="formData",
|
||||
* description="Target for this action, screen or region",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="targetId",
|
||||
* in="formData",
|
||||
* description="The id of the target for this action - regionId if the target is set to region",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="source",
|
||||
* in="formData",
|
||||
* description="Source for this action layout, region or widget",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="sourceId",
|
||||
* in="formData",
|
||||
* description="The id of the source object, layoutId, regionId or widgetId",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="triggerType",
|
||||
* in="formData",
|
||||
* description="Action trigger type, touch or webhook",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="triggerCode",
|
||||
* in="formData",
|
||||
* description="Action trigger code",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="widgetId",
|
||||
* in="formData",
|
||||
* description="For navWidget actionType, the WidgetId to navigate to",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="layoutCode",
|
||||
* in="formData",
|
||||
* description="For navLayout, the Layout Code identifier to navigate to",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/Action"),
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new record",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function add(Request $request, Response $response) : Response
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$triggerType = $sanitizedParams->getString('triggerType');
|
||||
$triggerCode = $sanitizedParams->getString('triggerCode', ['defaultOnEmptyString' => true]);
|
||||
$actionType = $sanitizedParams->getString('actionType');
|
||||
$target = $sanitizedParams->getString('target');
|
||||
$targetId = $sanitizedParams->getInt('targetId');
|
||||
$widgetId = $sanitizedParams->getInt('widgetId');
|
||||
$layoutCode = $sanitizedParams->getString('layoutCode');
|
||||
$layoutId = $sanitizedParams->getInt('layoutId');
|
||||
$source = $sanitizedParams->getString('source');
|
||||
$sourceId = $sanitizedParams->getInt('sourceId');
|
||||
|
||||
if ($layoutId === null) {
|
||||
throw new InvalidArgumentException(__('Please provide LayoutId'), 'layoutId');
|
||||
}
|
||||
|
||||
$layout = $this->layoutFactory->getById($layoutId);
|
||||
|
||||
// Make sure the Layout is checked out to begin with
|
||||
if (!$layout->isEditable()) {
|
||||
throw new InvalidArgumentException(__('Layout is not checked out'), 'statusId');
|
||||
}
|
||||
|
||||
// restrict to one touch Action per source
|
||||
if (
|
||||
(!empty($source) && $sourceId !== null && !empty($triggerType))
|
||||
&& $this->actionFactory->checkIfActionExist($source, $sourceId, $triggerType)
|
||||
) {
|
||||
throw new InvalidArgumentException(__('Action with specified Trigger Type already exists'), 'triggerType');
|
||||
}
|
||||
|
||||
$action = $this->actionFactory->create(
|
||||
$triggerType,
|
||||
$triggerCode,
|
||||
$actionType,
|
||||
$source,
|
||||
$sourceId,
|
||||
$target,
|
||||
$targetId,
|
||||
$widgetId,
|
||||
$layoutCode,
|
||||
$layoutId
|
||||
);
|
||||
|
||||
$action->save(['notifyLayout' => true]);
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => __('Added Action'),
|
||||
'httpStatus' => 201,
|
||||
'id' => $action->actionId,
|
||||
'data' => $action,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Action
|
||||
*
|
||||
* @SWG\PUT(
|
||||
* path="/action/{actionId}",
|
||||
* operationId="actionAdd",
|
||||
* tags={"action"},
|
||||
* summary="Add Action",
|
||||
* description="Add a new Action",
|
||||
* @SWG\Parameter(
|
||||
* name="actionId",
|
||||
* in="path",
|
||||
* description="Action ID to edit",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="layoutId",
|
||||
* in="formData",
|
||||
* description="LayoutId associted with this Action",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="actionType",
|
||||
* in="formData",
|
||||
* description="Action type, next, previous, navLayout, navWidget",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="target",
|
||||
* in="formData",
|
||||
* description="Target for this action, screen or region",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="targetId",
|
||||
* in="formData",
|
||||
* description="The id of the target for this action - regionId if the target is set to region",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="source",
|
||||
* in="formData",
|
||||
* description="Source for this action layout, region or widget",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="sourceId",
|
||||
* in="formData",
|
||||
* description="The id of the source object, layoutId, regionId or widgetId",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="triggerType",
|
||||
* in="formData",
|
||||
* description="Action trigger type, touch or webhook",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="triggerCode",
|
||||
* in="formData",
|
||||
* description="Action trigger code",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="widgetId",
|
||||
* in="formData",
|
||||
* description="For navWidget actionType, the WidgetId to navigate to",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="layoutCode",
|
||||
* in="formData",
|
||||
* description="For navLayout, the Layout Code identifier to navigate to",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/Action"),
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new record",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param int $id
|
||||
* @return Response
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function edit(Request $request, Response $response, int $id) : Response
|
||||
{
|
||||
$action = $this->actionFactory->getById($id);
|
||||
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
$layout = $this->layoutFactory->getById($action->layoutId);
|
||||
|
||||
// Make sure the Layout is checked out to begin with
|
||||
if (!$layout->isEditable()) {
|
||||
throw new InvalidArgumentException(__('Layout is not checked out'), 'statusId');
|
||||
}
|
||||
|
||||
$action->source = $sanitizedParams->getString('source');
|
||||
$action->sourceId = $sanitizedParams->getInt('sourceId');
|
||||
$action->triggerType = $sanitizedParams->getString('triggerType');
|
||||
$action->triggerCode = $sanitizedParams->getString('triggerCode', ['defaultOnEmptyString' => true]);
|
||||
$action->actionType = $sanitizedParams->getString('actionType');
|
||||
$action->target = $sanitizedParams->getString('target');
|
||||
$action->targetId = $sanitizedParams->getInt('targetId');
|
||||
$action->widgetId = $sanitizedParams->getInt('widgetId');
|
||||
$action->layoutCode = $sanitizedParams->getString('layoutCode');
|
||||
$action->validate();
|
||||
// restrict to one touch Action per source
|
||||
if ($this->actionFactory->checkIfActionExist($action->source, $action->sourceId, $action->triggerType, $action->actionId)) {
|
||||
throw new InvalidArgumentException(__('Action with specified Trigger Type already exists'), 'triggerType');
|
||||
}
|
||||
|
||||
$action->save(['notifyLayout' => true, 'validate' => false]);
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => __('Edited Action'),
|
||||
'id' => $action->actionId,
|
||||
'data' => $action
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Action
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param int $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
*
|
||||
* @SWG\Delete(
|
||||
* path="/action/{actionId}",
|
||||
* operationId="actionDelete",
|
||||
* tags={"action"},
|
||||
* summary="Delete Action",
|
||||
* description="Delete an existing Action",
|
||||
* @SWG\Parameter(
|
||||
* name="actionId",
|
||||
* in="path",
|
||||
* description="The Action ID to Delete",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function delete(Request $request, Response $response, int $id) : Response
|
||||
{
|
||||
$action = $this->actionFactory->getById($id);
|
||||
$layout = $this->layoutFactory->getById($action->layoutId);
|
||||
|
||||
// Make sure the Layout is checked out to begin with
|
||||
if (!$layout->isEditable()) {
|
||||
throw new InvalidArgumentException(__('Layout is not checked out'), 'statusId');
|
||||
}
|
||||
|
||||
$action->notifyLayout($layout->layoutId);
|
||||
$action->delete();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Deleted Action'))
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $source
|
||||
* @param int $sourceId
|
||||
* @return \Xibo\Entity\Layout|\Xibo\Entity\Region|\Xibo\Entity\Widget
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function checkIfSourceExists(string $source, int $sourceId)
|
||||
{
|
||||
if (strtolower($source) === 'layout') {
|
||||
$object = $this->layoutFactory->getById($sourceId);
|
||||
} elseif (strtolower($source) === 'region') {
|
||||
$object = $this->regionFactory->getById($sourceId);
|
||||
} elseif (strtolower($source) === 'widget') {
|
||||
$object = $this->widgetFactory->getById($sourceId);
|
||||
} else {
|
||||
throw new InvalidArgumentException(__('Provided source is invalid. ') , 'source');
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
}
|
||||
652
lib/Controller/Applications.php
Normal file
652
lib/Controller/Applications.php
Normal file
@@ -0,0 +1,652 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2025 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use League\OAuth2\Server\AuthorizationServer;
|
||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||
use League\OAuth2\Server\Grant\AuthCodeGrant;
|
||||
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Stash\Interfaces\PoolInterface;
|
||||
use Xibo\Entity\ApplicationScope;
|
||||
use Xibo\Factory\ApplicationFactory;
|
||||
use Xibo\Factory\ApplicationRedirectUriFactory;
|
||||
use Xibo\Factory\ApplicationScopeFactory;
|
||||
use Xibo\Factory\ConnectorFactory;
|
||||
use Xibo\Factory\UserFactory;
|
||||
use Xibo\Helper\DateFormatHelper;
|
||||
use Xibo\Helper\Session;
|
||||
use Xibo\OAuth\AuthCodeRepository;
|
||||
use Xibo\OAuth\RefreshTokenRepository;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\ControllerNotImplemented;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class Applications
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class Applications extends Base
|
||||
{
|
||||
/**
|
||||
* @var Session
|
||||
*/
|
||||
private $session;
|
||||
|
||||
/**
|
||||
* @var ApplicationFactory
|
||||
*/
|
||||
private $applicationFactory;
|
||||
|
||||
/**
|
||||
* @var ApplicationRedirectUriFactory
|
||||
*/
|
||||
private $applicationRedirectUriFactory;
|
||||
|
||||
/** @var ApplicationScopeFactory */
|
||||
private $applicationScopeFactory;
|
||||
|
||||
/** @var UserFactory */
|
||||
private $userFactory;
|
||||
|
||||
/** @var PoolInterface */
|
||||
private $pool;
|
||||
|
||||
/** @var \Xibo\Factory\ConnectorFactory */
|
||||
private $connectorFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param Session $session
|
||||
* @param ApplicationFactory $applicationFactory
|
||||
* @param ApplicationRedirectUriFactory $applicationRedirectUriFactory
|
||||
* @param $applicationScopeFactory
|
||||
* @param UserFactory $userFactory
|
||||
* @param $pool
|
||||
* @param \Xibo\Factory\ConnectorFactory $connectorFactory
|
||||
*/
|
||||
public function __construct(
|
||||
$session,
|
||||
$applicationFactory,
|
||||
$applicationRedirectUriFactory,
|
||||
$applicationScopeFactory,
|
||||
$userFactory,
|
||||
$pool,
|
||||
ConnectorFactory $connectorFactory
|
||||
) {
|
||||
$this->session = $session;
|
||||
$this->applicationFactory = $applicationFactory;
|
||||
$this->applicationRedirectUriFactory = $applicationRedirectUriFactory;
|
||||
$this->applicationScopeFactory = $applicationScopeFactory;
|
||||
$this->userFactory = $userFactory;
|
||||
$this->pool = $pool;
|
||||
$this->connectorFactory = $connectorFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display Page
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response)
|
||||
{
|
||||
// Load all connectors and output any javascript.
|
||||
$connectorJavaScript = [];
|
||||
foreach ($this->connectorFactory->query(['isVisible' => 1]) as $connector) {
|
||||
try {
|
||||
// Create a connector, add in platform settings and register it with the dispatcher.
|
||||
$connectorObject = $this->connectorFactory->create($connector);
|
||||
|
||||
$settingsFormJavaScript = $connectorObject->getSettingsFormJavaScript();
|
||||
if (!empty($settingsFormJavaScript)) {
|
||||
$connectorJavaScript[] = $settingsFormJavaScript;
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
// Log and ignore.
|
||||
$this->getLog()->error('Incorrectly configured connector. e=' . $exception->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'applications-page';
|
||||
$this->getState()->setData([
|
||||
'connectorJavaScript' => $connectorJavaScript,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display page grid
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function grid(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'grid';
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$applications = $this->applicationFactory->query(
|
||||
$this->gridRenderSort($sanitizedParams),
|
||||
$this->gridRenderFilter(
|
||||
['name' => $sanitizedParams->getString('name')],
|
||||
$sanitizedParams
|
||||
)
|
||||
);
|
||||
|
||||
foreach ($applications as $application) {
|
||||
if ($this->isApi($request)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Include the buttons property
|
||||
$application->includeProperty('buttons');
|
||||
|
||||
// Add an Edit button (edit form also exposes the secret - not possible to get through the API)
|
||||
$application->buttons = [];
|
||||
|
||||
if ($application->userId == $this->getUser()->userId || $this->getUser()->getUserTypeId() == 1) {
|
||||
// Edit
|
||||
$application->buttons[] = [
|
||||
'id' => 'application_edit_button',
|
||||
'url' => $this->urlFor($request, 'application.edit.form', ['id' => $application->key]),
|
||||
'text' => __('Edit')
|
||||
];
|
||||
|
||||
// Delete
|
||||
$application->buttons[] = [
|
||||
'id' => 'application_delete_button',
|
||||
'url' => $this->urlFor($request, 'application.delete.form', ['id' => $application->key]),
|
||||
'text' => __('Delete')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->setData($applications);
|
||||
$this->getState()->recordsTotal = $this->applicationFactory->countLast();
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the Authorize form.
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws InvalidArgumentException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function authorizeRequest(Request $request, Response $response)
|
||||
{
|
||||
// Pull authorize params from our session
|
||||
/** @var AuthorizationRequest $authParams */
|
||||
$authParams = $this->session->get('authParams');
|
||||
if (!$authParams) {
|
||||
throw new InvalidArgumentException(__('Authorisation Parameters missing from session.'), 'authParams');
|
||||
}
|
||||
|
||||
if ($this->applicationFactory->checkAuthorised($authParams->getClient()->getIdentifier(), $this->getUser()->userId)) {
|
||||
return $this->authorize($request->withParsedBody(['authorization' => 'Approve']), $response);
|
||||
}
|
||||
|
||||
$client = $this->applicationFactory->getClientEntity($authParams->getClient()->getIdentifier())->load();
|
||||
|
||||
// Process any scopes.
|
||||
$scopes = [];
|
||||
$authScopes = $authParams->getScopes();
|
||||
|
||||
// if we have scopes in the request, make sure we only add the valid ones.
|
||||
// the default scope is all, if it's not set on the Application, $scopes will still be empty here.
|
||||
if ($authScopes !== null) {
|
||||
$validScopes = $this->applicationScopeFactory->finalizeScopes(
|
||||
$authScopes,
|
||||
$authParams->getGrantTypeId(),
|
||||
$client
|
||||
);
|
||||
|
||||
// get all the valid scopes by their ID, we need to do this to present more details on the authorize form.
|
||||
foreach ($validScopes as $scope) {
|
||||
$scopes[] = $this->applicationScopeFactory->getById($scope->getIdentifier());
|
||||
}
|
||||
|
||||
if (count($scopes) <= 0) {
|
||||
throw new InvalidArgumentException(
|
||||
__('This application has not requested access to anything.'),
|
||||
'authParams'
|
||||
);
|
||||
}
|
||||
|
||||
// update scopes in auth request in session to scopes we actually present for approval
|
||||
$authParams->setScopes($validScopes);
|
||||
}
|
||||
|
||||
// Reasert the auth params.
|
||||
$this->session->set('authParams', $authParams);
|
||||
|
||||
// Get, show page
|
||||
$this->getState()->template = 'applications-authorize-page';
|
||||
$this->getState()->setData([
|
||||
'forceHide' => true,
|
||||
'authParams' => $authParams,
|
||||
'scopes' => $scopes,
|
||||
'application' => $client
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorize an oAuth request
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function authorize(Request $request, Response $response)
|
||||
{
|
||||
// Pull authorize params from our session
|
||||
/** @var AuthorizationRequest $authRequest */
|
||||
$authRequest = $this->session->get('authParams');
|
||||
if (!$authRequest) {
|
||||
throw new InvalidArgumentException(__('Authorisation Parameters missing from session.'), 'authParams');
|
||||
}
|
||||
|
||||
$sanitizedQueryParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$apiKeyPaths = $this->getConfig()->getApiKeyDetails();
|
||||
$privateKey = $apiKeyPaths['privateKeyPath'];
|
||||
$encryptionKey = $apiKeyPaths['encryptionKey'];
|
||||
|
||||
$server = new AuthorizationServer(
|
||||
$this->applicationFactory,
|
||||
new \Xibo\OAuth\AccessTokenRepository($this->getLog(), $this->pool, $this->applicationFactory),
|
||||
$this->applicationScopeFactory,
|
||||
$privateKey,
|
||||
$encryptionKey
|
||||
);
|
||||
|
||||
$server->enableGrantType(
|
||||
new AuthCodeGrant(
|
||||
new AuthCodeRepository(),
|
||||
new RefreshTokenRepository($this->getLog(), $this->pool),
|
||||
new \DateInterval('PT10M')
|
||||
),
|
||||
new \DateInterval('PT1H')
|
||||
);
|
||||
|
||||
// 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);
|
||||
|
||||
$this->applicationFactory->setApplicationApproved(
|
||||
$authRequest->getClient()->getIdentifier(),
|
||||
$authRequest->getUser()->getIdentifier(),
|
||||
Carbon::now()->format(DateFormatHelper::getSystemFormat()),
|
||||
$request->getAttribute('ip_address')
|
||||
);
|
||||
|
||||
$this->getLog()->audit(
|
||||
'Auth',
|
||||
0,
|
||||
'Application access approved',
|
||||
[
|
||||
'Application identifier ends with' => substr($authRequest->getClient()->getIdentifier(), -8),
|
||||
'Application Name' => $authRequest->getClient()->getName()
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$authRequest->setAuthorizationApproved(false);
|
||||
}
|
||||
|
||||
// Redirect back to the specified redirect url
|
||||
try {
|
||||
return $server->completeAuthorizationRequest($authRequest, $response);
|
||||
} catch (OAuthServerException $exception) {
|
||||
if ($exception->hasRedirect()) {
|
||||
return $response->withRedirect($exception->getRedirectUri());
|
||||
} else {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Form to register a new application.
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function addForm(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'applications-form-add';
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Application
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function editForm(Request $request, Response $response, $id)
|
||||
{
|
||||
// Get the client
|
||||
$client = $this->applicationFactory->getById($id);
|
||||
|
||||
if ($client->userId != $this->getUser()->userId && $this->getUser()->getUserTypeId() != 1) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Load this clients details.
|
||||
$client->load();
|
||||
|
||||
$scopes = $this->applicationScopeFactory->query();
|
||||
|
||||
foreach ($scopes as $scope) {
|
||||
/** @var ApplicationScope $scope */
|
||||
$found = false;
|
||||
foreach ($client->scopes as $checked) {
|
||||
if ($checked->id == $scope->id) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$scope->setUnmatchedProperty('selected', $found ? 1 : 0);
|
||||
}
|
||||
|
||||
// Render the view
|
||||
$this->getState()->template = 'applications-form-edit';
|
||||
$this->getState()->setData([
|
||||
'client' => $client,
|
||||
'scopes' => $scopes,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Application Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function deleteForm(Request $request, Response $response, $id)
|
||||
{
|
||||
// Get the client
|
||||
$client = $this->applicationFactory->getById($id);
|
||||
|
||||
if ($client->userId != $this->getUser()->userId && $this->getUser()->getUserTypeId() != 1) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'applications-form-delete';
|
||||
$this->getState()->setData([
|
||||
'client' => $client,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new application with OAuth
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws InvalidArgumentException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function add(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
$application = $this->applicationFactory->create();
|
||||
$application->name = $sanitizedParams->getString('name');
|
||||
|
||||
if ($application->name == '') {
|
||||
throw new InvalidArgumentException(__('Please enter Application name'), 'name');
|
||||
}
|
||||
|
||||
$application->userId = $this->getUser()->userId;
|
||||
$application->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => sprintf(__('Added %s'), $application->name),
|
||||
'data' => $application,
|
||||
'id' => $application->key
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Application
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function edit(Request $request, Response $response, $id)
|
||||
{
|
||||
$this->getLog()->debug('Editing ' . $id);
|
||||
|
||||
// Get the client
|
||||
$client = $this->applicationFactory->getById($id);
|
||||
|
||||
if ($client->userId != $this->getUser()->userId && $this->getUser()->getUserTypeId() != 1) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
$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();
|
||||
$this->pool->getItem('C_' . $client->key)->clear();
|
||||
}
|
||||
|
||||
if ($client->authCode === 1) {
|
||||
$client->description = $sanitizedParams->getString('description');
|
||||
$client->logo = $sanitizedParams->getString('logo');
|
||||
$client->coverImage = $sanitizedParams->getString('coverImage');
|
||||
$client->companyName = $sanitizedParams->getString('companyName');
|
||||
$client->termsUrl = $sanitizedParams->getString('termsUrl');
|
||||
$client->privacyUrl = $sanitizedParams->getString('privacyUrl');
|
||||
}
|
||||
|
||||
// Delete all the redirect urls and add them again
|
||||
$client->load();
|
||||
|
||||
foreach ($client->redirectUris as $uri) {
|
||||
$uri->delete();
|
||||
}
|
||||
|
||||
$client->redirectUris = [];
|
||||
|
||||
// Do we have a redirect?
|
||||
$redirectUris = $sanitizedParams->getArray('redirectUri');
|
||||
|
||||
foreach ($redirectUris as $redirectUri) {
|
||||
if ($redirectUri == '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$redirect = $this->applicationRedirectUriFactory->create();
|
||||
$redirect->redirectUri = $redirectUri;
|
||||
$client->assignRedirectUri($redirect);
|
||||
}
|
||||
|
||||
// clear scopes
|
||||
$client->scopes = [];
|
||||
|
||||
// API Scopes
|
||||
foreach ($this->applicationScopeFactory->query() as $scope) {
|
||||
/** @var ApplicationScope $scope */
|
||||
// See if this has been checked this time
|
||||
$checked = $sanitizedParams->getCheckbox('scope_' . $scope->id);
|
||||
|
||||
// Assign scopes
|
||||
if ($checked) {
|
||||
$client->assignScope($scope);
|
||||
}
|
||||
}
|
||||
|
||||
// Change the ownership?
|
||||
if ($sanitizedParams->getInt('userId') !== null) {
|
||||
// Check we have permissions to view this user
|
||||
$user = $this->userFactory->getById($sanitizedParams->getInt('userId'));
|
||||
|
||||
$this->getLog()->debug('Attempting to change ownership to ' . $user->userId . ' - ' . $user->userName);
|
||||
|
||||
if (!$this->getUser()->checkViewable($user)) {
|
||||
throw new InvalidArgumentException(__('You do not have permission to assign this user'), 'userId');
|
||||
}
|
||||
|
||||
$client->userId = $user->userId;
|
||||
}
|
||||
|
||||
$client->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => sprintf(__('Edited %s'), $client->name),
|
||||
'data' => $client,
|
||||
'id' => $client->key
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete application
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function delete(Request $request, Response $response, $id)
|
||||
{
|
||||
// Get the client
|
||||
$client = $this->applicationFactory->getById($id);
|
||||
|
||||
if ($client->userId != $this->getUser()->userId && $this->getUser()->getUserTypeId() != 1) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$client->delete();
|
||||
$this->pool->getItem('C_' . $client->key)->clear();
|
||||
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Deleted %s'), $client->name)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @param $userId
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function revokeAccess(Request $request, Response $response, $id, $userId)
|
||||
{
|
||||
if ($userId === null) {
|
||||
throw new InvalidArgumentException(__('No User ID provided'));
|
||||
}
|
||||
|
||||
if (empty($id)) {
|
||||
throw new InvalidArgumentException(__('No Client id provided'));
|
||||
}
|
||||
|
||||
$client = $this->applicationFactory->getClientEntity($id);
|
||||
|
||||
if ($this->getUser()->userId != $userId) {
|
||||
throw new InvalidArgumentException(__('Access denied: You do not own this authorization.'));
|
||||
}
|
||||
|
||||
// remove record in lk table
|
||||
$this->applicationFactory->revokeAuthorised($userId, $client->key);
|
||||
// clear cache for this clientId/userId pair, this is how we know the application is no longer approved
|
||||
$this->pool->getItem('C_' . $client->key . '/' . $userId)->clear();
|
||||
|
||||
$this->getLog()->audit(
|
||||
'Auth',
|
||||
0,
|
||||
'Application access revoked',
|
||||
[
|
||||
'Application identifier ends with' => substr($client->key, -8),
|
||||
'Application Name' => $client->getName()
|
||||
]
|
||||
);
|
||||
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Access to %s revoked'), $client->name)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
189
lib/Controller/AuditLog.php
Normal file
189
lib/Controller/AuditLog.php
Normal file
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\AuditLogFactory;
|
||||
use Xibo\Helper\DateFormatHelper;
|
||||
use Xibo\Helper\Random;
|
||||
use Xibo\Helper\SendFile;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class AuditLog
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class AuditLog extends Base
|
||||
{
|
||||
/**
|
||||
* @var AuditLogFactory
|
||||
*/
|
||||
private $auditLogFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param AuditLogFactory $auditLogFactory
|
||||
*/
|
||||
public function __construct($auditLogFactory)
|
||||
{
|
||||
$this->auditLogFactory = $auditLogFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'auditlog-page';
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
function grid(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getQueryParams());
|
||||
|
||||
$filterFromDt = $sanitizedParams->getDate('fromDt');
|
||||
$filterToDt = $sanitizedParams->getDate('toDt');
|
||||
$filterUser = $sanitizedParams->getString('user');
|
||||
$filterEntity = $sanitizedParams->getString('entity');
|
||||
$filterEntityId = $sanitizedParams->getString('entityId');
|
||||
$filterMessage = $sanitizedParams->getString('message');
|
||||
$filterIpAddress = $sanitizedParams->getString('ipAddress');
|
||||
|
||||
if ($filterFromDt != null && $filterFromDt == $filterToDt) {
|
||||
$filterToDt->addDay();
|
||||
}
|
||||
|
||||
// Get the dates and times
|
||||
if ($filterFromDt == null) {
|
||||
$filterFromDt = Carbon::now()->sub('1 day');
|
||||
}
|
||||
|
||||
if ($filterToDt == null) {
|
||||
$filterToDt = Carbon::now();
|
||||
}
|
||||
|
||||
$search = [
|
||||
'fromTimeStamp' => $filterFromDt->format('U'),
|
||||
'toTimeStamp' => $filterToDt->format('U'),
|
||||
'userName' => $filterUser,
|
||||
'entity' => $filterEntity,
|
||||
'entityId' => $filterEntityId,
|
||||
'message' => $filterMessage,
|
||||
'ipAddress' => $filterIpAddress,
|
||||
'sessionHistoryId' => $sanitizedParams->getInt('sessionHistoryId')
|
||||
];
|
||||
|
||||
$rows = $this->auditLogFactory->query(
|
||||
$this->gridRenderSort($sanitizedParams),
|
||||
$this->gridRenderFilter($search, $sanitizedParams)
|
||||
);
|
||||
|
||||
// Do some post processing
|
||||
foreach ($rows as $row) {
|
||||
/* @var \Xibo\Entity\AuditLog $row */
|
||||
$row->objectAfter = json_decode($row->objectAfter);
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->auditLogFactory->countLast();
|
||||
$this->getState()->setData($rows);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output CSV Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function exportForm(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'auditlog-form-export';
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a CSV of audit trail messages
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws InvalidArgumentException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function export(Request $request, Response $response) : Response
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
// We are expecting some parameters
|
||||
$filterFromDt = $sanitizedParams->getDate('filterFromDt');
|
||||
$filterToDt = $sanitizedParams->getDate('filterToDt');
|
||||
$tempFileName = $this->getConfig()->getSetting('LIBRARY_LOCATION') . 'temp/audittrail_' . Random::generateString();
|
||||
|
||||
if ($filterFromDt == null || $filterToDt == null) {
|
||||
throw new InvalidArgumentException(__('Please provide a from/to date.'), 'filterFromDt');
|
||||
}
|
||||
|
||||
$fromTimeStamp = $filterFromDt->setTime(0, 0, 0)->format('U');
|
||||
$toTimeStamp = $filterToDt->setTime(0, 0, 0)->format('U');
|
||||
|
||||
$rows = $this->auditLogFactory->query('logId', ['fromTimeStamp' => $fromTimeStamp, 'toTimeStamp' => $toTimeStamp]);
|
||||
|
||||
$out = fopen($tempFileName, 'w');
|
||||
fputcsv($out, ['ID', 'Date', 'User', 'Entity', 'EntityId', 'Message', 'Object']);
|
||||
|
||||
// Do some post processing
|
||||
foreach ($rows as $row) {
|
||||
/* @var \Xibo\Entity\AuditLog $row */
|
||||
fputcsv($out, [$row->logId, Carbon::createFromTimestamp($row->logDate)->format(DateFormatHelper::getSystemFormat()), $row->userName, $row->entity, $row->entityId, $row->message, $row->objectAfter]);
|
||||
}
|
||||
|
||||
fclose($out);
|
||||
|
||||
$this->setNoOutput(true);
|
||||
|
||||
return $this->render($request, SendFile::decorateResponse(
|
||||
$response,
|
||||
$this->getConfig()->getSetting('SENDFILE_MODE'),
|
||||
$tempFileName,
|
||||
'audittrail.csv'
|
||||
)->withHeader('Content-Type', 'text/csv;charset=utf-8'));
|
||||
}
|
||||
}
|
||||
512
lib/Controller/Base.php
Normal file
512
lib/Controller/Base.php
Normal file
@@ -0,0 +1,512 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2025 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
use Carbon\Carbon;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Slim\Routing\RouteContext;
|
||||
use Slim\Views\Twig;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Xibo\Entity\User;
|
||||
use Xibo\Helper\ApplicationState;
|
||||
use Xibo\Helper\HttpsDetect;
|
||||
use Xibo\Helper\SanitizerService;
|
||||
use Xibo\Service\BaseDependenciesService;
|
||||
use Xibo\Service\ConfigServiceInterface;
|
||||
use Xibo\Service\HelpServiceInterface;
|
||||
use Xibo\Service\LogServiceInterface;
|
||||
use Xibo\Support\Exception\ControllerNotImplemented;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class Base
|
||||
* @package Xibo\Controller
|
||||
*
|
||||
* Base for all Controllers.
|
||||
*
|
||||
*/
|
||||
class Base
|
||||
{
|
||||
use DataTablesDotNetTrait;
|
||||
|
||||
/**
|
||||
* @var LogServiceInterface
|
||||
*/
|
||||
private $log;
|
||||
|
||||
/**
|
||||
* @Inject
|
||||
* @var SanitizerService
|
||||
*/
|
||||
private $sanitizerService;
|
||||
|
||||
/**
|
||||
* @var ApplicationState
|
||||
*/
|
||||
private $state;
|
||||
|
||||
/**
|
||||
* @var HelpServiceInterface
|
||||
*/
|
||||
private $helpService;
|
||||
|
||||
/**
|
||||
* @var ConfigServiceInterface
|
||||
*/
|
||||
private $configService;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* Automatically output a full page if non-ajax request arrives
|
||||
* @var bool
|
||||
*/
|
||||
private $fullPage = true;
|
||||
|
||||
/**
|
||||
* Have we already rendered this controller.
|
||||
* @var bool
|
||||
*/
|
||||
private $rendered = false;
|
||||
|
||||
/**
|
||||
* Is this controller expected to output anything?
|
||||
* @var bool
|
||||
*/
|
||||
private $noOutput = false;
|
||||
|
||||
/**
|
||||
* @var Twig
|
||||
*/
|
||||
private $view;
|
||||
|
||||
/** @var EventDispatcher */
|
||||
private $dispatcher;
|
||||
|
||||
/** @var BaseDependenciesService */
|
||||
private $baseDependenciesService;
|
||||
|
||||
public function useBaseDependenciesService(BaseDependenciesService $baseDependenciesService)
|
||||
{
|
||||
$this->baseDependenciesService = $baseDependenciesService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get User
|
||||
* @return User
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->baseDependenciesService->getUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Application State
|
||||
* @return ApplicationState
|
||||
*/
|
||||
public function getState()
|
||||
{
|
||||
return $this->baseDependenciesService->getState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Log
|
||||
* @return LogServiceInterface
|
||||
*/
|
||||
public function getLog()
|
||||
{
|
||||
return $this->baseDependenciesService->getLogger();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $array
|
||||
* @return \Xibo\Support\Sanitizer\SanitizerInterface
|
||||
*/
|
||||
protected function getSanitizer($array)
|
||||
{
|
||||
$sanitizerService = $this->getSanitizerService();
|
||||
return $sanitizerService->getSanitizer($array);
|
||||
}
|
||||
|
||||
public function getSanitizerService(): SanitizerService
|
||||
{
|
||||
return $this->baseDependenciesService->getSanitizer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Config
|
||||
* @return ConfigServiceInterface
|
||||
*/
|
||||
public function getConfig()
|
||||
{
|
||||
return $this->baseDependenciesService->getConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Slim\Views\Twig
|
||||
*/
|
||||
public function getView()
|
||||
{
|
||||
return $this->baseDependenciesService->getView();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EventDispatcherInterface
|
||||
*/
|
||||
public function getDispatcher(): EventDispatcherInterface
|
||||
{
|
||||
return $this->baseDependenciesService->getDispatcher();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this the Api?
|
||||
* @param Request $request
|
||||
* @return bool
|
||||
*/
|
||||
protected function isApi(Request $request)
|
||||
{
|
||||
return ($request->getAttribute('_entryPoint') != 'web');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Url For Route
|
||||
* @param Request $request
|
||||
* @param string $route
|
||||
* @param array $data
|
||||
* @param array $params
|
||||
* @return string
|
||||
*/
|
||||
protected function urlFor(Request $request, $route, $data = [], $params = [])
|
||||
{
|
||||
$routeParser = RouteContext::fromRequest($request)->getRouteParser();
|
||||
return $routeParser->urlFor($route, $data, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to not output a full page automatically
|
||||
*/
|
||||
public function setNotAutomaticFullPage()
|
||||
{
|
||||
$this->fullPage = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set No output
|
||||
* @param bool $bool
|
||||
*/
|
||||
public function setNoOutput($bool = true)
|
||||
{
|
||||
$this->noOutput = $bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* End the controller execution, calling render
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws ControllerNotImplemented if the controller is not implemented correctly
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function render(Request $request, Response $response)
|
||||
{
|
||||
if ($this->noOutput) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
// State will contain the current ApplicationState, including a success flag that can be used to determine
|
||||
// if we are in error or not.
|
||||
$state = $this->getState();
|
||||
$data = $state->getData();
|
||||
|
||||
// Grid requests require some extra info appended.
|
||||
// they can come from any application, hence being dealt with first
|
||||
$grid = ($state->template === 'grid');
|
||||
|
||||
if ($grid) {
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
$recordsTotal = ($state->recordsTotal == null) ? count($data) : $state->recordsTotal;
|
||||
$recordsFiltered = ($state->recordsFiltered == null) ? $recordsTotal : $state->recordsFiltered;
|
||||
|
||||
$data = [
|
||||
'draw' => $params->getInt('draw'),
|
||||
'recordsTotal' => $recordsTotal,
|
||||
'recordsFiltered' => $recordsFiltered,
|
||||
'data' => $data
|
||||
];
|
||||
}
|
||||
|
||||
// API Request
|
||||
if ($this->isApi($request)) {
|
||||
// Envelope by default - the APIView will un-pack if necessary
|
||||
$this->getState()->setData([
|
||||
'grid' => $grid,
|
||||
'success' => $state->success,
|
||||
'status' => $state->httpStatus,
|
||||
'message' => $state->message,
|
||||
'id' => $state->id,
|
||||
'data' => $data
|
||||
]);
|
||||
|
||||
return $this->renderApiResponse($request, $response->withStatus($state->httpStatus));
|
||||
} else if ($request->isXhr()) {
|
||||
// WEB Ajax
|
||||
// --------
|
||||
// Are we a template that should be rendered to HTML
|
||||
// and then returned?
|
||||
if ($state->template != '' && $state->template != 'grid') {
|
||||
return $this->renderTwigAjaxReturn($request, $response);
|
||||
}
|
||||
|
||||
// We always return 200's
|
||||
if ($grid) {
|
||||
$json = $data;
|
||||
} else {
|
||||
$json = $state->asArray();
|
||||
}
|
||||
|
||||
return $response->withJson($json, 200);
|
||||
} else {
|
||||
// WEB Normal
|
||||
// ----------
|
||||
if (empty($state->template)) {
|
||||
$this->getLog()->debug(sprintf('Template Missing. State: %s', json_encode($state)));
|
||||
throw new ControllerNotImplemented(__('Template Missing'));
|
||||
}
|
||||
|
||||
// Append the sidebar content
|
||||
$data['clock'] = Carbon::now()->format('H:i T');
|
||||
$data['currentUser'] = $this->getUser();
|
||||
|
||||
try {
|
||||
$response = $this->getView()->render($response, $state->template . '.twig', $data);
|
||||
} catch (LoaderError | RuntimeError | SyntaxError $e) {
|
||||
$this->getLog()->error('Twig Error' . $e->getMessage());
|
||||
throw new GeneralException(__('Unable to view this page'));
|
||||
}
|
||||
}
|
||||
$this->rendered = true;
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function renderTwigAjaxReturn(Request $request, Response $response)
|
||||
{
|
||||
$data = $this->getState()->getData();
|
||||
$state = $this->getState();
|
||||
|
||||
// Supply the current user to the view
|
||||
$data['currentUser'] = $this->getUser();
|
||||
|
||||
// Render the view manually with Twig, parse it and pull out various bits
|
||||
try {
|
||||
$view = $this->getView()->render($response, $state->template . '.twig', $data);
|
||||
} catch (LoaderError | RuntimeError | SyntaxError $e) {
|
||||
$this->getLog()->error('Twig Error' . $e->getMessage());
|
||||
throw new GeneralException(__('Unable to view this page'));
|
||||
}
|
||||
|
||||
$view = $view->getBody();
|
||||
|
||||
// Log Rendered View
|
||||
$this->getLog()->debug(sprintf('%s View: %s', $state->template, $view));
|
||||
|
||||
if (!$view = json_decode($view, true)) {
|
||||
$this->getLog()->error(sprintf('Problem with Template: View = %s, Error = %s ', $state->template, json_last_error_msg()));
|
||||
throw new ControllerNotImplemented(__('Problem with Form Template'));
|
||||
}
|
||||
|
||||
$state->html = $view['html'];
|
||||
$state->dialogTitle = trim($view['title']);
|
||||
$state->callBack = $view['callBack'];
|
||||
$state->extra = $view['extra'];
|
||||
|
||||
// Process the buttons
|
||||
$state->buttons = [];
|
||||
// Expect each button on a new line
|
||||
if (trim($view['buttons']) != '') {
|
||||
|
||||
// Convert to an array
|
||||
$view['buttons'] = str_replace("\n\r", "\n", $view['buttons']);
|
||||
$buttons = explode("\n", $view['buttons']);
|
||||
|
||||
foreach ($buttons as $button) {
|
||||
if ($button == '')
|
||||
continue;
|
||||
|
||||
$this->getLog()->debug('Button is ' . $button);
|
||||
|
||||
$button = explode(',', trim($button));
|
||||
|
||||
if (count($button) != 2) {
|
||||
$this->getLog()->error(sprintf('There is a problem with the buttons in the template: %s. Buttons: %s.', $state->template, var_export($view['buttons'], true)));
|
||||
throw new ControllerNotImplemented(__('Problem with Form Template'));
|
||||
}
|
||||
|
||||
$state->buttons[trim($button[0])] = str_replace('|', ',', trim($button[1]));
|
||||
}
|
||||
}
|
||||
|
||||
// Process the fieldActions
|
||||
if (trim($view['fieldActions']) == '') {
|
||||
$state->fieldActions = [];
|
||||
} else {
|
||||
// Convert to an array
|
||||
$state->fieldActions = json_decode($view['fieldActions']);
|
||||
}
|
||||
|
||||
$json = json_decode($state->asJson());
|
||||
return $response = $response->withJson($json, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a template to string
|
||||
* @param string $template
|
||||
* @param array $data
|
||||
* @return string
|
||||
* @throws \Twig\Error\LoaderError
|
||||
* @throws \Twig\Error\RuntimeError
|
||||
* @throws \Twig\Error\SyntaxError
|
||||
*/
|
||||
public function renderTemplateToString($template, $data)
|
||||
{
|
||||
return $this->getView()->fetch($template . '.twig', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
*/
|
||||
public function renderApiResponse(Request $request, Response $response)
|
||||
{
|
||||
$data = $this->getState()->getData();
|
||||
|
||||
// Don't envelope unless requested
|
||||
if ($request->getParam('envelope', 0) == 1
|
||||
|| $request->getAttribute('_entryPoint') === 'test'
|
||||
) {
|
||||
// Envelope
|
||||
// append error bool
|
||||
if (!$data['success']) {
|
||||
$data['success'] = false;
|
||||
}
|
||||
|
||||
// append status code
|
||||
$data['status'] = $response->getStatusCode();
|
||||
|
||||
// Enveloped responses always return 200
|
||||
$response = $response->withStatus(200);
|
||||
} else {
|
||||
// Don't envelope
|
||||
// Set status
|
||||
$response = $response->withStatus($data['status']);
|
||||
|
||||
// Are we successful?
|
||||
if (!$data['success']) {
|
||||
// Error condition
|
||||
$data = [
|
||||
'error' => [
|
||||
'message' => $data['message'],
|
||||
'code' => $data['status'],
|
||||
'data' => $data['data']
|
||||
]
|
||||
];
|
||||
} else {
|
||||
// Are we a grid?
|
||||
if ($data['grid'] == true) {
|
||||
// Set the response to our data['data'] object
|
||||
$grid = $data['data'];
|
||||
$data = $grid['data'];
|
||||
|
||||
// Total Number of Rows
|
||||
$totalRows = $grid['recordsTotal'];
|
||||
|
||||
// Set some headers indicating our next/previous pages
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
$start = $sanitizedParams->getInt('start', ['default' => 0]);
|
||||
$size = $sanitizedParams->getInt('length', ['default' => 10]);
|
||||
|
||||
$linkHeader = '';
|
||||
$url = (new HttpsDetect())->getRootUrl() . $request->getUri()->getPath();
|
||||
|
||||
// Is there a next page?
|
||||
if ($start + $size < $totalRows) {
|
||||
$linkHeader .= '<' . $url . '?start=' . ($start + $size) . '&length=' . $size . '>; rel="next", ';
|
||||
}
|
||||
|
||||
// Is there a previous page?
|
||||
if ($start > 0) {
|
||||
$linkHeader .= '<' . $url . '?start=' . ($start - $size) . '&length=' . $size . '>; rel="prev", ';
|
||||
}
|
||||
|
||||
// The first page
|
||||
$linkHeader .= '<' . $url . '?start=0&length=' . $size . '>; rel="first"';
|
||||
|
||||
$response = $response
|
||||
->withHeader('X-Total-Count', $totalRows)
|
||||
->withHeader('Link', $linkHeader);
|
||||
} else {
|
||||
// Set the response to our data object
|
||||
$data = $data['data'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $response->withJson($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $form The form name
|
||||
* @return bool
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function getAutoSubmit(string $form)
|
||||
{
|
||||
return $this->getUser()->getOptionValue('autoSubmit.' . $form, 'false') === 'true';
|
||||
}
|
||||
|
||||
public function checkRootFolderAllowSave()
|
||||
{
|
||||
if ($this->getConfig()->getSetting('FOLDERS_ALLOW_SAVE_IN_ROOT') == 0
|
||||
&& !$this->getUser()->isSuperAdmin()
|
||||
) {
|
||||
throw new InvalidArgumentException(
|
||||
__('Saving into root folder is disabled, please select a different folder')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
1538
lib/Controller/Campaign.php
Normal file
1538
lib/Controller/Campaign.php
Normal file
File diff suppressed because it is too large
Load Diff
93
lib/Controller/Clock.php
Normal file
93
lib/Controller/Clock.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2021 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Xibo\Controller;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Helper\Session;
|
||||
|
||||
/**
|
||||
* Class Clock
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class Clock extends Base
|
||||
{
|
||||
/**
|
||||
* @var Session
|
||||
*/
|
||||
private $session;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param Session $session
|
||||
*/
|
||||
public function __construct($session)
|
||||
{
|
||||
$this->session = $session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Time
|
||||
*
|
||||
* @SWG\Get(
|
||||
* path="/clock",
|
||||
* operationId="clock",
|
||||
* tags={"misc"},
|
||||
* description="The Time",
|
||||
* summary="The current CMS time",
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful response",
|
||||
* @SWG\Schema(
|
||||
* type="object",
|
||||
* additionalProperties={"title":"time", "type":"string"}
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function clock(Request $request, Response $response)
|
||||
{
|
||||
$this->session->refreshExpiry = false;
|
||||
|
||||
if ($request->isXhr() || $this->isApi($request)) {
|
||||
$output = Carbon::now()->format('H:i T');
|
||||
|
||||
$this->getState()->setData(array('time' => $output));
|
||||
$this->getState()->html = $output;
|
||||
$this->getState()->clockUpdate = true;
|
||||
$this->getState()->success = true;
|
||||
return $this->render($request, $response);
|
||||
} else {
|
||||
// We are returning the response directly, so write the body.
|
||||
$response->getBody()->write(Carbon::now()->format('c'));
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
}
|
||||
585
lib/Controller/Command.php
Normal file
585
lib/Controller/Command.php
Normal file
@@ -0,0 +1,585 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2025 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Event\CommandDeleteEvent;
|
||||
use Xibo\Factory\CommandFactory;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\ControllerNotImplemented;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class Command
|
||||
* Command Controller
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class Command extends Base
|
||||
{
|
||||
/**
|
||||
* @var CommandFactory
|
||||
*/
|
||||
private $commandFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param CommandFactory $commandFactory
|
||||
*/
|
||||
public function __construct($commandFactory)
|
||||
{
|
||||
$this->commandFactory = $commandFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'command-page';
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/command",
|
||||
* operationId="commandSearch",
|
||||
* tags={"command"},
|
||||
* summary="Command Search",
|
||||
* description="Search this users Commands",
|
||||
* @SWG\Parameter(
|
||||
* name="commandId",
|
||||
* in="query",
|
||||
* description="Filter by Command Id",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="command",
|
||||
* in="query",
|
||||
* description="Filter by Command Name",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="code",
|
||||
* in="query",
|
||||
* description="Filter by Command Code",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="useRegexForName",
|
||||
* in="query",
|
||||
* description="Flag (0,1). When filtering by multiple commands in command filter, should we use regex?",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="useRegexForCode",
|
||||
* in="query",
|
||||
* description="Flag (0,1). When filtering by multiple codes in code filter, should we use regex?",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="logicalOperatorName",
|
||||
* in="query",
|
||||
* description="When filtering by multiple commands in command filter,
|
||||
* which logical operator should be used? AND|OR",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="logicalOperatorCode",
|
||||
* in="query",
|
||||
* description="When filtering by multiple codes in code filter,
|
||||
* which logical operator should be used? AND|OR",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/Command")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function grid(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$filter = [
|
||||
'commandId' => $sanitizedParams->getInt('commandId'),
|
||||
'command' => $sanitizedParams->getString('command'),
|
||||
'code' => $sanitizedParams->getString('code'),
|
||||
'useRegexForName' => $sanitizedParams->getCheckbox('useRegexForName'),
|
||||
'useRegexForCode' => $sanitizedParams->getCheckbox('useRegexForCode'),
|
||||
'logicalOperatorName' => $sanitizedParams->getString('logicalOperatorName'),
|
||||
'logicalOperatorCode' => $sanitizedParams->getString('logicalOperatorCode'),
|
||||
];
|
||||
|
||||
$commands = $this->commandFactory->query(
|
||||
$this->gridRenderSort($sanitizedParams),
|
||||
$this->gridRenderFilter($filter, $sanitizedParams)
|
||||
);
|
||||
|
||||
foreach ($commands as $command) {
|
||||
/* @var \Xibo\Entity\Command $command */
|
||||
|
||||
if ($this->isApi($request)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$command->includeProperty('buttons');
|
||||
|
||||
if ($this->getUser()->featureEnabled('command.modify')) {
|
||||
// Command edit
|
||||
if ($this->getUser()->checkEditable($command)) {
|
||||
$command->buttons[] = array(
|
||||
'id' => 'command_button_edit',
|
||||
'url' => $this->urlFor($request, 'command.edit.form', ['id' => $command->commandId]),
|
||||
'text' => __('Edit')
|
||||
);
|
||||
}
|
||||
|
||||
// Command delete
|
||||
if ($this->getUser()->checkDeleteable($command)) {
|
||||
$command->buttons[] = [
|
||||
'id' => 'command_button_delete',
|
||||
'url' => $this->urlFor($request, 'command.delete.form', ['id' => $command->commandId]),
|
||||
'text' => __('Delete'),
|
||||
'multi-select' => true,
|
||||
'dataAttributes' => [
|
||||
[
|
||||
'name' => 'commit-url',
|
||||
'value' => $this->urlFor($request, 'command.delete', ['id' => $command->commandId])
|
||||
],
|
||||
['name' => 'commit-method', 'value' => 'delete'],
|
||||
['name' => 'id', 'value' => 'command_button_delete'],
|
||||
['name' => 'text', 'value' => __('Delete')],
|
||||
['name' => 'sort-group', 'value' => 1],
|
||||
['name' => 'rowtitle', 'value' => $command->command]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
// Command Permissions
|
||||
if ($this->getUser()->checkPermissionsModifyable($command)) {
|
||||
// Permissions button
|
||||
$command->buttons[] = [
|
||||
'id' => 'command_button_permissions',
|
||||
'url' => $this->urlFor(
|
||||
$request,
|
||||
'user.permissions.form',
|
||||
['entity' => 'Command', 'id' => $command->commandId]
|
||||
),
|
||||
'text' => __('Share'),
|
||||
'multi-select' => true,
|
||||
'dataAttributes' => [
|
||||
[
|
||||
'name' => 'commit-url',
|
||||
'value' => $this->urlFor(
|
||||
$request,
|
||||
'user.permissions.multi',
|
||||
['entity' => 'Command', 'id' => $command->commandId]
|
||||
)
|
||||
],
|
||||
['name' => 'commit-method', 'value' => 'post'],
|
||||
['name' => 'id', 'value' => 'command_button_permissions'],
|
||||
['name' => 'text', 'value' => __('Share')],
|
||||
['name' => 'rowtitle', 'value' => $command->command],
|
||||
['name' => 'sort-group', 'value' => 2],
|
||||
['name' => 'custom-handler', 'value' => 'XiboMultiSelectPermissionsFormOpen'],
|
||||
[
|
||||
'name' => 'custom-handler-url',
|
||||
'value' => $this->urlFor(
|
||||
$request,
|
||||
'user.permissions.multi.form',
|
||||
['entity' => 'Command']
|
||||
)
|
||||
],
|
||||
['name' => 'content-id-name', 'value' => 'commandId']
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->commandFactory->countLast();
|
||||
$this->getState()->setData($commands);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Command Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function addForm(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'command-form-add';
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Command
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function editForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$command = $this->commandFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($command)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'command-form-edit';
|
||||
$this->getState()->setData([
|
||||
'command' => $command
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Command
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function deleteForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$command = $this->commandFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($command)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'command-form-delete';
|
||||
$this->getState()->setData([
|
||||
'command' => $command
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Command
|
||||
*
|
||||
* @SWG\Post(
|
||||
* path="/command",
|
||||
* operationId="commandAdd",
|
||||
* tags={"command"},
|
||||
* summary="Command Add",
|
||||
* description="Add a Command",
|
||||
* @SWG\Parameter(
|
||||
* name="command",
|
||||
* in="formData",
|
||||
* description="The Command Name",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="description",
|
||||
* in="formData",
|
||||
* description="A description for the command",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="code",
|
||||
* in="formData",
|
||||
* description="A unique code for this command",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="commandString",
|
||||
* in="formData",
|
||||
* description="The Command String for this Command. Can be overridden on Display Settings.",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="validationString",
|
||||
* in="formData",
|
||||
* description="The Validation String for this Command. Can be overridden on Display Settings.",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="availableOn",
|
||||
* in="formData",
|
||||
* description="An array of Player types this Command is available on, empty for all.",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="createAlertOn",
|
||||
* in="formData",
|
||||
* description="On command execution, when should a Display alert be created?
|
||||
* success, failure, always or never",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/Command"),
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new record",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function add(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$command = $this->commandFactory->create();
|
||||
$command->command = $sanitizedParams->getString('command');
|
||||
$command->description = $sanitizedParams->getString('description');
|
||||
$command->code = $sanitizedParams->getString('code');
|
||||
$command->userId = $this->getUser()->userId;
|
||||
$command->commandString = $sanitizedParams->getString('commandString');
|
||||
$command->validationString = $sanitizedParams->getString('validationString');
|
||||
$command->createAlertOn = $sanitizedParams->getString('createAlertOn', ['default' => 'never']);
|
||||
$availableOn = $sanitizedParams->getArray('availableOn');
|
||||
if (empty($availableOn)) {
|
||||
$command->availableOn = null;
|
||||
} else {
|
||||
$command->availableOn = implode(',', $availableOn);
|
||||
}
|
||||
$command->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => sprintf(__('Added %s'), $command->command),
|
||||
'id' => $command->commandId,
|
||||
'data' => $command
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Command
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
*
|
||||
* @SWG\Put(
|
||||
* path="/command/{commandId}",
|
||||
* operationId="commandEdit",
|
||||
* tags={"command"},
|
||||
* summary="Edit Command",
|
||||
* description="Edit the provided command",
|
||||
* @SWG\Parameter(
|
||||
* name="commandId",
|
||||
* in="path",
|
||||
* description="The Command Id to Edit",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="command",
|
||||
* in="formData",
|
||||
* description="The Command Name",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="description",
|
||||
* in="formData",
|
||||
* description="A description for the command",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="commandString",
|
||||
* in="formData",
|
||||
* description="The Command String for this Command. Can be overridden on Display Settings.",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="validationString",
|
||||
* in="formData",
|
||||
* description="The Validation String for this Command. Can be overridden on Display Settings.",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="availableOn",
|
||||
* in="formData",
|
||||
* description="An array of Player types this Command is available on, empty for all.",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="createAlertOn",
|
||||
* in="formData",
|
||||
* description="On command execution, when should a Display alert be created?
|
||||
* success, failure, always or never",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/Command")
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function edit(Request $request, Response $response, $id)
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
$command = $this->commandFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($command)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$command->command = $sanitizedParams->getString('command');
|
||||
$command->description = $sanitizedParams->getString('description');
|
||||
$command->commandString = $sanitizedParams->getString('commandString');
|
||||
$command->validationString = $sanitizedParams->getString('validationString');
|
||||
$command->createAlertOn = $sanitizedParams->getString('createAlertOn', ['default' => 'never']);
|
||||
$availableOn = $sanitizedParams->getArray('availableOn');
|
||||
if (empty($availableOn)) {
|
||||
$command->availableOn = null;
|
||||
} else {
|
||||
$command->availableOn = implode(',', $availableOn);
|
||||
}
|
||||
$command->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 200,
|
||||
'message' => sprintf(__('Edited %s'), $command->command),
|
||||
'id' => $command->commandId,
|
||||
'data' => $command
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Command
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @SWG\Delete(
|
||||
* path="/command/{commandId}",
|
||||
* operationId="commandDelete",
|
||||
* tags={"command"},
|
||||
* summary="Delete Command",
|
||||
* description="Delete the provided command",
|
||||
* @SWG\Parameter(
|
||||
* name="commandId",
|
||||
* in="path",
|
||||
* description="The Command Id to Delete",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function delete(Request $request, Response $response, $id)
|
||||
{
|
||||
$command = $this->commandFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($command)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getDispatcher()->dispatch(new CommandDeleteEvent($command), CommandDeleteEvent::$NAME);
|
||||
|
||||
$command->delete();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Deleted %s'), $command->command)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
261
lib/Controller/Connector.php
Normal file
261
lib/Controller/Connector.php
Normal file
@@ -0,0 +1,261 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2025 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Exception\HttpMethodNotAllowedException;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Event\ConnectorDeletingEvent;
|
||||
use Xibo\Event\ConnectorEnabledChangeEvent;
|
||||
use Xibo\Factory\ConnectorFactory;
|
||||
use Xibo\Factory\WidgetFactory;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\ControllerNotImplemented;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Connector controller to view, activate and install connectors.
|
||||
*/
|
||||
class Connector extends Base
|
||||
{
|
||||
/** @var \Xibo\Factory\ConnectorFactory */
|
||||
private $connectorFactory;
|
||||
|
||||
/** @var WidgetFactory */
|
||||
private $widgetFactory;
|
||||
|
||||
public function __construct(ConnectorFactory $connectorFactory, WidgetFactory $widgetFactory)
|
||||
{
|
||||
$this->connectorFactory = $connectorFactory;
|
||||
$this->widgetFactory = $widgetFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Slim\Http\ServerRequest $request
|
||||
* @param \Slim\Http\Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|\Slim\Http\Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function grid(Request $request, Response $response)
|
||||
{
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
|
||||
$connectors = $this->connectorFactory->query($request->getParams());
|
||||
|
||||
// Should we show uninstalled connectors?
|
||||
if ($params->getCheckbox('showUninstalled')) {
|
||||
$connectors = array_merge($connectors, $this->connectorFactory->getUninstalled());
|
||||
}
|
||||
|
||||
foreach ($connectors as $connector) {
|
||||
// Instantiate and decorate the entity
|
||||
try {
|
||||
$connector->decorate($this->connectorFactory->create($connector));
|
||||
} catch (NotFoundException) {
|
||||
$this->getLog()->info('Connector installed which is not found in this CMS. ' . $connector->className);
|
||||
$connector->setUnmatchedProperty('isHidden', 1);
|
||||
} catch (\Exception $e) {
|
||||
$this->getLog()->error('Incorrectly configured connector '
|
||||
. $connector->className . '. e=' . $e->getMessage());
|
||||
$connector->setUnmatchedProperty('isHidden', 1);
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = count($connectors);
|
||||
$this->getState()->setData($connectors);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Connector Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function editForm(Request $request, Response $response, $id)
|
||||
{
|
||||
// Is this an installed connector, or not.
|
||||
if (is_numeric($id)) {
|
||||
$connector = $this->connectorFactory->getById($id);
|
||||
} else {
|
||||
$connector = $this->connectorFactory->getUninstalledById($id);
|
||||
}
|
||||
$interface = $this->connectorFactory->create($connector);
|
||||
|
||||
$this->getState()->template = $interface->getSettingsFormTwig() ?: 'connector-form-edit';
|
||||
$this->getState()->setData([
|
||||
'connector' => $connector,
|
||||
'interface' => $interface
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Connector Form Proxy
|
||||
* this is a magic method used to call a connector method which returns some JSON data
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @param $method
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Slim\Exception\HttpMethodNotAllowedException
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function editFormProxy(Request $request, Response $response, $id, $method)
|
||||
{
|
||||
$connector = $this->connectorFactory->getById($id);
|
||||
$interface = $this->connectorFactory->create($connector);
|
||||
|
||||
if (method_exists($interface, $method)) {
|
||||
return $response->withJson($interface->{$method}($this->getSanitizer($request->getParams())));
|
||||
} else {
|
||||
throw new HttpMethodNotAllowedException($request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Connector
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function edit(Request $request, Response $response, $id)
|
||||
{
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
if (is_numeric($id)) {
|
||||
$connector = $this->connectorFactory->getById($id);
|
||||
} else {
|
||||
$connector = $this->connectorFactory->getUninstalledById($id);
|
||||
|
||||
// Null the connectorId so that we add this to the database.
|
||||
$connector->connectorId = null;
|
||||
}
|
||||
$interface = $this->connectorFactory->create($connector);
|
||||
|
||||
// Is this an uninstallation request
|
||||
if ($params->getCheckbox('shouldUninstall')) {
|
||||
// Others
|
||||
$this->getDispatcher()->dispatch(
|
||||
new ConnectorDeletingEvent($connector, $this->getConfig()),
|
||||
ConnectorDeletingEvent::$NAME
|
||||
);
|
||||
|
||||
// Ourselves
|
||||
if (method_exists($interface, 'delete')) {
|
||||
$interface->delete($this->getConfig());
|
||||
}
|
||||
|
||||
$connector->delete();
|
||||
|
||||
// Successful
|
||||
$this->getState()->hydrate([
|
||||
'message' => sprintf(__('Uninstalled %s'), $interface->getTitle())
|
||||
]);
|
||||
} else {
|
||||
// Core properties
|
||||
$connector->isEnabled = $params->getCheckbox('isEnabled');
|
||||
|
||||
// Enabled state change.
|
||||
// Update ourselves, and any others that might be interested.
|
||||
if ($connector->hasPropertyChanged('isEnabled')) {
|
||||
// Others
|
||||
$this->getDispatcher()->dispatch(
|
||||
new ConnectorEnabledChangeEvent($connector, $this->getConfig()),
|
||||
ConnectorEnabledChangeEvent::$NAME
|
||||
);
|
||||
|
||||
// Ourselves
|
||||
if ($connector->isEnabled && method_exists($interface, 'enable')) {
|
||||
$interface->enable($this->getConfig());
|
||||
} else if (!$connector->isEnabled && method_exists($interface, 'disable')) {
|
||||
$interface->disable($this->getConfig());
|
||||
}
|
||||
}
|
||||
|
||||
$connector->settings = $interface->processSettingsForm($params, $connector->settings);
|
||||
$connector->save();
|
||||
|
||||
// Successful
|
||||
$this->getState()->hydrate([
|
||||
'message' => sprintf(__('Edited %s'), $interface->getTitle()),
|
||||
'id' => $id,
|
||||
'data' => $connector
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $token
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
* @throws AccessDeniedException
|
||||
*/
|
||||
public function connectorPreview(Request $request, Response $response)
|
||||
{
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
$token = $params->getString('token');
|
||||
$isDebug = $params->getCheckbox('isDebug');
|
||||
|
||||
if (empty($token)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Dispatch an event to check the token
|
||||
$tokenEvent = new \Xibo\Event\XmdsConnectorTokenEvent();
|
||||
$tokenEvent->setToken($token);
|
||||
$this->getDispatcher()->dispatch($tokenEvent, \Xibo\Event\XmdsConnectorTokenEvent::$NAME);
|
||||
|
||||
if (empty($tokenEvent->getWidgetId())) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Get the widget
|
||||
$widget = $this->widgetFactory->getById($tokenEvent->getWidgetId());
|
||||
|
||||
// It has been found, so we raise an event here to see if any connector can provide a file for it.
|
||||
$event = new \Xibo\Event\XmdsConnectorFileEvent($widget, $isDebug);
|
||||
$this->getDispatcher()->dispatch($event, \Xibo\Event\XmdsConnectorFileEvent::$NAME);
|
||||
|
||||
// What now?
|
||||
return $event->getResponse();
|
||||
}
|
||||
}
|
||||
345
lib/Controller/CypressTest.php
Normal file
345
lib/Controller/CypressTest.php
Normal file
@@ -0,0 +1,345 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2023 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Entity\Display;
|
||||
use Xibo\Factory\CampaignFactory;
|
||||
use Xibo\Factory\CommandFactory;
|
||||
use Xibo\Factory\DayPartFactory;
|
||||
use Xibo\Factory\DisplayFactory;
|
||||
use Xibo\Factory\DisplayGroupFactory;
|
||||
use Xibo\Factory\FolderFactory;
|
||||
use Xibo\Factory\LayoutFactory;
|
||||
use Xibo\Factory\ScheduleFactory;
|
||||
use Xibo\Helper\Session;
|
||||
use Xibo\Storage\StorageServiceInterface;
|
||||
use Xibo\Support\Exception\ControllerNotImplemented;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class CypressTest
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class CypressTest extends Base
|
||||
{
|
||||
/** @var StorageServiceInterface */
|
||||
private $store;
|
||||
|
||||
/**
|
||||
* @var Session
|
||||
*/
|
||||
private $session;
|
||||
|
||||
/**
|
||||
* @var ScheduleFactory
|
||||
*/
|
||||
private $scheduleFactory;
|
||||
|
||||
/** @var FolderFactory */
|
||||
private $folderFactory;
|
||||
/**
|
||||
* @var CommandFactory
|
||||
*/
|
||||
private $commandFactory;
|
||||
|
||||
/**
|
||||
* @var DisplayGroupFactory
|
||||
*/
|
||||
private $displayGroupFactory;
|
||||
|
||||
/**
|
||||
* @var CampaignFactory
|
||||
*/
|
||||
private $campaignFactory;
|
||||
|
||||
/** @var DisplayFactory */
|
||||
private $displayFactory;
|
||||
|
||||
/** @var LayoutFactory */
|
||||
private $layoutFactory;
|
||||
|
||||
/** @var DayPartFactory */
|
||||
private $dayPartFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param StorageServiceInterface $store
|
||||
* @param Session $session
|
||||
* @param ScheduleFactory $scheduleFactory
|
||||
* @param DisplayGroupFactory $displayGroupFactory
|
||||
* @param CampaignFactory $campaignFactory
|
||||
* @param DisplayFactory $displayFactory
|
||||
* @param LayoutFactory $layoutFactory
|
||||
* @param DayPartFactory $dayPartFactory
|
||||
*/
|
||||
|
||||
public function __construct(
|
||||
$store,
|
||||
$session,
|
||||
$scheduleFactory,
|
||||
$displayGroupFactory,
|
||||
$campaignFactory,
|
||||
$displayFactory,
|
||||
$layoutFactory,
|
||||
$dayPartFactory,
|
||||
$folderFactory,
|
||||
$commandFactory
|
||||
) {
|
||||
$this->store = $store;
|
||||
$this->session = $session;
|
||||
$this->scheduleFactory = $scheduleFactory;
|
||||
$this->displayGroupFactory = $displayGroupFactory;
|
||||
$this->campaignFactory = $campaignFactory;
|
||||
$this->displayFactory = $displayFactory;
|
||||
$this->layoutFactory = $layoutFactory;
|
||||
$this->dayPartFactory = $dayPartFactory;
|
||||
$this->folderFactory = $folderFactory;
|
||||
$this->commandFactory = $commandFactory;
|
||||
}
|
||||
|
||||
// <editor-fold desc="Displays">
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws NotFoundException
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function scheduleCampaign(Request $request, Response $response): Response|ResponseInterface
|
||||
{
|
||||
$this->getLog()->debug('Add Schedule');
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$schedule = $this->scheduleFactory->createEmpty();
|
||||
$schedule->userId = $this->getUser()->userId;
|
||||
$schedule->eventTypeId = 5;
|
||||
$schedule->campaignId = $sanitizedParams->getInt('campaignId');
|
||||
$schedule->commandId = $sanitizedParams->getInt('commandId');
|
||||
$schedule->displayOrder = $sanitizedParams->getInt('displayOrder', ['default' => 0]);
|
||||
$schedule->isPriority = $sanitizedParams->getInt('isPriority', ['default' => 0]);
|
||||
$schedule->isGeoAware = $sanitizedParams->getCheckbox('isGeoAware');
|
||||
$schedule->actionType = $sanitizedParams->getString('actionType');
|
||||
$schedule->actionTriggerCode = $sanitizedParams->getString('actionTriggerCode');
|
||||
$schedule->actionLayoutCode = $sanitizedParams->getString('actionLayoutCode');
|
||||
$schedule->maxPlaysPerHour = $sanitizedParams->getInt('maxPlaysPerHour', ['default' => 0]);
|
||||
$schedule->syncGroupId = $sanitizedParams->getInt('syncGroupId');
|
||||
|
||||
// Set the parentCampaignId for campaign events
|
||||
if ($schedule->eventTypeId === \Xibo\Entity\Schedule::$CAMPAIGN_EVENT) {
|
||||
$schedule->parentCampaignId = $schedule->campaignId;
|
||||
|
||||
// Make sure we're not directly scheduling an ad campaign
|
||||
$campaign = $this->campaignFactory->getById($schedule->campaignId);
|
||||
if ($campaign->type === 'ad') {
|
||||
throw new InvalidArgumentException(
|
||||
__('Direct scheduling of an Ad Campaign is not allowed'),
|
||||
'campaignId'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Fields only collected for interrupt events
|
||||
if ($schedule->eventTypeId === \Xibo\Entity\Schedule::$INTERRUPT_EVENT) {
|
||||
$schedule->shareOfVoice = $sanitizedParams->getInt('shareOfVoice', [
|
||||
'throw' => function () {
|
||||
new InvalidArgumentException(
|
||||
__('Share of Voice must be a whole number between 0 and 3600'),
|
||||
'shareOfVoice'
|
||||
);
|
||||
}
|
||||
]);
|
||||
} else {
|
||||
$schedule->shareOfVoice = null;
|
||||
}
|
||||
|
||||
$schedule->dayPartId = 2;
|
||||
$schedule->syncTimezone = 0;
|
||||
|
||||
$displays = $this->displayFactory->query(null, ['display' => $sanitizedParams->getString('displayName')]);
|
||||
$display = $displays[0];
|
||||
$schedule->assignDisplayGroup($this->displayGroupFactory->getById($display->displayGroupId));
|
||||
|
||||
// Ready to do the add
|
||||
$schedule->setDisplayNotifyService($this->displayFactory->getDisplayNotifyService());
|
||||
if ($schedule->campaignId != null) {
|
||||
$schedule->setCampaignFactory($this->campaignFactory);
|
||||
}
|
||||
$schedule->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => __('Added Event'),
|
||||
'id' => $schedule->eventId,
|
||||
'data' => $schedule
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotFoundException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function displaySetStatus(Request $request, Response $response): Response|ResponseInterface
|
||||
{
|
||||
$this->getLog()->debug('Set display status');
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$displays = $this->displayFactory->query(null, ['display' => $sanitizedParams->getString('displayName')]);
|
||||
$display = $displays[0];
|
||||
|
||||
// Get the display
|
||||
$status = $sanitizedParams->getInt('statusId');
|
||||
|
||||
// Set display status
|
||||
$display->mediaInventoryStatus = $status;
|
||||
|
||||
$this->store->update('UPDATE `display` SET MediaInventoryStatus = :status, auditingUntil = :auditingUntil
|
||||
WHERE displayId = :displayId', [
|
||||
'displayId' => $display->displayId,
|
||||
'auditingUntil' => Carbon::now()->addSeconds(86400)->format('U'),
|
||||
'status' => Display::$STATUS_DONE
|
||||
]);
|
||||
$this->store->commitIfNecessary();
|
||||
$this->store->close();
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotFoundException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function displayStatusEquals(Request $request, Response $response): Response|ResponseInterface
|
||||
{
|
||||
$this->getLog()->debug('Check display status');
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
// Get the display
|
||||
$displays = $this->displayFactory->query(null, ['display' => $sanitizedParams->getString('displayName')]);
|
||||
$display = $displays[0];
|
||||
$status = $sanitizedParams->getInt('statusId');
|
||||
|
||||
// Check display status
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'data' => $display->mediaInventoryStatus === $status
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
// </editor-fold>
|
||||
|
||||
public function createCommand(Request $request, Response $response): Response|ResponseInterface
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$command = $this->commandFactory->create();
|
||||
$command->command = $sanitizedParams->getString('command');
|
||||
$command->description = $sanitizedParams->getString('description');
|
||||
$command->code = $sanitizedParams->getString('code');
|
||||
$command->userId = $this->getUser()->userId;
|
||||
$command->commandString = $sanitizedParams->getString('commandString');
|
||||
$command->validationString = $sanitizedParams->getString('validationString');
|
||||
$availableOn = $sanitizedParams->getArray('availableOn');
|
||||
if (empty($availableOn)) {
|
||||
$command->availableOn = null;
|
||||
} else {
|
||||
$command->availableOn = implode(',', $availableOn);
|
||||
}
|
||||
$command->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => sprintf(__('Added %s'), $command->command),
|
||||
'id' => $command->commandId,
|
||||
'data' => $command
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws NotFoundException
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function createCampaign(Request $request, Response $response): Response|ResponseInterface
|
||||
{
|
||||
$this->getLog()->debug('Creating campaign');
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$folder = $this->folderFactory->getById($this->getUser()->homeFolderId, 0);
|
||||
|
||||
// Create Campaign
|
||||
$campaign = $this->campaignFactory->create(
|
||||
'list',
|
||||
$sanitizedParams->getString('name'),
|
||||
$this->getUser()->userId,
|
||||
$folder->getId()
|
||||
);
|
||||
|
||||
// Cycle based playback
|
||||
if ($campaign->type === 'list') {
|
||||
$campaign->cyclePlaybackEnabled = $sanitizedParams->getCheckbox('cyclePlaybackEnabled');
|
||||
$campaign->playCount = ($campaign->cyclePlaybackEnabled) ? $sanitizedParams->getInt('playCount') : null;
|
||||
|
||||
// For compatibility with existing API implementations we set a default here.
|
||||
$campaign->listPlayOrder = ($campaign->cyclePlaybackEnabled)
|
||||
? 'block'
|
||||
: $sanitizedParams->getString('listPlayOrder', ['default' => 'round']);
|
||||
} else if ($campaign->type === 'ad') {
|
||||
$campaign->targetType = $sanitizedParams->getString('targetType');
|
||||
$campaign->target = $sanitizedParams->getInt('target');
|
||||
$campaign->listPlayOrder = 'round';
|
||||
}
|
||||
|
||||
// All done, save.
|
||||
$campaign->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => __('Added campaign'),
|
||||
'id' => $campaign->campaignId,
|
||||
'data' => $campaign
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
// <editor-fold desc="Schedule">
|
||||
|
||||
// </editor-fold>
|
||||
}
|
||||
1990
lib/Controller/DataSet.php
Normal file
1990
lib/Controller/DataSet.php
Normal file
File diff suppressed because it is too large
Load Diff
690
lib/Controller/DataSetColumn.php
Normal file
690
lib/Controller/DataSetColumn.php
Normal file
@@ -0,0 +1,690 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2023 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Stash\Interfaces\PoolInterface;
|
||||
use Xibo\Factory\DataSetColumnFactory;
|
||||
use Xibo\Factory\DataSetColumnTypeFactory;
|
||||
use Xibo\Factory\DataSetFactory;
|
||||
use Xibo\Factory\DataTypeFactory;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
|
||||
/**
|
||||
* Class DataSetColumn
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class DataSetColumn extends Base
|
||||
{
|
||||
/** @var DataSetFactory */
|
||||
private $dataSetFactory;
|
||||
|
||||
/** @var DataSetColumnFactory */
|
||||
private $dataSetColumnFactory;
|
||||
|
||||
/** @var DataSetColumnTypeFactory */
|
||||
private $dataSetColumnTypeFactory;
|
||||
|
||||
/** @var DataTypeFactory */
|
||||
private $dataTypeFactory;
|
||||
|
||||
/** @var PoolInterface */
|
||||
private $pool;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param DataSetFactory $dataSetFactory
|
||||
* @param DataSetColumnFactory $dataSetColumnFactory
|
||||
* @param DataSetColumnTypeFactory $dataSetColumnTypeFactory
|
||||
* @param DataTypeFactory $dataTypeFactory
|
||||
* @param PoolInterface $pool
|
||||
*/
|
||||
public function __construct($dataSetFactory, $dataSetColumnFactory, $dataSetColumnTypeFactory, $dataTypeFactory, $pool)
|
||||
{
|
||||
$this->dataSetFactory = $dataSetFactory;
|
||||
$this->dataSetColumnFactory = $dataSetColumnFactory;
|
||||
$this->dataSetColumnTypeFactory = $dataSetColumnTypeFactory;
|
||||
$this->dataTypeFactory = $dataTypeFactory;
|
||||
$this->pool = $pool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Column Page
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response, $id)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'dataset-column-page';
|
||||
$this->getState()->setData([
|
||||
'dataSet' => $dataSet
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Column Search
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
* @SWG\Get(
|
||||
* path="/dataset/{dataSetId}/column",
|
||||
* operationId="dataSetColumnSearch",
|
||||
* tags={"dataset"},
|
||||
* summary="Search Columns",
|
||||
* description="Search Columns for DataSet",
|
||||
* @SWG\Parameter(
|
||||
* name="dataSetId",
|
||||
* in="path",
|
||||
* description="The DataSet ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="dataSetColumnId",
|
||||
* in="query",
|
||||
* description="Filter by DataSet ColumnID",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/DataSetColumn")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function grid(Request $request, Response $response, $id)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
$parsedRequestParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
if (!$this->getUser()->checkEditable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$dataSetColumns = $this->dataSetColumnFactory->query(
|
||||
$this->gridRenderSort($parsedRequestParams),
|
||||
$this->gridRenderFilter(
|
||||
['dataSetId' => $id, 'dataSetColumnId' => $parsedRequestParams->getInt('dataSetColumnId')],
|
||||
$parsedRequestParams
|
||||
)
|
||||
);
|
||||
|
||||
foreach ($dataSetColumns as $column) {
|
||||
/* @var \Xibo\Entity\DataSetColumn $column */
|
||||
|
||||
$column->dataType = __($column->dataType);
|
||||
$column->dataSetColumnType = __($column->dataSetColumnType);
|
||||
|
||||
if ($this->isApi($request))
|
||||
break;
|
||||
|
||||
$column->includeProperty('buttons');
|
||||
|
||||
if ($this->getUser()->featureEnabled('dataset.modify')) {
|
||||
// Edit
|
||||
$column->buttons[] = array(
|
||||
'id' => 'dataset_button_edit',
|
||||
'url' => $this->urlFor($request,'dataSet.column.edit.form', ['id' => $id, 'colId' => $column->dataSetColumnId]),
|
||||
'text' => __('Edit')
|
||||
);
|
||||
|
||||
if ($this->getUser()->checkDeleteable($dataSet)) {
|
||||
// Delete
|
||||
$column->buttons[] = array(
|
||||
'id' => 'dataset_button_delete',
|
||||
'url' => $this->urlFor($request,'dataSet.column.delete.form', ['id' => $id, 'colId' => $column->dataSetColumnId]),
|
||||
'text' => __('Delete')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->setData($dataSetColumns);
|
||||
$this->getState()->recordsTotal = $this->dataSetColumnFactory->countLast();
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function addForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'dataset-column-form-add';
|
||||
$this->getState()->setData([
|
||||
'dataSet' => $dataSet,
|
||||
'dataTypes' => $this->dataTypeFactory->query(),
|
||||
'dataSetColumnTypes' => $this->dataSetColumnTypeFactory->query(),
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
* @SWG\Post(
|
||||
* path="/dataset/{dataSetId}/column",
|
||||
* operationId="dataSetColumnAdd",
|
||||
* tags={"dataset"},
|
||||
* summary="Add Column",
|
||||
* description="Add a Column to a DataSet",
|
||||
* @SWG\Parameter(
|
||||
* name="dataSetId",
|
||||
* in="path",
|
||||
* description="The DataSet ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="heading",
|
||||
* in="formData",
|
||||
* description="The heading for the Column",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="listContent",
|
||||
* in="formData",
|
||||
* description="A comma separated list of content for drop downs",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="columnOrder",
|
||||
* in="formData",
|
||||
* description="The display order for this column",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="dataTypeId",
|
||||
* in="formData",
|
||||
* description="The data type ID for this column",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="dataSetColumnTypeId",
|
||||
* in="formData",
|
||||
* description="The column type for this column",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="formula",
|
||||
* in="formData",
|
||||
* description="MySQL SELECT syntax formula for this Column if the column type is formula",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="remoteField",
|
||||
* in="formData",
|
||||
* description="JSON-String to select Data from the Remote DataSet",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="showFilter",
|
||||
* in="formData",
|
||||
* description="Flag indicating whether this column should present a filter on DataEntry",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="showSort",
|
||||
* in="formData",
|
||||
* description="Flag indicating whether this column should allow sorting on DataEntry",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="tooltip",
|
||||
* in="formData",
|
||||
* description="Help text that should be displayed when entering data for this Column.",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="isRequired",
|
||||
* in="formData",
|
||||
* description="Flag indicating whether value must be provided for this Column.",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="dateFormat",
|
||||
* in="formData",
|
||||
* description="PHP date format for the dates in the source of the remote DataSet",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/DataSetColumn"),
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new record",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function add(Request $request, Response $response, $id)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
if (!$this->getUser()->checkEditable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Create a Column
|
||||
$column = $this->dataSetColumnFactory->createEmpty();
|
||||
$column->heading = $sanitizedParams->getString('heading');
|
||||
$column->listContent = $sanitizedParams->getString('listContent');
|
||||
$column->columnOrder = $sanitizedParams->getInt('columnOrder');
|
||||
$column->dataTypeId = $sanitizedParams->getInt('dataTypeId');
|
||||
$column->dataSetColumnTypeId = $sanitizedParams->getInt('dataSetColumnTypeId');
|
||||
$column->formula = $request->getParam('formula', null);
|
||||
$column->remoteField = $request->getParam('remoteField', null);
|
||||
$column->showFilter = $sanitizedParams->getCheckbox('showFilter');
|
||||
$column->showSort = $sanitizedParams->getCheckbox('showSort');
|
||||
$column->tooltip = $sanitizedParams->getString('tooltip');
|
||||
$column->isRequired = $sanitizedParams->getCheckbox('isRequired', ['default' => 0]);
|
||||
$column->dateFormat = $sanitizedParams->getString('dateFormat', ['default' => null]);
|
||||
|
||||
if ($column->dataSetColumnTypeId == 3) {
|
||||
$this->pool->deleteItem('/dataset/cache/' . $dataSet->dataSetId);
|
||||
$this->getLog()->debug('New remote column detected, clear cache for remote dataSet ID ' . $dataSet->dataSetId);
|
||||
}
|
||||
|
||||
// Assign the column to set the column order if necessary
|
||||
$dataSet->assignColumn($column);
|
||||
|
||||
// client side formula disable sort
|
||||
if (substr($column->formula, 0, 1) === '$') {
|
||||
$column->showSort = 0;
|
||||
}
|
||||
|
||||
// Save the column
|
||||
$column->save();
|
||||
|
||||
// Notify the change
|
||||
$dataSet->notify();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => sprintf(__('Added %s'), $column->heading),
|
||||
'id' => $column->dataSetColumnId,
|
||||
'data' => $column
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @param $colId
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function editForm(Request $request, Response $response, $id, $colId)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'dataset-column-form-edit';
|
||||
$this->getState()->setData([
|
||||
'dataSet' => $dataSet,
|
||||
'dataSetColumn' => $this->dataSetColumnFactory->getById($colId),
|
||||
'dataTypes' => $this->dataTypeFactory->query(),
|
||||
'dataSetColumnTypes' => $this->dataSetColumnTypeFactory->query(),
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @param $colId
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
* @SWG\Put(
|
||||
* path="/dataset/{dataSetId}/column/{dataSetColumnId}",
|
||||
* operationId="dataSetColumnEdit",
|
||||
* tags={"dataset"},
|
||||
* summary="Edit Column",
|
||||
* description="Edit a Column to a DataSet",
|
||||
* @SWG\Parameter(
|
||||
* name="dataSetId",
|
||||
* in="path",
|
||||
* description="The DataSet ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="dataSetColumnId",
|
||||
* in="path",
|
||||
* description="The Column ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="heading",
|
||||
* in="formData",
|
||||
* description="The heading for the Column",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="listContent",
|
||||
* in="formData",
|
||||
* description="A comma separated list of content for drop downs",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="columnOrder",
|
||||
* in="formData",
|
||||
* description="The display order for this column",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="dataTypeId",
|
||||
* in="formData",
|
||||
* description="The data type ID for this column",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="dataSetColumnTypeId",
|
||||
* in="formData",
|
||||
* description="The column type for this column",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="formula",
|
||||
* in="formData",
|
||||
* description="MySQL SELECT syntax formula for this Column if the column type is formula",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="remoteField",
|
||||
* in="formData",
|
||||
* description="JSON-String to select Data from the Remote DataSet",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="showFilter",
|
||||
* in="formData",
|
||||
* description="Flag indicating whether this column should present a filter on DataEntry",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="showSort",
|
||||
* in="formData",
|
||||
* description="Flag indicating whether this column should allow sorting on DataEntry",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="tooltip",
|
||||
* in="formData",
|
||||
* description="Help text that should be displayed when entering data for this Column.",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="isRequired",
|
||||
* in="formData",
|
||||
* description="Flag indicating whether value must be provided for this Column.",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="dateFormat",
|
||||
* in="formData",
|
||||
* description="PHP date format for the dates in the source of the remote DataSet",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/DataSetColumn"),
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new record",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function edit(Request $request, Response $response, $id, $colId)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
if (!$this->getUser()->checkEditable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Column
|
||||
$column = $this->dataSetColumnFactory->getById($colId);
|
||||
$column->heading = $sanitizedParams->getString('heading');
|
||||
$column->listContent = $sanitizedParams->getString('listContent');
|
||||
$column->columnOrder = $sanitizedParams->getInt('columnOrder');
|
||||
$column->dataTypeId = $sanitizedParams->getInt('dataTypeId');
|
||||
$column->dataSetColumnTypeId = $sanitizedParams->getInt('dataSetColumnTypeId');
|
||||
$column->formula = $request->getParam('formula', null);
|
||||
$column->remoteField = $request->getParam('remoteField', null);
|
||||
$column->showFilter = $sanitizedParams->getCheckbox('showFilter');
|
||||
$column->showSort = $sanitizedParams->getCheckbox('showSort');
|
||||
$column->tooltip = $sanitizedParams->getString('tooltip');
|
||||
$column->isRequired = $sanitizedParams->getCheckbox('isRequired');
|
||||
$column->dateFormat = $sanitizedParams->getString('dateFormat', ['default' => null]);
|
||||
|
||||
// client side formula disable sort
|
||||
if (substr($column->formula, 0, 1) === '$') {
|
||||
$column->showSort = 0;
|
||||
}
|
||||
|
||||
$column->save();
|
||||
|
||||
if ($column->dataSetColumnTypeId == 3 && $column->hasPropertyChanged('remoteField')) {
|
||||
$this->pool->deleteItem('/dataset/cache/' . $dataSet->dataSetId);
|
||||
$this->getLog()->debug('Edited remoteField detected, clear cache for remote dataSet ID ' . $dataSet->dataSetId);
|
||||
}
|
||||
|
||||
$dataSet->notify();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => sprintf(__('Edited %s'), $column->heading),
|
||||
'id' => $column->dataSetColumnId,
|
||||
'data' => $column
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @param $colId
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function deleteForm(Request $request, Response $response, $id, $colId)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'dataset-column-form-delete';
|
||||
$this->getState()->setData([
|
||||
'dataSet' => $dataSet,
|
||||
'dataSetColumn' => $this->dataSetColumnFactory->getById($colId),
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @param $colId
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
* @SWG\Delete(
|
||||
* path="/dataset/{dataSetId}/column/{dataSetColumnId}",
|
||||
* operationId="dataSetColumnDelete",
|
||||
* tags={"dataset"},
|
||||
* summary="Delete Column",
|
||||
* description="Delete DataSet Column",
|
||||
* @SWG\Parameter(
|
||||
* name="dataSetId",
|
||||
* in="path",
|
||||
* description="The DataSet ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="dataSetColumnId",
|
||||
* in="path",
|
||||
* description="The Column ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function delete(Request $request, Response $response, $id, $colId)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Get the column
|
||||
$column = $this->dataSetColumnFactory->getById($colId);
|
||||
$column->delete();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Deleted %s'), $column->heading)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
593
lib/Controller/DataSetData.php
Normal file
593
lib/Controller/DataSetData.php
Normal file
@@ -0,0 +1,593 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\DataSetFactory;
|
||||
use Xibo\Factory\MediaFactory;
|
||||
use Xibo\Helper\DateFormatHelper;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class DataSetData
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class DataSetData extends Base
|
||||
{
|
||||
/** @var DataSetFactory */
|
||||
private $dataSetFactory;
|
||||
|
||||
/** @var MediaFactory */
|
||||
private $mediaFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param DataSetFactory $dataSetFactory
|
||||
* @param MediaFactory $mediaFactory
|
||||
*/
|
||||
public function __construct($dataSetFactory, $mediaFactory)
|
||||
{
|
||||
$this->dataSetFactory = $dataSetFactory;
|
||||
$this->mediaFactory = $mediaFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display Page
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response, $id)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Load data set
|
||||
$dataSet->load();
|
||||
|
||||
$this->getState()->template = 'dataset-dataentry-page';
|
||||
$this->getState()->setData([
|
||||
'dataSet' => $dataSet
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grid
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @SWG\Get(
|
||||
* path="/dataset/data/{dataSetId}",
|
||||
* operationId="dataSetData",
|
||||
* tags={"dataset"},
|
||||
* summary="DataSet Data",
|
||||
* description="Get Data for DataSet",
|
||||
* @SWG\Parameter(
|
||||
* name="dataSetId",
|
||||
* in="path",
|
||||
* description="The DataSet ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function grid(Request $request, Response $response, $id)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
if (!$this->getUser()->checkEditable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$sorting = $this->gridRenderSort($sanitizedParams);
|
||||
|
||||
if ($sorting != null) {
|
||||
$sorting = implode(',', $sorting);
|
||||
}
|
||||
|
||||
// Filter criteria
|
||||
$filter = '';
|
||||
$params = [];
|
||||
$i = 0;
|
||||
foreach ($dataSet->getColumn() as $column) {
|
||||
/* @var \Xibo\Entity\DataSetColumn $column */
|
||||
if ($column->dataSetColumnTypeId == 1) {
|
||||
$i++;
|
||||
if ($sanitizedParams->getString($column->heading) != null) {
|
||||
$filter .= 'AND `' . $column->heading . '` LIKE :heading_' . $i . ' ';
|
||||
$params['heading_' . $i] = '%' . $sanitizedParams->getString($column->heading) . '%';
|
||||
}
|
||||
}
|
||||
}
|
||||
$filter = trim($filter, 'AND');
|
||||
|
||||
// Work out the limits
|
||||
$filter = $this->gridRenderFilter(['filter' => $request->getParam('filter', $filter)], $sanitizedParams);
|
||||
|
||||
try {
|
||||
$data = $dataSet->getData(
|
||||
[
|
||||
'order' => $sorting,
|
||||
'start' => $filter['start'],
|
||||
'size' => $filter['length'],
|
||||
'filter' => $filter['filter']
|
||||
],
|
||||
[],
|
||||
$params,
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
$data = ['exception' => __('Error getting DataSet data, failed with following message: ') . $e->getMessage()];
|
||||
$this->getLog()->error('Error getting DataSet data, failed with following message: ' . $e->getMessage());
|
||||
$this->getLog()->debug($e->getTraceAsString());
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->setData($data);
|
||||
|
||||
// Output the count of records for paging purposes
|
||||
if ($dataSet->countLast() != 0)
|
||||
$this->getState()->recordsTotal = $dataSet->countLast();
|
||||
|
||||
// Set this dataSet as being active
|
||||
$dataSet->setActive();
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function addForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$dataSet->load();
|
||||
|
||||
$this->getState()->template = 'dataset-data-form-add';
|
||||
$this->getState()->setData([
|
||||
'dataSet' => $dataSet
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\DuplicateEntityException
|
||||
* @SWG\Post(
|
||||
* path="/dataset/data/{dataSetId}",
|
||||
* operationId="dataSetDataAdd",
|
||||
* tags={"dataset"},
|
||||
* summary="Add Row",
|
||||
* description="Add a row of Data to a DataSet",
|
||||
* @SWG\Parameter(
|
||||
* name="dataSetId",
|
||||
* in="path",
|
||||
* description="The DataSet ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="dataSetColumnId_ID",
|
||||
* in="formData",
|
||||
* description="Parameter for each dataSetColumnId in the DataSet",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new record",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function add(Request $request, Response $response, $id)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
if (!$this->getUser()->checkEditable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$row = [];
|
||||
|
||||
// Expect input for each value-column
|
||||
foreach ($dataSet->getColumn() as $column) {
|
||||
/* @var \Xibo\Entity\DataSetColumn $column */
|
||||
if ($column->dataSetColumnTypeId == 1) {
|
||||
// Sanitize accordingly
|
||||
if ($column->dataTypeId == 2) {
|
||||
// Number
|
||||
$value = $sanitizedParams->getDouble('dataSetColumnId_' . $column->dataSetColumnId);
|
||||
} else if ($column->dataTypeId == 3) {
|
||||
// Date
|
||||
$date = $sanitizedParams->getDate('dataSetColumnId_' . $column->dataSetColumnId);
|
||||
// format only if we have the date provided.
|
||||
$value = $date === null ? $date : $date->format(DateFormatHelper::getSystemFormat());
|
||||
} else if ($column->dataTypeId == 5) {
|
||||
// Media Id
|
||||
$value = $sanitizedParams->getInt('dataSetColumnId_' . $column->dataSetColumnId);
|
||||
} else if ($column->dataTypeId === 6) {
|
||||
// HTML
|
||||
$value = $sanitizedParams->getHtml('dataSetColumnId_' . $column->dataSetColumnId);
|
||||
} else {
|
||||
// String
|
||||
$value = $sanitizedParams->getString('dataSetColumnId_' . $column->dataSetColumnId);
|
||||
}
|
||||
|
||||
$row[$column->heading] = $value;
|
||||
} elseif ($column->dataSetColumnTypeId == 3) {
|
||||
throw new InvalidArgumentException(__('Cannot add new rows to remote dataSet'), 'dataSetColumnTypeId');
|
||||
}
|
||||
}
|
||||
|
||||
// Use the data set object to add a row
|
||||
$rowId = $dataSet->addRow($row);
|
||||
|
||||
|
||||
// Save the dataSet
|
||||
$dataSet->save(['validate' => false, 'saveColumns' => false]);
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => __('Added Row'),
|
||||
'id' => $rowId,
|
||||
'data' => [
|
||||
'id' => $rowId
|
||||
]
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @param $rowId
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function editForm(Request $request, Response $response, $id, $rowId)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$dataSet->load();
|
||||
|
||||
$row = $dataSet->getData(['id' => $rowId])[0];
|
||||
|
||||
// Augment my row with any already selected library image
|
||||
foreach ($dataSet->getColumn() as $dataSetColumn) {
|
||||
if ($dataSetColumn->dataTypeId === 5) {
|
||||
// Add this image object to my row
|
||||
try {
|
||||
if (isset($row[$dataSetColumn->heading])) {
|
||||
$row['__images'][$dataSetColumn->dataSetColumnId] = $this->mediaFactory->getById($row[$dataSetColumn->heading]);
|
||||
}
|
||||
} catch (NotFoundException $notFoundException) {
|
||||
$this->getLog()->debug('DataSet ' . $id . ' references an image that no longer exists. ID is ' . $row[$dataSetColumn->heading]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'dataset-data-form-edit';
|
||||
$this->getState()->setData([
|
||||
'dataSet' => $dataSet,
|
||||
'row' => $row
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Row
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @param int $rowId
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\DuplicateEntityException
|
||||
* @SWG\Put(
|
||||
* path="/dataset/data/{dataSetId}/{rowId}",
|
||||
* operationId="dataSetDataEdit",
|
||||
* tags={"dataset"},
|
||||
* summary="Edit Row",
|
||||
* description="Edit a row of Data to a DataSet",
|
||||
* @SWG\Parameter(
|
||||
* name="dataSetId",
|
||||
* in="path",
|
||||
* description="The DataSet ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="rowId",
|
||||
* in="path",
|
||||
* description="The Row ID of the Data to Edit",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="dataSetColumnId_ID",
|
||||
* in="formData",
|
||||
* description="Parameter for each dataSetColumnId in the DataSet",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function edit(Request $request, Response $response, $id, $rowId)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
if (!$this->getUser()->checkEditable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$existingRow = $dataSet->getData(['id' => $rowId])[0];
|
||||
$row = [];
|
||||
|
||||
// Expect input for each value-column
|
||||
foreach ($dataSet->getColumn() as $column) {
|
||||
$existingValue = $existingRow[$column->heading];
|
||||
/* @var \Xibo\Entity\DataSetColumn $column */
|
||||
if ($column->dataSetColumnTypeId == 1) {
|
||||
// Pull out the value
|
||||
$value = $request->getParam('dataSetColumnId_' . $column->dataSetColumnId, null);
|
||||
|
||||
$this->getLog()->debug('Value is: ' . var_export($value, true)
|
||||
. ', existing value is ' . var_export($existingValue, true));
|
||||
|
||||
// Sanitize accordingly
|
||||
if ($column->dataTypeId == 2) {
|
||||
// Number
|
||||
if (isset($value)) {
|
||||
$value = $sanitizedParams->getDouble('dataSetColumnId_' . $column->dataSetColumnId);
|
||||
} else {
|
||||
$value = $existingValue;
|
||||
}
|
||||
} else if ($column->dataTypeId == 3) {
|
||||
// Date
|
||||
if (isset($value)) {
|
||||
$value = $sanitizedParams->getDate('dataSetColumnId_' . $column->dataSetColumnId);
|
||||
} else {
|
||||
$value = $existingValue;
|
||||
}
|
||||
} else if ($column->dataTypeId == 5) {
|
||||
// Media Id
|
||||
if (isset($value)) {
|
||||
$value = $sanitizedParams->getInt('dataSetColumnId_' . $column->dataSetColumnId);
|
||||
} else {
|
||||
$value = null;
|
||||
}
|
||||
} else if ($column->dataTypeId === 6) {
|
||||
// HTML
|
||||
if (isset($value)) {
|
||||
$value = $sanitizedParams->getHtml('dataSetColumnId_' . $column->dataSetColumnId);
|
||||
} else {
|
||||
$value = null;
|
||||
}
|
||||
} else {
|
||||
// String
|
||||
if (isset($value)) {
|
||||
$value = $sanitizedParams->getString('dataSetColumnId_' . $column->dataSetColumnId);
|
||||
} else {
|
||||
$value = $existingValue;
|
||||
}
|
||||
}
|
||||
|
||||
$row[$column->heading] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Use the data set object to edit a row
|
||||
if ($row != []) {
|
||||
$dataSet->editRow($rowId, $row);
|
||||
} else {
|
||||
throw new InvalidArgumentException(__('Cannot edit data of remote columns'), 'dataSetColumnTypeId');
|
||||
}
|
||||
// Save the dataSet
|
||||
$dataSet->save(['validate' => false, 'saveColumns' => false]);
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => __('Edited Row'),
|
||||
'id' => $rowId,
|
||||
'data' => [
|
||||
'id' => $rowId
|
||||
]
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @param int $rowId
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function deleteForm(Request $request, Response $response, $id, $rowId)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$dataSet->load();
|
||||
|
||||
$this->getState()->template = 'dataset-data-form-delete';
|
||||
$this->getState()->setData([
|
||||
'dataSet' => $dataSet,
|
||||
'row' => $dataSet->getData(['id' => $rowId])[0]
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Row
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @param $rowId
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\DuplicateEntityException
|
||||
* @SWG\Delete(
|
||||
* path="/dataset/data/{dataSetId}/{rowId}",
|
||||
* operationId="dataSetDataDelete",
|
||||
* tags={"dataset"},
|
||||
* summary="Delete Row",
|
||||
* description="Delete a row of Data to a DataSet",
|
||||
* @SWG\Parameter(
|
||||
* name="dataSetId",
|
||||
* in="path",
|
||||
* description="The DataSet ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="rowId",
|
||||
* in="path",
|
||||
* description="The Row ID of the Data to Delete",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function delete(Request $request, Response $response, $id, $rowId)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
if (empty($dataSet->getData(['id' => $rowId])[0])) {
|
||||
throw new NotFoundException(__('row not found'), 'dataset');
|
||||
}
|
||||
|
||||
// Delete the row
|
||||
$dataSet->deleteRow($rowId);
|
||||
|
||||
// Save the dataSet
|
||||
$dataSet->save(['validate' => false, 'saveColumns' => false]);
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => __('Deleted Row'),
|
||||
'id' => $rowId
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
913
lib/Controller/DataSetRss.php
Normal file
913
lib/Controller/DataSetRss.php
Normal file
@@ -0,0 +1,913 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\Exceptions\InvalidDateException;
|
||||
use PicoFeed\Syndication\Rss20FeedBuilder;
|
||||
use PicoFeed\Syndication\Rss20ItemBuilder;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Stash\Interfaces\PoolInterface;
|
||||
use Xibo\Factory\DataSetColumnFactory;
|
||||
use Xibo\Factory\DataSetFactory;
|
||||
use Xibo\Factory\DataSetRssFactory;
|
||||
use Xibo\Helper\DateFormatHelper;
|
||||
use Xibo\Storage\StorageServiceInterface;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
class DataSetRss extends Base
|
||||
{
|
||||
/** @var DataSetRssFactory */
|
||||
private $dataSetRssFactory;
|
||||
|
||||
/** @var DataSetFactory */
|
||||
private $dataSetFactory;
|
||||
|
||||
/** @var DataSetColumnFactory */
|
||||
private $dataSetColumnFactory;
|
||||
|
||||
/** @var PoolInterface */
|
||||
private $pool;
|
||||
|
||||
/** @var StorageServiceInterface */
|
||||
private $store;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param DataSetRssFactory $dataSetRssFactory
|
||||
* @param DataSetFactory $dataSetFactory
|
||||
* @param DataSetColumnFactory $dataSetColumnFactory
|
||||
* @param PoolInterface $pool
|
||||
* @param StorageServiceInterface $store
|
||||
*/
|
||||
public function __construct($dataSetRssFactory, $dataSetFactory, $dataSetColumnFactory, $pool, $store)
|
||||
{
|
||||
$this->dataSetRssFactory = $dataSetRssFactory;
|
||||
$this->dataSetFactory = $dataSetFactory;
|
||||
$this->dataSetColumnFactory = $dataSetColumnFactory;
|
||||
$this->pool = $pool;
|
||||
$this->store = $store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display Page
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response, $id)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'dataset-rss-page';
|
||||
$this->getState()->setData([
|
||||
'dataSet' => $dataSet
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @SWG\Get(
|
||||
* path="/dataset/{dataSetId}/rss",
|
||||
* operationId="dataSetRSSSearch",
|
||||
* tags={"dataset"},
|
||||
* summary="Search RSSs",
|
||||
* description="Search RSSs for DataSet",
|
||||
* @SWG\Parameter(
|
||||
* name="dataSetId",
|
||||
* in="path",
|
||||
* description="The DataSet ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/DataSetRss")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function grid(Request $request, Response $response, $id)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
$sanitizedParams = $this->getSanitizer($request->getQueryParams());
|
||||
|
||||
if (!$this->getUser()->checkEditable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$feeds = $this->dataSetRssFactory->query($this->gridRenderSort($sanitizedParams), $this->gridRenderFilter([
|
||||
'dataSetId' => $id,
|
||||
'useRegexForName' => $sanitizedParams->getCheckbox('useRegexForName')
|
||||
], $sanitizedParams));
|
||||
|
||||
foreach ($feeds as $feed) {
|
||||
|
||||
if ($this->isApi($request))
|
||||
continue;
|
||||
|
||||
$feed->includeProperty('buttons');
|
||||
|
||||
if ($this->getUser()->featureEnabled('dataset.data')) {
|
||||
// Edit
|
||||
$feed->buttons[] = array(
|
||||
'id' => 'datasetrss_button_edit',
|
||||
'url' => $this->urlFor($request,'dataSet.rss.edit.form', ['id' => $id, 'rssId' => $feed->id]),
|
||||
'text' => __('Edit')
|
||||
);
|
||||
|
||||
if ($this->getUser()->checkDeleteable($dataSet)) {
|
||||
// Delete
|
||||
$feed->buttons[] = array(
|
||||
'id' => 'datasetrss_button_delete',
|
||||
'url' => $this->urlFor($request,'dataSet.rss.delete.form', ['id' => $id, 'rssId' => $feed->id]),
|
||||
'text' => __('Delete')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->setData($feeds);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function addForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$columns = $dataSet->getColumn();
|
||||
$dateColumns = [];
|
||||
|
||||
foreach ($columns as $column) {
|
||||
if ($column->dataTypeId === 3)
|
||||
$dateColumns[] = $column;
|
||||
}
|
||||
|
||||
$this->getState()->template = 'dataset-rss-form-add';
|
||||
$this->getState()->setData([
|
||||
'dataSet' => $dataSet,
|
||||
'extra' => [
|
||||
'orderClauses' => [],
|
||||
'filterClauses' => [],
|
||||
'columns' => $columns,
|
||||
'dateColumns' => $dateColumns
|
||||
]
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @SWG\Post(
|
||||
* path="/dataset/{dataSetId}/rss",
|
||||
* operationId="dataSetRssAdd",
|
||||
* tags={"dataset"},
|
||||
* summary="Add RSS",
|
||||
* description="Add a RSS to a DataSet",
|
||||
* @SWG\Parameter(
|
||||
* name="dataSetId",
|
||||
* in="path",
|
||||
* description="The DataSet ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="title",
|
||||
* in="formData",
|
||||
* description="The title for the RSS",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="title",
|
||||
* in="formData",
|
||||
* description="The author for the RSS",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="summaryColumnId",
|
||||
* in="formData",
|
||||
* description="The columnId to be used as each item summary",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="contentColumnId",
|
||||
* in="formData",
|
||||
* description="The columnId to be used as each item content",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="publishedDateColumnId",
|
||||
* in="formData",
|
||||
* description="The columnId to be used as each item published date",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/DataSetRss"),
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new record",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function add(Request $request, Response $response, $id)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
if (!$this->getUser()->checkEditable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
if ($sanitizedParams->getString('title') == '') {
|
||||
throw new InvalidArgumentException(__('Please enter title'), 'title');
|
||||
}
|
||||
|
||||
if ($sanitizedParams->getString('author') == '') {
|
||||
throw new InvalidArgumentException(__('Please enter author name'), 'author');
|
||||
}
|
||||
|
||||
// Create RSS
|
||||
$feed = $this->dataSetRssFactory->createEmpty();
|
||||
$feed->dataSetId = $id;
|
||||
$feed->title = $sanitizedParams->getString('title');
|
||||
$feed->author = $sanitizedParams->getString('author');
|
||||
$feed->titleColumnId = $sanitizedParams->getInt('titleColumnId');
|
||||
$feed->summaryColumnId = $sanitizedParams->getInt('summaryColumnId');
|
||||
$feed->contentColumnId = $sanitizedParams->getInt('contentColumnId');
|
||||
$feed->publishedDateColumnId = $sanitizedParams->getInt('publishedDateColumnId');
|
||||
$this->handleFormFilterAndOrder($request, $response, $feed);
|
||||
|
||||
// New feed needs a PSK
|
||||
$feed->setNewPsk();
|
||||
|
||||
// Save
|
||||
$feed->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => sprintf(__('Added %s'), $feed->title),
|
||||
'id' => $feed->id,
|
||||
'data' => $feed
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param \Xibo\Entity\DataSetRss $feed
|
||||
*/
|
||||
private function handleFormFilterAndOrder(Request $request, Response $response, $feed)
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
// Order criteria
|
||||
$orderClauses = $sanitizedParams->getArray('orderClause');
|
||||
$orderClauseDirections = $sanitizedParams->getArray('orderClauseDirection');
|
||||
$orderClauseMapping = [];
|
||||
|
||||
$i = -1;
|
||||
foreach ($orderClauses as $orderClause) {
|
||||
$i++;
|
||||
|
||||
if ($orderClause == '')
|
||||
continue;
|
||||
|
||||
// Map the stop code received to the stop ref (if there is one)
|
||||
$orderClauseMapping[] = [
|
||||
'orderClause' => $orderClause,
|
||||
'orderClauseDirection' => isset($orderClauseDirections[$i]) ? $orderClauseDirections[$i] : '',
|
||||
];
|
||||
}
|
||||
|
||||
$feed->sort = json_encode([
|
||||
'sort' => $sanitizedParams->getString('sort'),
|
||||
'useOrderingClause' => $sanitizedParams->getCheckbox('useOrderingClause'),
|
||||
'orderClauses' => $orderClauseMapping
|
||||
]);
|
||||
|
||||
// Filter criteria
|
||||
$filterClauses = $sanitizedParams->getArray('filterClause');
|
||||
$filterClauseOperator = $sanitizedParams->getArray('filterClauseOperator');
|
||||
$filterClauseCriteria = $sanitizedParams->getArray('filterClauseCriteria');
|
||||
$filterClauseValue = $sanitizedParams->getArray('filterClauseValue');
|
||||
$filterClauseMapping = [];
|
||||
|
||||
$i = -1;
|
||||
foreach ($filterClauses as $filterClause) {
|
||||
$i++;
|
||||
|
||||
if ($filterClause == '')
|
||||
continue;
|
||||
|
||||
// Map the stop code received to the stop ref (if there is one)
|
||||
$filterClauseMapping[] = [
|
||||
'filterClause' => $filterClause,
|
||||
'filterClauseOperator' => isset($filterClauseOperator[$i]) ? $filterClauseOperator[$i] : '',
|
||||
'filterClauseCriteria' => isset($filterClauseCriteria[$i]) ? $filterClauseCriteria[$i] : '',
|
||||
'filterClauseValue' => isset($filterClauseValue[$i]) ? $filterClauseValue[$i] : '',
|
||||
];
|
||||
}
|
||||
|
||||
$feed->filter = json_encode([
|
||||
'filter' => $sanitizedParams->getString('filter'),
|
||||
'useFilteringClause' => $sanitizedParams->getCheckbox('useFilteringClause'),
|
||||
'filterClauses' => $filterClauseMapping
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @param $rssId
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function editForm(Request $request, Response $response, $id, $rssId)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$feed = $this->dataSetRssFactory->getById($rssId);
|
||||
|
||||
$columns = $dataSet->getColumn();
|
||||
$dateColumns = [];
|
||||
|
||||
foreach ($columns as $column) {
|
||||
if ($column->dataTypeId === 3)
|
||||
$dateColumns[] = $column;
|
||||
}
|
||||
|
||||
$this->getState()->template = 'dataset-rss-form-edit';
|
||||
$this->getState()->setData([
|
||||
'dataSet' => $dataSet,
|
||||
'feed' => $feed,
|
||||
'extra' => array_merge($feed->getSort(), $feed->getFilter(), ['columns' => $columns, 'dateColumns' => $dateColumns])
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @param $rssId
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @SWG\Put(
|
||||
* path="/dataset/{dataSetId}/rss/{rssId}",
|
||||
* operationId="dataSetRssEdit",
|
||||
* tags={"dataset"},
|
||||
* summary="Edit Rss",
|
||||
* description="Edit DataSet Rss Feed",
|
||||
* @SWG\Parameter(
|
||||
* name="dataSetId",
|
||||
* in="path",
|
||||
* description="The DataSet ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="rssId",
|
||||
* in="path",
|
||||
* description="The RSS ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="title",
|
||||
* in="formData",
|
||||
* description="The title for the RSS",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="title",
|
||||
* in="formData",
|
||||
* description="The author for the RSS",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="summaryColumnId",
|
||||
* in="formData",
|
||||
* description="The rssId to be used as each item summary",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="contentColumnId",
|
||||
* in="formData",
|
||||
* description="The columnId to be used as each item content",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="publishedDateColumnId",
|
||||
* in="formData",
|
||||
* description="The columnId to be used as each item published date",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="regeneratePsk",
|
||||
* in="formData",
|
||||
* description="Regenerate the PSK?",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function edit(Request $request, Response $response, $id, $rssId)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
if (!$this->getUser()->checkEditable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
if ($sanitizedParams->getString('title') == '') {
|
||||
throw new InvalidArgumentException(__('Please enter title'), 'title');
|
||||
}
|
||||
|
||||
if ($sanitizedParams->getString('author') == '') {
|
||||
throw new InvalidArgumentException(__('Please enter author name'), 'author');
|
||||
}
|
||||
|
||||
$feed = $this->dataSetRssFactory->getById($rssId);
|
||||
$feed->title = $sanitizedParams->getString('title');
|
||||
$feed->author = $sanitizedParams->getString('author');
|
||||
$feed->titleColumnId = $sanitizedParams->getInt('titleColumnId');
|
||||
$feed->summaryColumnId = $sanitizedParams->getInt('summaryColumnId');
|
||||
$feed->contentColumnId = $sanitizedParams->getInt('contentColumnId');
|
||||
$feed->publishedDateColumnId = $sanitizedParams->getInt('publishedDateColumnId');
|
||||
$this->handleFormFilterAndOrder($request, $response, $feed);
|
||||
|
||||
if ($sanitizedParams->getCheckbox('regeneratePsk')) {
|
||||
$feed->setNewPsk();
|
||||
}
|
||||
|
||||
$feed->save();
|
||||
|
||||
// Delete from the cache
|
||||
$this->pool->deleteItem('/dataset/rss/' . $feed->id);
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => sprintf(__('Edited %s'), $feed->title),
|
||||
'id' => $feed->id,
|
||||
'data' => $feed
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @param $rssId
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function deleteForm(Request $request, Response $response, $id, $rssId)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$feed = $this->dataSetRssFactory->getById($rssId);
|
||||
|
||||
$this->getState()->template = 'dataset-rss-form-delete';
|
||||
$this->getState()->setData([
|
||||
'dataSet' => $dataSet,
|
||||
'feed' => $feed
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @param $rssId
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @SWG\Delete(
|
||||
* path="/dataset/{dataSetId}/rss/{rssId}",
|
||||
* operationId="dataSetRSSDelete",
|
||||
* tags={"dataset"},
|
||||
* summary="Delete RSS",
|
||||
* description="Delete DataSet RSS",
|
||||
* @SWG\Parameter(
|
||||
* name="dataSetId",
|
||||
* in="path",
|
||||
* description="The DataSet ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="rssId",
|
||||
* in="path",
|
||||
* description="The RSS ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function delete(Request $request, Response $response, $id, $rssId)
|
||||
{
|
||||
$dataSet = $this->dataSetFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($dataSet)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$feed = $this->dataSetRssFactory->getById($rssId);
|
||||
$feed->delete();
|
||||
|
||||
// Delete from the cache
|
||||
$this->pool->deleteItem('/dataset/rss/' . $feed->id);
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Deleted %s'), $feed->title)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output feed
|
||||
* this is a public route (no authentication requried)
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $psk
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function feed(Request $request, Response $response, $psk)
|
||||
{
|
||||
$this->setNoOutput();
|
||||
|
||||
$this->getLog()->debug('RSS Feed Request with PSK ' . $psk);
|
||||
|
||||
// Try and get the feed using the PSK
|
||||
try {
|
||||
$feed = $this->dataSetRssFactory->getByPsk($psk);
|
||||
|
||||
// Get the DataSet out
|
||||
$dataSet = $this->dataSetFactory->getById($feed->dataSetId);
|
||||
|
||||
// What is the edit date of this data set
|
||||
$dataSetEditDate = ($dataSet->lastDataEdit == 0)
|
||||
? Carbon::now()->subMonths(2)
|
||||
: Carbon::createFromTimestamp($dataSet->lastDataEdit);
|
||||
|
||||
// Do we have this feed in the cache?
|
||||
$cache = $this->pool->getItem('/dataset/rss/' . $feed->id);
|
||||
|
||||
$output = $cache->get();
|
||||
|
||||
if ($cache->isMiss() || $cache->getCreation() < $dataSetEditDate) {
|
||||
// We need to recache
|
||||
$this->getLog()->debug('Generating RSS feed and saving to cache. Created on '
|
||||
. ($cache->getCreation()
|
||||
? $cache->getCreation()->format(DateFormatHelper::getSystemFormat())
|
||||
: 'never'));
|
||||
|
||||
$output = $this->generateFeed($feed, $dataSetEditDate, $dataSet);
|
||||
|
||||
$cache->set($output);
|
||||
$cache->expiresAfter(new \DateInterval('PT5M'));
|
||||
$this->pool->saveDeferred($cache);
|
||||
} else {
|
||||
$this->getLog()->debug('Serving from Cache');
|
||||
}
|
||||
|
||||
$response->withHeader('Content-Type', 'application/rss+xml');
|
||||
echo $output;
|
||||
} catch (NotFoundException) {
|
||||
$this->getState()->httpStatus = 404;
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Xibo\Entity\DataSetRss $feed
|
||||
* @param Carbon $dataSetEditDate
|
||||
* @param \Xibo\Entity\DataSet $dataSet
|
||||
* @return string
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
private function generateFeed($feed, $dataSetEditDate, $dataSet): string
|
||||
{
|
||||
// Create the start of our feed, its description, etc.
|
||||
$builder = Rss20FeedBuilder::create()
|
||||
->withTitle($feed->title)
|
||||
->withAuthor($feed->author)
|
||||
->withFeedUrl('')
|
||||
->withSiteUrl('')
|
||||
->withDate($dataSetEditDate);
|
||||
|
||||
$sort = $feed->getSort();
|
||||
$filter = $feed->getFilter();
|
||||
|
||||
// Get results, using the filter criteria
|
||||
// Ordering
|
||||
$ordering = '';
|
||||
|
||||
if ($sort['useOrderingClause'] == 1) {
|
||||
$ordering = $sort['sort'];
|
||||
} else {
|
||||
// Build an order string
|
||||
foreach ($sort['orderClauses'] as $clause) {
|
||||
$ordering .= $clause['orderClause'] . ' ' . $clause['orderClauseDirection'] . ',';
|
||||
}
|
||||
|
||||
$ordering = rtrim($ordering, ',');
|
||||
}
|
||||
|
||||
// Filtering
|
||||
$filtering = '';
|
||||
|
||||
if ($filter['useFilteringClause'] == 1) {
|
||||
$filtering = $filter['filter'];
|
||||
} else {
|
||||
// Build
|
||||
$i = 0;
|
||||
foreach ($filter['filterClauses'] as $clause) {
|
||||
$i++;
|
||||
$criteria = '';
|
||||
|
||||
switch ($clause['filterClauseCriteria']) {
|
||||
|
||||
case 'starts-with':
|
||||
$criteria = 'LIKE \'' . $clause['filterClauseValue'] . '%\'';
|
||||
break;
|
||||
|
||||
case 'ends-with':
|
||||
$criteria = 'LIKE \'%' . $clause['filterClauseValue'] . '\'';
|
||||
break;
|
||||
|
||||
case 'contains':
|
||||
$criteria = 'LIKE \'%' . $clause['filterClauseValue'] . '%\'';
|
||||
break;
|
||||
|
||||
case 'equals':
|
||||
$criteria = '= \'' . $clause['filterClauseValue'] . '\'';
|
||||
break;
|
||||
|
||||
case 'not-contains':
|
||||
$criteria = 'NOT LIKE \'%' . $clause['filterClauseValue'] . '%\'';
|
||||
break;
|
||||
|
||||
case 'not-starts-with':
|
||||
$criteria = 'NOT LIKE \'' . $clause['filterClauseValue'] . '%\'';
|
||||
break;
|
||||
|
||||
case 'not-ends-with':
|
||||
$criteria = 'NOT LIKE \'%' . $clause['filterClauseValue'] . '\'';
|
||||
break;
|
||||
|
||||
case 'not-equals':
|
||||
$criteria = '<> \'' . $clause['filterClauseValue'] . '\'';
|
||||
break;
|
||||
|
||||
case 'greater-than':
|
||||
$criteria = '> \'' . $clause['filterClauseValue'] . '\'';
|
||||
break;
|
||||
|
||||
case 'less-than':
|
||||
$criteria = '< \'' . $clause['filterClauseValue'] . '\'';
|
||||
break;
|
||||
|
||||
default:
|
||||
// Continue out of the switch and the loop (this takes us back to our foreach)
|
||||
continue 2;
|
||||
}
|
||||
|
||||
if ($i > 1)
|
||||
$filtering .= ' ' . $clause['filterClauseOperator'] . ' ';
|
||||
|
||||
// Ability to filter by not-empty and empty
|
||||
if ($clause['filterClauseCriteria'] == 'is-empty') {
|
||||
$filtering .= 'IFNULL(`' . $clause['filterClause'] . '`, \'\') = \'\'';
|
||||
} else if ($clause['filterClauseCriteria'] == 'is-not-empty') {
|
||||
$filtering .= 'IFNULL(`' . $clause['filterClause'] . '`, \'\') <> \'\'';
|
||||
} else {
|
||||
$filtering .= $clause['filterClause'] . ' ' . $criteria;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get an array representing the id->heading mappings
|
||||
$mappings = [];
|
||||
$columns = [];
|
||||
|
||||
if ($feed->titleColumnId != 0)
|
||||
$columns[] = $feed->titleColumnId;
|
||||
|
||||
if ($feed->summaryColumnId != 0)
|
||||
$columns[] = $feed->summaryColumnId;
|
||||
|
||||
if ($feed->contentColumnId != 0)
|
||||
$columns[] = $feed->contentColumnId;
|
||||
|
||||
if ($feed->publishedDateColumnId != 0)
|
||||
$columns[] = $feed->publishedDateColumnId;
|
||||
|
||||
foreach ($columns as $dataSetColumnId) {
|
||||
// Get the column definition this represents
|
||||
$column = $dataSet->getColumn($dataSetColumnId);
|
||||
/* @var \Xibo\Entity\DataSetColumn $column */
|
||||
|
||||
$mappings[$column->heading] = [
|
||||
'dataSetColumnId' => $dataSetColumnId,
|
||||
'heading' => $column->heading,
|
||||
'dataTypeId' => $column->dataTypeId
|
||||
];
|
||||
}
|
||||
|
||||
$filter = [
|
||||
'filter' => $filtering,
|
||||
'order' => $ordering
|
||||
];
|
||||
|
||||
// Set the timezone for SQL
|
||||
$dateNow = Carbon::now();
|
||||
|
||||
$this->store->setTimeZone($dateNow->format('P'));
|
||||
|
||||
// Get the data (complete table, filtered)
|
||||
$dataSetResults = $dataSet->getData($filter);
|
||||
|
||||
foreach ($dataSetResults as $row) {
|
||||
$item = Rss20ItemBuilder::create($builder);
|
||||
$item->withUrl('');
|
||||
|
||||
$hasContent = false;
|
||||
$hasDate = false;
|
||||
|
||||
// Go through the columns of each row
|
||||
foreach ($row as $key => $value) {
|
||||
// Is this one of the columns we're interested in?
|
||||
if (isset($mappings[$key])) {
|
||||
// Yes it is - which one?
|
||||
$hasContent = true;
|
||||
|
||||
if ($mappings[$key]['dataSetColumnId'] === $feed->titleColumnId) {
|
||||
$item->withTitle($value);
|
||||
} else if ($mappings[$key]['dataSetColumnId'] === $feed->summaryColumnId) {
|
||||
$item->withSummary($value);
|
||||
} else if ($mappings[$key]['dataSetColumnId'] === $feed->contentColumnId) {
|
||||
$item->withContent($value);
|
||||
} else if ($mappings[$key]['dataSetColumnId'] === $feed->publishedDateColumnId) {
|
||||
try {
|
||||
$date = Carbon::createFromTimestamp($value);
|
||||
} catch (InvalidDateException) {
|
||||
$date = $dataSetEditDate;
|
||||
}
|
||||
|
||||
if ($date !== null) {
|
||||
$item->withPublishedDate($date);
|
||||
$hasDate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$hasDate) {
|
||||
$item->withPublishedDate($dataSetEditDate);
|
||||
}
|
||||
|
||||
if ($hasContent) {
|
||||
$builder->withItem($item);
|
||||
}
|
||||
}
|
||||
|
||||
// Found, do things
|
||||
return $builder->build();
|
||||
}
|
||||
}
|
||||
100
lib/Controller/DataTablesDotNetTrait.php
Normal file
100
lib/Controller/DataTablesDotNetTrait.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2021 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Xibo\Support\Sanitizer\SanitizerInterface;
|
||||
|
||||
/**
|
||||
* Trait DataTablesDotNetTrait
|
||||
* @package Xibo\Controller
|
||||
*
|
||||
* Methods which implement the particular sorting/filtering requirements of DataTables.Net
|
||||
*/
|
||||
trait DataTablesDotNetTrait
|
||||
{
|
||||
/**
|
||||
* Set the filter
|
||||
* @param array $extraFilter
|
||||
* @param SanitizerInterface|null $sanitizedRequestParams
|
||||
* @return array
|
||||
*/
|
||||
protected function gridRenderFilter(array $extraFilter, $sanitizedRequestParams = null)
|
||||
{
|
||||
if ($sanitizedRequestParams === null) {
|
||||
return $extraFilter;
|
||||
}
|
||||
|
||||
// Handle filtering
|
||||
$filter = [];
|
||||
if ($sanitizedRequestParams->getInt('disablePaging') != 1) {
|
||||
$filter['start'] = $sanitizedRequestParams->getInt('start', ['default' => 0]);
|
||||
$filter['length'] = $sanitizedRequestParams->getInt('length', ['default' => 10]);
|
||||
}
|
||||
|
||||
$search = $sanitizedRequestParams->getArray('search', ['default' => []]);
|
||||
if (is_array($search) && isset($search['value'])) {
|
||||
$filter['search'] = $search['value'];
|
||||
} else if ($search != '') {
|
||||
$filter['search'] = $search;
|
||||
}
|
||||
|
||||
// Merge with any extra filter items that have been provided
|
||||
$filter = array_merge($extraFilter, $filter);
|
||||
|
||||
return $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sort order
|
||||
* @param SanitizerInterface|array $sanitizedRequestParams
|
||||
* @return array
|
||||
*/
|
||||
protected function gridRenderSort($sanitizedRequestParams)
|
||||
{
|
||||
if ($sanitizedRequestParams instanceof SanitizerInterface) {
|
||||
$columns = $sanitizedRequestParams->getArray('columns');
|
||||
$order = $sanitizedRequestParams->getArray('order');
|
||||
} else {
|
||||
$columns = $sanitizedRequestParams['columns'] ?? null;
|
||||
$order = $sanitizedRequestParams['order'] ?? null;
|
||||
}
|
||||
|
||||
if ($columns === null
|
||||
|| !is_array($columns)
|
||||
|| count($columns) <= 0
|
||||
|| $order === null
|
||||
|| !is_array($order)
|
||||
|| count($order) <= 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array_map(function ($element) use ($columns) {
|
||||
$val = (isset($columns[$element['column']]['name']) && $columns[$element['column']]['name'] != '')
|
||||
? $columns[$element['column']]['name']
|
||||
: $columns[$element['column']]['data'];
|
||||
$val = preg_replace('/[^A-Za-z0-9_]/', '', $val);
|
||||
return '`' . $val . '`' . (($element['dir'] == 'desc') ? ' DESC' : '');
|
||||
}, $order);
|
||||
}
|
||||
}
|
||||
621
lib/Controller/DayPart.php
Normal file
621
lib/Controller/DayPart.php
Normal file
@@ -0,0 +1,621 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (c) 2022 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\DayPartFactory;
|
||||
use Xibo\Factory\ScheduleFactory;
|
||||
use Xibo\Service\DisplayNotifyServiceInterface;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class DayPart
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class DayPart extends Base
|
||||
{
|
||||
/** @var DayPartFactory */
|
||||
private $dayPartFactory;
|
||||
|
||||
/** @var ScheduleFactory */
|
||||
private $scheduleFactory;
|
||||
|
||||
/** @var DisplayNotifyServiceInterface */
|
||||
private $displayNotifyService;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param DayPartFactory $dayPartFactory
|
||||
* @param ScheduleFactory $scheduleFactory
|
||||
* @param \Xibo\Service\DisplayNotifyServiceInterface $displayNotifyService
|
||||
*/
|
||||
public function __construct($dayPartFactory, $scheduleFactory, DisplayNotifyServiceInterface $displayNotifyService)
|
||||
{
|
||||
$this->dayPartFactory = $dayPartFactory;
|
||||
$this->scheduleFactory = $scheduleFactory;
|
||||
$this->displayNotifyService = $displayNotifyService;
|
||||
}
|
||||
|
||||
/**
|
||||
* View Route
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'daypart-page';
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search
|
||||
*
|
||||
* @SWG\Get(
|
||||
* path="/daypart",
|
||||
* operationId="dayPartSearch",
|
||||
* tags={"dayPart"},
|
||||
* summary="Daypart Search",
|
||||
* description="Search dayparts",
|
||||
* @SWG\Parameter(
|
||||
* name="dayPartId",
|
||||
* in="query",
|
||||
* description="The dayPart ID to Search",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="query",
|
||||
* description="The name of the dayPart to Search",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="embed",
|
||||
* in="query",
|
||||
* description="Embed related data such as exceptions",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/DayPart")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function grid(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getQueryParams());
|
||||
|
||||
$filter = [
|
||||
'dayPartId' => $sanitizedParams->getInt('dayPartId'),
|
||||
'name' => $sanitizedParams->getString('name'),
|
||||
'useRegexForName' => $sanitizedParams->getCheckbox('useRegexForName'),
|
||||
'isAlways' => $sanitizedParams->getInt('isAlways'),
|
||||
'isCustom' => $sanitizedParams->getInt('isCustom'),
|
||||
'isRetired' => $sanitizedParams->getInt('isRetired')
|
||||
];
|
||||
|
||||
$dayParts = $this->dayPartFactory->query($this->gridRenderSort($sanitizedParams), $this->gridRenderFilter($filter, $sanitizedParams));
|
||||
$embed = ($sanitizedParams->getString('embed') != null) ? explode(',', $sanitizedParams->getString('embed')) : [];
|
||||
|
||||
foreach ($dayParts as $dayPart) {
|
||||
/* @var \Xibo\Entity\DayPart $dayPart */
|
||||
if (!in_array('exceptions', $embed)){
|
||||
$dayPart->excludeProperty('exceptions');
|
||||
}
|
||||
if ($this->isApi($request))
|
||||
continue;
|
||||
|
||||
$dayPart->includeProperty('buttons');
|
||||
|
||||
if ($dayPart->isCustom !== 1
|
||||
&& $dayPart->isAlways !== 1
|
||||
&& $this->getUser()->featureEnabled('daypart.modify')
|
||||
) {
|
||||
// CRUD
|
||||
$dayPart->buttons[] = array(
|
||||
'id' => 'daypart_button_edit',
|
||||
'url' => $this->urlFor($request,'daypart.edit.form', ['id' => $dayPart->dayPartId]),
|
||||
'text' => __('Edit')
|
||||
);
|
||||
|
||||
if ($this->getUser()->checkDeleteable($dayPart)) {
|
||||
$dayPart->buttons[] = [
|
||||
'id' => 'daypart_button_delete',
|
||||
'url' => $this->urlFor($request,'daypart.delete.form', ['id' => $dayPart->dayPartId]),
|
||||
'text' => __('Delete'),
|
||||
'multi-select' => true,
|
||||
'dataAttributes' => [
|
||||
['name' => 'commit-url', 'value' => $this->urlFor($request,'daypart.delete', ['id' => $dayPart->dayPartId])],
|
||||
['name' => 'commit-method', 'value' => 'delete'],
|
||||
['name' => 'id', 'value' => 'daypart_button_delete'],
|
||||
['name' => 'text', 'value' => __('Delete')],
|
||||
['name' => 'sort-group', 'value' => 1],
|
||||
['name' => 'rowtitle', 'value' => $dayPart->name]
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->getUser()->checkPermissionsModifyable($dayPart)
|
||||
&& $this->getUser()->featureEnabled('daypart.modify')
|
||||
) {
|
||||
if (count($dayPart->buttons) > 0)
|
||||
$dayPart->buttons[] = ['divider' => true];
|
||||
|
||||
// Edit Permissions
|
||||
$dayPart->buttons[] = [
|
||||
'id' => 'daypart_button_permissions',
|
||||
'url' => $this->urlFor($request,'user.permissions.form', ['entity' => 'DayPart', 'id' => $dayPart->dayPartId]),
|
||||
'text' => __('Share'),
|
||||
'multi-select' => true,
|
||||
'dataAttributes' => [
|
||||
['name' => 'commit-url', 'value' => $this->urlFor($request,'user.permissions.multi', ['entity' => 'DayPart', 'id' => $dayPart->dayPartId])],
|
||||
['name' => 'commit-method', 'value' => 'post'],
|
||||
['name' => 'id', 'value' => 'daypart_button_permissions'],
|
||||
['name' => 'text', 'value' => __('Share')],
|
||||
['name' => 'rowtitle', 'value' => $dayPart->name],
|
||||
['name' => 'sort-group', 'value' => 2],
|
||||
['name' => 'custom-handler', 'value' => 'XiboMultiSelectPermissionsFormOpen'],
|
||||
['name' => 'custom-handler-url', 'value' => $this->urlFor($request,'user.permissions.multi.form', ['entity' => 'DayPart'])],
|
||||
['name' => 'content-id-name', 'value' => 'dayPartId']
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->dayPartFactory->countLast();
|
||||
$this->getState()->setData($dayParts);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Daypart Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function addForm(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'daypart-form-add';
|
||||
$this->getState()->setData([
|
||||
'extra' => [
|
||||
'exceptions' => []
|
||||
]
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Daypart
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function editForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$dayPart = $this->dayPartFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($dayPart)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
if ($dayPart->isAlways === 1 || $dayPart->isCustom === 1) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'daypart-form-edit';
|
||||
$this->getState()->setData([
|
||||
'dayPart' => $dayPart,
|
||||
'extra' => [
|
||||
'exceptions' => $dayPart->exceptions
|
||||
]
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Daypart
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function deleteForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$dayPart = $this->dayPartFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($dayPart)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
if ($dayPart->isAlways === 1 || $dayPart->isCustom === 1) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Get a count of schedules for this day part
|
||||
$schedules = $this->scheduleFactory->getByDayPartId($id);
|
||||
|
||||
$this->getState()->template = 'daypart-form-delete';
|
||||
$this->getState()->setData([
|
||||
'countSchedules' => count($schedules),
|
||||
'dayPart' => $dayPart
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add
|
||||
* @SWG\Post(
|
||||
* path="/daypart",
|
||||
* operationId="dayPartAdd",
|
||||
* tags={"dayPart"},
|
||||
* summary="Daypart Add",
|
||||
* description="Add a Daypart",
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="formData",
|
||||
* description="The Daypart Name",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="description",
|
||||
* in="formData",
|
||||
* description="A description for the dayPart",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="startTime",
|
||||
* in="formData",
|
||||
* description="The start time for this day part",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="endTime",
|
||||
* in="formData",
|
||||
* description="The end time for this day part",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="exceptionDays",
|
||||
* in="formData",
|
||||
* description="String array of exception days",
|
||||
* type="array",
|
||||
* required=false,
|
||||
* @SWG\Items(type="string")
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="exceptionStartTimes",
|
||||
* in="formData",
|
||||
* description="String array of exception start times to match the exception days",
|
||||
* type="array",
|
||||
* required=false,
|
||||
* @SWG\Items(type="string")
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="exceptionEndTimes",
|
||||
* in="formData",
|
||||
* description="String array of exception end times to match the exception days",
|
||||
* type="array",
|
||||
* required=false,
|
||||
* @SWG\Items(type="string")
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/DayPart"),
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new record",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function add(Request $request, Response $response)
|
||||
{
|
||||
$dayPart = $this->dayPartFactory->createEmpty();
|
||||
$this->handleCommonInputs($dayPart, $request);
|
||||
|
||||
$dayPart
|
||||
->setScheduleFactory($this->scheduleFactory, $this->displayNotifyService)
|
||||
->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => sprintf(__('Added %s'), $dayPart->name),
|
||||
'id' => $dayPart->dayPartId,
|
||||
'data' => $dayPart
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
* @SWG\Put(
|
||||
* path="/daypart/{dayPartId}",
|
||||
* operationId="dayPartEdit",
|
||||
* tags={"dayPart"},
|
||||
* summary="Daypart Edit",
|
||||
* description="Edit a Daypart",
|
||||
* @SWG\Parameter(
|
||||
* name="dayPartId",
|
||||
* in="path",
|
||||
* description="The Daypart Id",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="formData",
|
||||
* description="The Daypart Name",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="description",
|
||||
* in="formData",
|
||||
* description="A description for the dayPart",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="startTime",
|
||||
* in="formData",
|
||||
* description="The start time for this day part",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="endTime",
|
||||
* in="formData",
|
||||
* description="The end time for this day part",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="exceptionDays",
|
||||
* in="formData",
|
||||
* description="String array of exception days",
|
||||
* type="array",
|
||||
* required=false,
|
||||
* @SWG\Items(type="string")
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="exceptionStartTimes",
|
||||
* in="formData",
|
||||
* description="String array of exception start times to match the exception days",
|
||||
* type="array",
|
||||
* required=false,
|
||||
* @SWG\Items(type="string")
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="exceptionEndTimes",
|
||||
* in="formData",
|
||||
* description="String array of exception end times to match the exception days",
|
||||
* type="array",
|
||||
* required=false,
|
||||
* @SWG\Items(type="string")
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/DayPart")
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function edit(Request $request, Response $response, $id)
|
||||
{
|
||||
$dayPart = $this->dayPartFactory->getById($id)
|
||||
->load();
|
||||
|
||||
if (!$this->getUser()->checkEditable($dayPart)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
if ($dayPart->isAlways === 1 || $dayPart->isCustom === 1) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->handleCommonInputs($dayPart, $request);
|
||||
$dayPart
|
||||
->setScheduleFactory($this->scheduleFactory, $this->displayNotifyService)
|
||||
->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 200,
|
||||
'message' => sprintf(__('Edited %s'), $dayPart->name),
|
||||
'id' => $dayPart->dayPartId,
|
||||
'data' => $dayPart
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle common inputs
|
||||
* @param \Xibo\Entity\DayPart $dayPart
|
||||
* @param Request $request
|
||||
*/
|
||||
private function handleCommonInputs($dayPart, Request $request)
|
||||
{
|
||||
$dayPart->userId = $this->getUser()->userId;
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$dayPart->name = $sanitizedParams->getString('name');
|
||||
$dayPart->description = $sanitizedParams->getString('description');
|
||||
$dayPart->isRetired = $sanitizedParams->getCheckbox('isRetired');
|
||||
$dayPart->startTime = $sanitizedParams->getString('startTime');
|
||||
$dayPart->endTime = $sanitizedParams->getString('endTime');
|
||||
|
||||
// Exceptions
|
||||
$exceptionDays = $sanitizedParams->getArray('exceptionDays', ['default' => []]);
|
||||
$exceptionStartTimes = $sanitizedParams->getArray('exceptionStartTimes', ['default' => []]);
|
||||
$exceptionEndTimes = $sanitizedParams->getArray('exceptionEndTimes', ['default' => []]);
|
||||
|
||||
// Clear down existing exceptions
|
||||
$dayPart->exceptions = [];
|
||||
|
||||
$i = -1;
|
||||
foreach ($exceptionDays as $exceptionDay) {
|
||||
// Pull the corrisponding start/end time out of the same position in the array
|
||||
$i++;
|
||||
|
||||
$exceptionDayStartTime = isset($exceptionStartTimes[$i]) ? $exceptionStartTimes[$i] : '';
|
||||
$exceptionDayEndTime = isset($exceptionEndTimes[$i]) ? $exceptionEndTimes[$i] : '';
|
||||
|
||||
if ($exceptionDay == '' || $exceptionDayStartTime == '' || $exceptionDayEndTime == '')
|
||||
continue;
|
||||
|
||||
// Is this already set?
|
||||
$found = false;
|
||||
foreach ($dayPart->exceptions as $exception) {
|
||||
|
||||
if ($exception['day'] == $exceptionDay) {
|
||||
$exception['start'] = $exceptionDayStartTime;
|
||||
$exception['end'] = $exceptionDayEndTime;
|
||||
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise add it
|
||||
if (!$found) {
|
||||
$dayPart->exceptions[] = [
|
||||
'day' => $exceptionDay,
|
||||
'start' => $exceptionDayStartTime,
|
||||
'end' => $exceptionDayEndTime
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
* @SWG\Delete(
|
||||
* path="/daypart/{dayPartId}",
|
||||
* operationId="dayPartDelete",
|
||||
* tags={"dayPart"},
|
||||
* summary="Delete DayPart",
|
||||
* description="Delete the provided dayPart",
|
||||
* @SWG\Parameter(
|
||||
* name="dayPartId",
|
||||
* in="path",
|
||||
* description="The Daypart Id to Delete",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function delete(Request $request, Response $response, $id)
|
||||
{
|
||||
$dayPart = $this->dayPartFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($dayPart)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
if ($dayPart->isSystemDayPart()) {
|
||||
throw new InvalidArgumentException(__('Cannot Delete system specific DayParts'));
|
||||
}
|
||||
|
||||
$dayPart
|
||||
->setScheduleFactory($this->scheduleFactory, $this->displayNotifyService)
|
||||
->delete();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Deleted %s'), $dayPart->name)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
698
lib/Controller/Developer.php
Normal file
698
lib/Controller/Developer.php
Normal file
@@ -0,0 +1,698 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\ModuleFactory;
|
||||
use Xibo\Factory\ModuleTemplateFactory;
|
||||
use Xibo\Helper\SendFile;
|
||||
use Xibo\Service\MediaService;
|
||||
use Xibo\Service\UploadService;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\ConfigurationException;
|
||||
use Xibo\Support\Exception\ControllerNotImplemented;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class Module
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class Developer extends Base
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ModuleFactory $moduleFactory,
|
||||
private readonly ModuleTemplateFactory $moduleTemplateFactory
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the module templates page
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Slim\Http\Response
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function displayTemplatePage(Request $request, Response $response): Response
|
||||
{
|
||||
$this->getState()->template = 'developer-template-page';
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Module templates in a grid
|
||||
* @param \Slim\Http\ServerRequest $request
|
||||
* @param \Slim\Http\Response $response
|
||||
* @return \Slim\Http\Response
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function templateGrid(Request $request, Response $response): Response
|
||||
{
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
|
||||
$templates = $this->moduleTemplateFactory->loadUserTemplates(
|
||||
$this->gridRenderSort($params),
|
||||
$this->gridRenderFilter(
|
||||
[
|
||||
'id' => $params->getInt('id'),
|
||||
'templateId' => $params->getString('templateId'),
|
||||
'dataType' => $params->getString('dataType'),
|
||||
],
|
||||
$params
|
||||
)
|
||||
);
|
||||
|
||||
foreach ($templates as $template) {
|
||||
if ($this->isApi($request)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$template->includeProperty('buttons');
|
||||
|
||||
if ($this->getUser()->checkEditable($template) &&
|
||||
$this->getUser()->featureEnabled('developer.edit')
|
||||
) {
|
||||
// Edit button
|
||||
$template->buttons[] = [
|
||||
'id' => 'template_button_edit',
|
||||
'url' => $this->urlFor($request, 'developer.templates.view.edit', ['id' => $template->id]),
|
||||
'text' => __('Edit'),
|
||||
'class' => 'XiboRedirectButton',
|
||||
];
|
||||
|
||||
$template->buttons[] = [
|
||||
'id' => 'template_button_export',
|
||||
'linkType' => '_self', 'external' => true,
|
||||
'url' => $this->urlFor($request, 'developer.templates.export', ['id' => $template->id]),
|
||||
'text' => __('Export XML'),
|
||||
];
|
||||
|
||||
$template->buttons[] = [
|
||||
'id' => 'template_button_copy',
|
||||
'url' => $this->urlFor($request, 'developer.templates.form.copy', ['id' => $template->id]),
|
||||
'text' => __('Copy'),
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->getUser()->featureEnabled('developer.edit') &&
|
||||
$this->getUser()->checkPermissionsModifyable($template)
|
||||
) {
|
||||
$template->buttons[] = ['divider' => true];
|
||||
// Permissions for Module Template
|
||||
$template->buttons[] = [
|
||||
'id' => 'template_button_permissions',
|
||||
'url' => $this->urlFor(
|
||||
$request,
|
||||
'user.permissions.form',
|
||||
['entity' => 'ModuleTemplate', 'id' => $template->id]
|
||||
),
|
||||
'text' => __('Share'),
|
||||
'multi-select' => true,
|
||||
'dataAttributes' => [
|
||||
[
|
||||
'name' => 'commit-url',
|
||||
'value' => $this->urlFor(
|
||||
$request,
|
||||
'user.permissions.multi',
|
||||
['entity' => 'ModuleTemplate', 'id' => $template->id]
|
||||
)
|
||||
],
|
||||
['name' => 'commit-method', 'value' => 'post'],
|
||||
['name' => 'id', 'value' => 'template_button_permissions'],
|
||||
['name' => 'text', 'value' => __('Share')],
|
||||
['name' => 'rowtitle', 'value' => $template->templateId],
|
||||
['name' => 'sort-group', 'value' => 2],
|
||||
['name' => 'custom-handler', 'value' => 'XiboMultiSelectPermissionsFormOpen'],
|
||||
[
|
||||
'name' => 'custom-handler-url',
|
||||
'value' => $this->urlFor(
|
||||
$request,
|
||||
'user.permissions.multi.form',
|
||||
['entity' => 'ModuleTemplate']
|
||||
)
|
||||
],
|
||||
['name' => 'content-id-name', 'value' => 'id']
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->getUser()->checkDeleteable($template) &&
|
||||
$this->getUser()->featureEnabled('developer.delete')
|
||||
) {
|
||||
$template->buttons[] = ['divider' => true];
|
||||
// Delete button
|
||||
$template->buttons[] = [
|
||||
'id' => 'template_button_delete',
|
||||
'url' => $this->urlFor($request, 'developer.templates.form.delete', ['id' => $template->id]),
|
||||
'text' => __('Delete'),
|
||||
'multi-select' => true,
|
||||
'dataAttributes' => [
|
||||
[
|
||||
'name' => 'commit-url',
|
||||
'value' => $this->urlFor(
|
||||
$request,
|
||||
'developer.templates.delete',
|
||||
['id' => $template->id]
|
||||
)
|
||||
],
|
||||
['name' => 'commit-method', 'value' => 'delete'],
|
||||
['name' => 'id', 'value' => 'template_button_delete'],
|
||||
['name' => 'text', 'value' => __('Delete')],
|
||||
['name' => 'sort-group', 'value' => 1],
|
||||
['name' => 'rowtitle', 'value' => $template->templateId]
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->moduleTemplateFactory->countLast();
|
||||
$this->getState()->setData($templates);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an add form for a module template
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Slim\Http\Response
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function templateAddForm(Request $request, Response $response): Response
|
||||
{
|
||||
$this->getState()->template = 'developer-template-form-add';
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the module template page
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param mixed $id The template ID to edit.
|
||||
* @return \Slim\Http\Response
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function displayTemplateEditPage(Request $request, Response $response, $id): Response
|
||||
{
|
||||
$template = $this->moduleTemplateFactory->getUserTemplateById($id);
|
||||
if ($template->ownership !== 'user') {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'developer-template-edit-page';
|
||||
$this->getState()->setData([
|
||||
'template' => $template,
|
||||
'propertiesJSON' => json_encode($template->properties),
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a module template
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Slim\Http\Response
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function templateAdd(Request $request, Response $response): Response
|
||||
{
|
||||
// When adding a template we just save the XML
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
|
||||
$templateId = $params->getString('templateId', ['throw' => function () {
|
||||
throw new InvalidArgumentException(__('Please supply a unique template ID'), 'templateId');
|
||||
}]);
|
||||
$title = $params->getString('title', ['throw' => function () {
|
||||
throw new InvalidArgumentException(__('Please supply a title'), 'title');
|
||||
}]);
|
||||
$dataType = $params->getString('dataType', ['throw' => function () {
|
||||
throw new InvalidArgumentException(__('Please supply a data type'), 'dataType');
|
||||
}]);
|
||||
$showIn = $params->getString('showIn', ['throw' => function () {
|
||||
throw new InvalidArgumentException(
|
||||
__('Please select relevant editor which should show this Template'),
|
||||
'showIn'
|
||||
);
|
||||
}]);
|
||||
|
||||
// do we have a template selected?
|
||||
if (!empty($params->getString('copyTemplateId'))) {
|
||||
// get the selected template
|
||||
$copyTemplate = $this->moduleTemplateFactory->getByDataTypeAndId(
|
||||
$dataType,
|
||||
$params->getString('copyTemplateId')
|
||||
);
|
||||
|
||||
// get the template xml and load to document.
|
||||
$xml = new \DOMDocument();
|
||||
$xml->loadXML($copyTemplate->getXml());
|
||||
|
||||
// get template node, make adjustments from the form
|
||||
$templateNode = $xml->getElementsByTagName('template')[0];
|
||||
$this->setNode($xml, 'id', $templateId, false, $templateNode);
|
||||
$this->setNode($xml, 'title', $title, false, $templateNode);
|
||||
$this->setNode($xml, 'showIn', $showIn, false, $templateNode);
|
||||
|
||||
// create template with updated xml.
|
||||
$template = $this->moduleTemplateFactory->createUserTemplate($xml->saveXML());
|
||||
} else {
|
||||
// The most basic template possible.
|
||||
$template = $this->moduleTemplateFactory->createUserTemplate('<?xml version="1.0"?>
|
||||
<template>
|
||||
<id>' . $templateId . '</id>
|
||||
<title>' . $title . '</title>
|
||||
<type>static</type>
|
||||
<dataType>' . $dataType . '</dataType>
|
||||
<showIn>'. $showIn . '</showIn>
|
||||
<properties></properties>
|
||||
</template>');
|
||||
}
|
||||
|
||||
$template->ownerId = $this->getUser()->userId;
|
||||
$template->save();
|
||||
|
||||
$this->getState()->hydrate([
|
||||
'httpState' => 201,
|
||||
'message' => __('Added'),
|
||||
'id' => $template->id,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a module template
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return Response
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function templateEdit(Request $request, Response $response, $id): Response
|
||||
{
|
||||
$template = $this->moduleTemplateFactory->getUserTemplateById($id);
|
||||
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
$templateId = $params->getString('templateId', ['throw' => function () {
|
||||
throw new InvalidArgumentException(__('Please supply a unique template ID'), 'templateId');
|
||||
}]);
|
||||
$title = $params->getString('title', ['throw' => function () {
|
||||
throw new InvalidArgumentException(__('Please supply a title'), 'title');
|
||||
}]);
|
||||
$dataType = $params->getString('dataType', ['throw' => function () {
|
||||
throw new InvalidArgumentException(__('Please supply a data type'), 'dataType');
|
||||
}]);
|
||||
$showIn = $params->getString('showIn', ['throw' => function () {
|
||||
throw new InvalidArgumentException(
|
||||
__('Please select relevant editor which should show this Template'),
|
||||
'showIn'
|
||||
);
|
||||
}]);
|
||||
|
||||
$template->dataType = $dataType;
|
||||
$template->isEnabled = $params->getCheckbox('enabled');
|
||||
|
||||
// TODO: validate?
|
||||
$twig = $params->getParam('twig');
|
||||
$hbs = $params->getParam('hbs');
|
||||
$style = $params->getParam('style');
|
||||
$head = $params->getParam('head');
|
||||
$properties = $params->getParam('properties');
|
||||
$onTemplateRender = $params->getParam('onTemplateRender');
|
||||
$onTemplateVisible = $params->getParam('onTemplateVisible');
|
||||
|
||||
// We need to edit the XML we have for this template.
|
||||
$document = $template->getDocument();
|
||||
|
||||
// Root nodes
|
||||
$template->templateId = $templateId;
|
||||
$this->setNode($document, 'id', $templateId, false);
|
||||
$this->setNode($document, 'title', $title, false);
|
||||
$this->setNode($document, 'showIn', $showIn, false);
|
||||
$this->setNode($document, 'dataType', $dataType, false);
|
||||
$this->setNode($document, 'onTemplateRender', $onTemplateRender);
|
||||
$this->setNode($document, 'onTemplateVisible', $onTemplateVisible);
|
||||
|
||||
// Stencil nodes.
|
||||
$stencilNodes = $document->getElementsByTagName('stencil');
|
||||
if ($stencilNodes->count() <= 0) {
|
||||
$stencilNode = $document->createElement('stencil');
|
||||
$document->documentElement->appendChild($stencilNode);
|
||||
} else {
|
||||
$stencilNode = $stencilNodes[0];
|
||||
}
|
||||
|
||||
$this->setNode($document, 'twig', $twig, true, $stencilNode);
|
||||
$this->setNode($document, 'hbs', $hbs, true, $stencilNode);
|
||||
$this->setNode($document, 'style', $style, true, $stencilNode);
|
||||
$this->setNode($document, 'head', $head, true, $stencilNode);
|
||||
|
||||
// Properties.
|
||||
// this is different because we want to replace the properties node with a new one.
|
||||
if (!empty($properties)) {
|
||||
// parse json and create a new properties node
|
||||
$newPropertiesXml = $this->moduleTemplateFactory->parseJsonPropertiesToXml($properties);
|
||||
|
||||
$propertiesNodes = $document->getElementsByTagName('properties');
|
||||
|
||||
if ($propertiesNodes->count() <= 0) {
|
||||
$document->documentElement->appendChild(
|
||||
$document->importNode($newPropertiesXml->documentElement, true)
|
||||
);
|
||||
} else {
|
||||
$document->documentElement->replaceChild(
|
||||
$document->importNode($newPropertiesXml->documentElement, true),
|
||||
$propertiesNodes[0]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// All done.
|
||||
$template->setXml($document->saveXML());
|
||||
$template->save();
|
||||
|
||||
if ($params->getCheckbox('isInvalidateWidget')) {
|
||||
$template->invalidate();
|
||||
}
|
||||
|
||||
$this->getState()->hydrate([
|
||||
'message' => sprintf(__('Edited %s'), $template->title),
|
||||
'id' => $template->id,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to set a node.
|
||||
* @param \DOMDocument $document
|
||||
* @param string $node
|
||||
* @param string $value
|
||||
* @param bool $cdata
|
||||
* @param \DOMElement|null $childNode
|
||||
* @return void
|
||||
* @throws \DOMException
|
||||
*/
|
||||
private function setNode(
|
||||
\DOMDocument $document,
|
||||
string $node,
|
||||
string $value,
|
||||
bool $cdata = true,
|
||||
?\DOMElement $childNode = null
|
||||
): void {
|
||||
$addTo = $childNode ?? $document->documentElement;
|
||||
|
||||
$nodes = $addTo->getElementsByTagName($node);
|
||||
if ($nodes->count() <= 0) {
|
||||
if ($cdata) {
|
||||
$element = $document->createElement($node);
|
||||
$cdata = $document->createCDATASection($value);
|
||||
$element->appendChild($cdata);
|
||||
} else {
|
||||
$element = $document->createElement($node, $value);
|
||||
}
|
||||
|
||||
$addTo->appendChild($element);
|
||||
} else {
|
||||
/** @var \DOMElement $element */
|
||||
$element = $nodes[0];
|
||||
if ($cdata) {
|
||||
$cdata = $document->createCDATASection($value);
|
||||
$element->textContent = $value;
|
||||
|
||||
if ($element->firstChild !== null) {
|
||||
$element->replaceChild($cdata, $element->firstChild);
|
||||
} else {
|
||||
//$element->textContent = '';
|
||||
$element->appendChild($cdata);
|
||||
}
|
||||
} else {
|
||||
$element->textContent = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getAvailableDataTypes(Request $request, Response $response)
|
||||
{
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
$dataTypes = $this->moduleFactory->getAllDataTypes();
|
||||
|
||||
if ($params->getString('dataType') !== null) {
|
||||
foreach ($dataTypes as $dataType) {
|
||||
if ($dataType->id === $params->getString('dataType')) {
|
||||
$dataTypes = [$dataType];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = 0;
|
||||
$this->getState()->setData($dataTypes);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export module template
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function templateExport(Request $request, Response $response, $id): Response|ResponseInterface
|
||||
{
|
||||
$template = $this->moduleTemplateFactory->getUserTemplateById($id);
|
||||
|
||||
if ($template->ownership !== 'user') {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$tempFileName = $this->getConfig()->getSetting('LIBRARY_LOCATION') . 'temp/' . $template->templateId . '.xml';
|
||||
|
||||
$template->getDocument()->save($tempFileName);
|
||||
|
||||
$this->setNoOutput(true);
|
||||
|
||||
return $this->render($request, SendFile::decorateResponse(
|
||||
$response,
|
||||
$this->getConfig()->getSetting('SENDFILE_MODE'),
|
||||
$tempFileName,
|
||||
$template->templateId . '.xml'
|
||||
)->withHeader('Content-Type', 'text/xml;charset=utf-8'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Import xml file and create module template
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return ResponseInterface|Response
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
public function templateImport(Request $request, Response $response): Response|ResponseInterface
|
||||
{
|
||||
$this->getLog()->debug('Import Module Template');
|
||||
|
||||
$libraryFolder = $this->getConfig()->getSetting('LIBRARY_LOCATION');
|
||||
|
||||
// Make sure the library exists
|
||||
MediaService::ensureLibraryExists($libraryFolder);
|
||||
|
||||
$options = [
|
||||
'upload_dir' => $libraryFolder . 'temp/',
|
||||
'accept_file_types' => '/\.xml/i',
|
||||
'libraryQuotaFull' => false,
|
||||
];
|
||||
|
||||
$this->getLog()->debug('Hand off to Upload Handler with options: ' . json_encode($options));
|
||||
|
||||
// Hand off to the Upload Handler provided by jquery-file-upload
|
||||
$uploadService = new UploadService($libraryFolder . 'temp/', $options, $this->getLog(), $this->getState());
|
||||
$uploadHandler = $uploadService->createUploadHandler();
|
||||
|
||||
$uploadHandler->setPostProcessor(function ($file, $uploadHandler) use ($libraryFolder) {
|
||||
// Return right away if the file already has an error.
|
||||
if (!empty($file->error)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
$this->getUser()->isQuotaFullByUser(true);
|
||||
|
||||
$filePath = $libraryFolder . 'temp/' . $file->fileName;
|
||||
|
||||
// load the xml from uploaded file
|
||||
$xml = new \DOMDocument();
|
||||
$xml->load($filePath);
|
||||
|
||||
// Add the Template
|
||||
$moduleTemplate = $this->moduleTemplateFactory->createUserTemplate($xml->saveXML());
|
||||
$moduleTemplate->ownerId = $this->getUser()->userId;
|
||||
$moduleTemplate->save();
|
||||
|
||||
// Tidy up the temporary file
|
||||
@unlink($filePath);
|
||||
|
||||
return $file;
|
||||
});
|
||||
|
||||
$uploadHandler->post();
|
||||
|
||||
$this->setNoOutput(true);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show module template copy form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return Response|ResponseInterface
|
||||
* @throws AccessDeniedException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function templateCopyForm(Request $request, Response $response, $id): Response|ResponseInterface
|
||||
{
|
||||
$moduleTemplate = $this->moduleTemplateFactory->getUserTemplateById($id);
|
||||
|
||||
if (!$this->getUser()->checkViewable($moduleTemplate)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'developer-template-form-copy';
|
||||
$this->getState()->setData([
|
||||
'template' => $moduleTemplate,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy module template
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return Response|ResponseInterface
|
||||
* @throws AccessDeniedException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function templateCopy(Request $request, Response $response, $id): Response|ResponseInterface
|
||||
{
|
||||
$moduleTemplate = $this->moduleTemplateFactory->getUserTemplateById($id);
|
||||
|
||||
if (!$this->getUser()->checkViewable($moduleTemplate)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
|
||||
$newTemplate = clone $moduleTemplate;
|
||||
$newTemplate->templateId = $params->getString('templateId');
|
||||
$newTemplate->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => sprintf(__('Copied as %s'), $newTemplate->templateId),
|
||||
'id' => $newTemplate->id,
|
||||
'data' => $newTemplate
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show module template delete form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return Response|ResponseInterface
|
||||
* @throws AccessDeniedException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function templateDeleteForm(Request $request, Response $response, $id): Response|ResponseInterface
|
||||
{
|
||||
$moduleTemplate = $this->moduleTemplateFactory->getUserTemplateById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($moduleTemplate)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'developer-template-form-delete';
|
||||
$this->getState()->setData([
|
||||
'template' => $moduleTemplate,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete module template
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return Response|ResponseInterface
|
||||
* @throws AccessDeniedException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function templateDelete(Request $request, Response $response, $id): Response|ResponseInterface
|
||||
{
|
||||
$moduleTemplate = $this->moduleTemplateFactory->getUserTemplateById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($moduleTemplate)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$moduleTemplate->delete();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Deleted %s'), $moduleTemplate->templateId)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
3196
lib/Controller/Display.php
Normal file
3196
lib/Controller/Display.php
Normal file
File diff suppressed because it is too large
Load Diff
3029
lib/Controller/DisplayGroup.php
Normal file
3029
lib/Controller/DisplayGroup.php
Normal file
File diff suppressed because it is too large
Load Diff
698
lib/Controller/DisplayProfile.php
Normal file
698
lib/Controller/DisplayProfile.php
Normal file
@@ -0,0 +1,698 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Stash\Interfaces\PoolInterface;
|
||||
use Xibo\Factory\CommandFactory;
|
||||
use Xibo\Factory\DayPartFactory;
|
||||
use Xibo\Factory\DisplayProfileFactory;
|
||||
use Xibo\Factory\PlayerVersionFactory;
|
||||
use Xibo\Helper\DateFormatHelper;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class DisplayProfile
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class DisplayProfile extends Base
|
||||
{
|
||||
use DisplayProfileConfigFields;
|
||||
|
||||
/** @var PoolInterface */
|
||||
private $pool;
|
||||
|
||||
/**
|
||||
* @var DayPartFactory
|
||||
*/
|
||||
private $dayPartFactory;
|
||||
|
||||
/**
|
||||
* @var DisplayProfileFactory
|
||||
*/
|
||||
private $displayProfileFactory;
|
||||
|
||||
/**
|
||||
* @var CommandFactory
|
||||
*/
|
||||
private $commandFactory;
|
||||
|
||||
/** @var PlayerVersionFactory */
|
||||
private $playerVersionFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param PoolInterface $pool
|
||||
* @param DisplayProfileFactory $displayProfileFactory
|
||||
* @param CommandFactory $commandFactory
|
||||
* @param PlayerVersionFactory $playerVersionFactory
|
||||
* @param DayPartFactory $dayPartFactory
|
||||
*/
|
||||
public function __construct($pool, $displayProfileFactory, $commandFactory, $playerVersionFactory, $dayPartFactory)
|
||||
{
|
||||
$this->pool = $pool;
|
||||
$this->displayProfileFactory = $displayProfileFactory;
|
||||
$this->commandFactory = $commandFactory;
|
||||
$this->playerVersionFactory = $playerVersionFactory;
|
||||
$this->dayPartFactory = $dayPartFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include display page template page based on sub page selected
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
function displayPage(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'displayprofile-page';
|
||||
$this->getState()->setData([
|
||||
'types' => $this->displayProfileFactory->getAvailableTypes()
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/displayprofile",
|
||||
* operationId="displayProfileSearch",
|
||||
* tags={"displayprofile"},
|
||||
* summary="Display Profile Search",
|
||||
* description="Search this users Display Profiles",
|
||||
* @SWG\Parameter(
|
||||
* name="displayProfileId",
|
||||
* in="query",
|
||||
* description="Filter by DisplayProfile Id",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="displayProfile",
|
||||
* in="query",
|
||||
* description="Filter by DisplayProfile Name",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="type",
|
||||
* in="query",
|
||||
* description="Filter by DisplayProfile Type (windows|android|lg)",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="embed",
|
||||
* in="query",
|
||||
* description="Embed related data such as config,commands,configWithDefault",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/DisplayProfile")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
function grid(Request $request, Response $response)
|
||||
{
|
||||
$parsedQueryParams = $this->getSanitizer($request->getQueryParams());
|
||||
|
||||
$filter = [
|
||||
'displayProfileId' => $parsedQueryParams->getInt('displayProfileId'),
|
||||
'displayProfile' => $parsedQueryParams->getString('displayProfile'),
|
||||
'useRegexForName' => $parsedQueryParams->getCheckbox('useRegexForName'),
|
||||
'type' => $parsedQueryParams->getString('type'),
|
||||
'logicalOperatorName' => $parsedQueryParams->getString('logicalOperatorName'),
|
||||
];
|
||||
|
||||
$embed = ($parsedQueryParams->getString('embed') != null)
|
||||
? explode(',', $parsedQueryParams->getString('embed'))
|
||||
: [];
|
||||
|
||||
$profiles = $this->displayProfileFactory->query(
|
||||
$this->gridRenderSort($parsedQueryParams),
|
||||
$this->gridRenderFilter($filter, $parsedQueryParams)
|
||||
);
|
||||
|
||||
foreach ($profiles as $profile) {
|
||||
// Load the config
|
||||
$profile->load([
|
||||
'loadConfig' => in_array('config', $embed),
|
||||
'loadCommands' => in_array('commands', $embed)
|
||||
]);
|
||||
|
||||
if (in_array('configWithDefault', $embed)) {
|
||||
$profile->includeProperty('configDefault');
|
||||
}
|
||||
|
||||
if (!in_array('config', $embed)) {
|
||||
$profile->excludeProperty('config');
|
||||
}
|
||||
|
||||
if ($this->isApi($request)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$profile->includeProperty('buttons');
|
||||
|
||||
if ($this->getUser()->featureEnabled('displayprofile.modify')) {
|
||||
// Default Layout
|
||||
$profile->buttons[] = array(
|
||||
'id' => 'displayprofile_button_edit',
|
||||
'url' => $this->urlFor(
|
||||
$request,
|
||||
'displayProfile.edit.form',
|
||||
['id' => $profile->displayProfileId]
|
||||
),
|
||||
'text' => __('Edit')
|
||||
);
|
||||
|
||||
$profile->buttons[] = array(
|
||||
'id' => 'displayprofile_button_copy',
|
||||
'url' => $this->urlFor(
|
||||
$request,
|
||||
'displayProfile.copy.form',
|
||||
['id' => $profile->displayProfileId]
|
||||
),
|
||||
'text' => __('Copy')
|
||||
);
|
||||
|
||||
if ($this->getUser()->checkDeleteable($profile)) {
|
||||
$profile->buttons[] = array(
|
||||
'id' => 'displayprofile_button_delete',
|
||||
'url' => $this->urlFor(
|
||||
$request,
|
||||
'displayProfile.delete.form',
|
||||
['id' => $profile->displayProfileId]
|
||||
),
|
||||
'text' => __('Delete')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->displayProfileFactory->countLast();
|
||||
$this->getState()->setData($profiles);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display Profile Add Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
function addForm(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'displayprofile-form-add';
|
||||
$this->getState()->setData([
|
||||
'types' => $this->displayProfileFactory->getAvailableTypes()
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display Profile Add
|
||||
*
|
||||
* @SWG\Post(
|
||||
* path="/displayprofile",
|
||||
* operationId="displayProfileAdd",
|
||||
* tags={"displayprofile"},
|
||||
* summary="Add Display Profile",
|
||||
* description="Add a Display Profile",
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="formData",
|
||||
* description="The Name of the Display Profile",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="type",
|
||||
* in="formData",
|
||||
* description="The Client Type this Profile will apply to",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="isDefault",
|
||||
* in="formData",
|
||||
* description="Flag indicating if this is the default profile for the client type",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/DisplayProfile"),
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new record",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function add(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$displayProfile = $this->displayProfileFactory->createEmpty();
|
||||
$displayProfile->name = $sanitizedParams->getString('name');
|
||||
$displayProfile->type = $sanitizedParams->getString('type');
|
||||
$displayProfile->isDefault = $sanitizedParams->getCheckbox('isDefault');
|
||||
$displayProfile->userId = $this->getUser()->userId;
|
||||
$displayProfile->isCustom = $this->displayProfileFactory->isCustomType($displayProfile->type);
|
||||
|
||||
// We do not set any config at this point, so that unless the user chooses to edit the display profile
|
||||
// our defaults in the Display Profile Factory take effect
|
||||
$displayProfile->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => sprintf(__('Added %s'), $displayProfile->name),
|
||||
'id' => $displayProfile->displayProfileId,
|
||||
'data' => $displayProfile
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Profile Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function editForm(Request $request, Response $response, $id)
|
||||
{
|
||||
// Create a form out of the config object.
|
||||
$displayProfile = $this->displayProfileFactory->getById($id);
|
||||
|
||||
// Check permissions
|
||||
if ($this->getUser()->userTypeId != 1 && $this->getUser()->userId != $displayProfile->userId) {
|
||||
throw new AccessDeniedException(__('You do not have permission to edit this profile'));
|
||||
}
|
||||
|
||||
// Player Version Setting
|
||||
$versionId = $displayProfile->type === 'chromeOS'
|
||||
? $displayProfile->getSetting('playerVersionId')
|
||||
: $displayProfile->getSetting('versionMediaId');
|
||||
|
||||
$playerVersions = [];
|
||||
|
||||
// Daypart - Operating Hours
|
||||
$dayPartId = $displayProfile->getSetting('dayPartId');
|
||||
$dayparts = [];
|
||||
|
||||
// Get the Player Version for this display profile type
|
||||
if ($versionId !== null) {
|
||||
try {
|
||||
$playerVersions[] = $this->playerVersionFactory->getById($versionId);
|
||||
} catch (NotFoundException) {
|
||||
$this->getLog()->debug('Unknown versionId set on Display Profile. '
|
||||
. $displayProfile->displayProfileId);
|
||||
}
|
||||
}
|
||||
|
||||
if ($dayPartId !== null) {
|
||||
try {
|
||||
$dayparts[] = $this->dayPartFactory->getById($dayPartId);
|
||||
} catch (NotFoundException $e) {
|
||||
$this->getLog()->debug('Unknown dayPartId set on Display Profile. ' . $displayProfile->displayProfileId);
|
||||
}
|
||||
}
|
||||
|
||||
// elevated logs
|
||||
$elevateLogsUntil = $displayProfile->getSetting('elevateLogsUntil');
|
||||
$elevateLogsUntilIso = !empty($elevateLogsUntil)
|
||||
? Carbon::createFromTimestamp($elevateLogsUntil)->format(DateFormatHelper::getSystemFormat())
|
||||
: null;
|
||||
$displayProfile->setUnmatchedProperty('elevateLogsUntilIso', $elevateLogsUntilIso);
|
||||
|
||||
$this->getState()->template = 'displayprofile-form-edit';
|
||||
$this->getState()->setData([
|
||||
'displayProfile' => $displayProfile,
|
||||
'commands' => $displayProfile->commands,
|
||||
'versions' => $playerVersions,
|
||||
'lockOptions' => json_decode($displayProfile->getSetting('lockOptions', '[]'), true),
|
||||
'dayParts' => $dayparts
|
||||
]);
|
||||
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
* @SWG\Put(
|
||||
* path="/displayprofile/{displayProfileId}",
|
||||
* operationId="displayProfileEdit",
|
||||
* tags={"displayprofile"},
|
||||
* summary="Edit Display Profile",
|
||||
* description="Edit a Display Profile",
|
||||
* @SWG\Parameter(
|
||||
* name="displayProfileId",
|
||||
* in="path",
|
||||
* description="The Display Profile ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="formData",
|
||||
* description="The Name of the Display Profile",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="type",
|
||||
* in="formData",
|
||||
* description="The Client Type this Profile will apply to",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="isDefault",
|
||||
* in="formData",
|
||||
* description="Flag indicating if this is the default profile for the client type",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function edit(Request $request, Response $response, $id)
|
||||
{
|
||||
// Create a form out of the config object.
|
||||
$displayProfile = $this->displayProfileFactory->getById($id);
|
||||
|
||||
$parsedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
if ($this->getUser()->userTypeId != 1 && $this->getUser()->userId != $displayProfile->userId) {
|
||||
throw new AccessDeniedException(__('You do not have permission to edit this profile'));
|
||||
}
|
||||
|
||||
$displayProfile->name = $parsedParams->getString('name');
|
||||
$displayProfile->isDefault = $parsedParams->getCheckbox('isDefault');
|
||||
|
||||
// Track changes to versionMediaId
|
||||
$originalPlayerVersionId = $displayProfile->getSetting('playerVersionId');
|
||||
|
||||
// Different fields for each client type
|
||||
$this->editConfigFields($displayProfile, $parsedParams);
|
||||
|
||||
// Capture and update commands
|
||||
foreach ($this->commandFactory->query() as $command) {
|
||||
if ($parsedParams->getString('commandString_' . $command->commandId) != null) {
|
||||
// Set and assign the command
|
||||
$command->commandString = $parsedParams->getString('commandString_' . $command->commandId);
|
||||
$command->validationString = $parsedParams->getString('validationString_' . $command->commandId);
|
||||
$command->createAlertOn = $parsedParams->getString('createAlertOn_' . $command->commandId);
|
||||
|
||||
$displayProfile->assignCommand($command);
|
||||
} else {
|
||||
$displayProfile->unassignCommand($command);
|
||||
}
|
||||
}
|
||||
|
||||
// If we are chromeOS and the default profile, has the player version changed?
|
||||
if ($displayProfile->type === 'chromeOS'
|
||||
&& ($displayProfile->isDefault || $displayProfile->hasPropertyChanged('isDefault'))
|
||||
&& ($originalPlayerVersionId !== $displayProfile->getSetting('playerVersionId'))
|
||||
) {
|
||||
$this->getLog()->debug('edit: updating symlink to the latest chromeOS version');
|
||||
|
||||
// Update a symlink to the new player version.
|
||||
try {
|
||||
$version = $this->playerVersionFactory->getById($displayProfile->getSetting('playerVersionId'));
|
||||
$version->setActive();
|
||||
} catch (NotFoundException) {
|
||||
$this->getLog()->error('edit: Player version does not exist');
|
||||
}
|
||||
}
|
||||
|
||||
// Save the changes
|
||||
$displayProfile->save();
|
||||
|
||||
// Clear the display cached
|
||||
$this->pool->deleteItem('display/');
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => sprintf(__('Edited %s'), $displayProfile->name),
|
||||
'id' => $displayProfile->displayProfileId,
|
||||
'data' => $displayProfile
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
function deleteForm(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)
|
||||
throw new AccessDeniedException(__('You do not have permission to edit this profile'));
|
||||
|
||||
$this->getState()->template = 'displayprofile-form-delete';
|
||||
$this->getState()->setData([
|
||||
'displayProfile' => $displayProfile,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Display Profile
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
* @SWG\Delete(
|
||||
* path="/displayprofile/{displayProfileId}",
|
||||
* operationId="displayProfileDelete",
|
||||
* tags={"displayprofile"},
|
||||
* summary="Delete Display Profile",
|
||||
* description="Delete an existing Display Profile",
|
||||
* @SWG\Parameter(
|
||||
* name="displayProfileId",
|
||||
* in="path",
|
||||
* description="The Display Profile ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
function delete(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) {
|
||||
throw new AccessDeniedException(__('You do not have permission to delete this profile'));
|
||||
}
|
||||
|
||||
$displayProfile->delete();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Deleted %s'), $displayProfile->name)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function copyForm(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)
|
||||
throw new AccessDeniedException(__('You do not have permission to delete this profile'));
|
||||
|
||||
$this->getState()->template = 'displayprofile-form-copy';
|
||||
$this->getState()->setData([
|
||||
'displayProfile' => $displayProfile
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy Display Profile
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
* @SWG\Post(
|
||||
* path="/displayprofile/{displayProfileId}/copy",
|
||||
* operationId="displayProfileCopy",
|
||||
* tags={"displayprofile"},
|
||||
* summary="Copy Display Profile",
|
||||
* description="Copy an existing Display Profile",
|
||||
* @SWG\Parameter(
|
||||
* name="displayProfileId",
|
||||
* in="path",
|
||||
* description="The Display Profile ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="path",
|
||||
* description="The name for the copy",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/DisplayProfile"),
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new record",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
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) {
|
||||
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
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => sprintf(__('Added %s'), $new->name),
|
||||
'id' => $new->displayProfileId,
|
||||
'data' => $new
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
1106
lib/Controller/DisplayProfileConfigFields.php
Normal file
1106
lib/Controller/DisplayProfileConfigFields.php
Normal file
File diff suppressed because it is too large
Load Diff
270
lib/Controller/Fault.php
Normal file
270
lib/Controller/Fault.php
Normal file
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\DisplayFactory;
|
||||
use Xibo\Factory\LogFactory;
|
||||
use Xibo\Helper\Environment;
|
||||
use Xibo\Helper\Random;
|
||||
use Xibo\Helper\SendFile;
|
||||
use Xibo\Storage\StorageServiceInterface;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class Fault
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class Fault extends Base
|
||||
{
|
||||
/** @var StorageServiceInterface */
|
||||
private $store;
|
||||
|
||||
/**
|
||||
* @var LogFactory
|
||||
*/
|
||||
private $logFactory;
|
||||
|
||||
/** @var DisplayFactory */
|
||||
private $displayFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param StorageServiceInterface $store
|
||||
* @param LogFactory $logFactory
|
||||
* @param DisplayFactory $displayFactory
|
||||
*/
|
||||
public function __construct($store, $logFactory, $displayFactory)
|
||||
{
|
||||
$this->store = $store;
|
||||
$this->logFactory = $logFactory;
|
||||
$this->displayFactory = $displayFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response)
|
||||
{
|
||||
$url = $request->getUri() . $request->getUri()->getPath();
|
||||
|
||||
$config = $this->getConfig();
|
||||
$data = [
|
||||
'environmentCheck' => $config->checkEnvironment(),
|
||||
'environmentFault' => $config->envFault,
|
||||
'environmentWarning' => $config->envWarning,
|
||||
'binLogError' => ($config->checkBinLogEnabled() && !$config->checkBinLogFormat()),
|
||||
'urlError' => !Environment::checkUrl($url)
|
||||
];
|
||||
|
||||
$this->getState()->template = 'fault-page';
|
||||
$this->getState()->setData($data);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function collect(Request $request, Response $response)
|
||||
{
|
||||
$this->setNoOutput(true);
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
// Create a ZIP file
|
||||
$tempFileName = $this->getConfig()->getSetting('LIBRARY_LOCATION') . 'temp/' . Random::generateString();
|
||||
$zip = new \ZipArchive();
|
||||
|
||||
$result = $zip->open($tempFileName, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);
|
||||
if ($result !== true) {
|
||||
throw new InvalidArgumentException(__('Can\'t create ZIP. Error Code: ' . $result));
|
||||
}
|
||||
|
||||
// Decide what we output based on the options selected.
|
||||
$outputVersion = $sanitizedParams->getCheckbox('outputVersion') == 1;
|
||||
$outputLog = $sanitizedParams->getCheckbox('outputLog') == 1;
|
||||
$outputEnvCheck = $sanitizedParams->getCheckbox('outputEnvCheck') == 1;
|
||||
$outputSettings = $sanitizedParams->getCheckbox('outputSettings') == 1;
|
||||
$outputDisplays = $sanitizedParams->getCheckbox('outputDisplays') == 1;
|
||||
$outputDisplayProfile = $sanitizedParams->getCheckbox('outputDisplayProfile') == 1;
|
||||
|
||||
if (!$outputVersion &&
|
||||
!$outputLog &&
|
||||
!$outputEnvCheck &&
|
||||
!$outputSettings &&
|
||||
!$outputDisplays &&
|
||||
!$outputDisplayProfile
|
||||
) {
|
||||
throw new InvalidArgumentException(__('Please select at least one option'));
|
||||
}
|
||||
|
||||
$environmentVariables = [
|
||||
'app_ver' => Environment::$WEBSITE_VERSION_NAME,
|
||||
'XmdsVersion' => Environment::$XMDS_VERSION,
|
||||
'XlfVersion' => Environment::$XLF_VERSION
|
||||
];
|
||||
|
||||
// Should we output the version?
|
||||
if ($outputVersion) {
|
||||
$zip->addFromString('version.json', json_encode($environmentVariables, JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
// Should we output a log?
|
||||
if ($outputLog) {
|
||||
$tempLogFile = $this->getConfig()->getSetting('LIBRARY_LOCATION') . 'temp/log_' . Random::generateString();
|
||||
$out = fopen($tempLogFile, 'w');
|
||||
fputcsv(
|
||||
$out,
|
||||
[
|
||||
'logId',
|
||||
'runNo',
|
||||
'logDate',
|
||||
'channel',
|
||||
'page',
|
||||
'function',
|
||||
'message',
|
||||
'display.display',
|
||||
'type',
|
||||
'sessionHistoryId'
|
||||
]
|
||||
);
|
||||
|
||||
$fromDt = Carbon::now()->subSeconds(60 * 10)->format('U');
|
||||
// Do some post processing
|
||||
foreach ($this->logFactory->query(['logId'], ['fromDt' => $fromDt]) as $row) {
|
||||
/* @var \Xibo\Entity\LogEntry $row */
|
||||
fputcsv(
|
||||
$out,
|
||||
[
|
||||
$row->logId,
|
||||
$row->runNo,
|
||||
$row->logDate,
|
||||
$row->channel,
|
||||
$row->page,
|
||||
$row->function,
|
||||
$row->message,
|
||||
$row->display,
|
||||
$row->type,
|
||||
$row->sessionHistoryId
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
fclose($out);
|
||||
|
||||
$zip->addFile($tempLogFile, 'log.csv');
|
||||
}
|
||||
|
||||
// Output ENV Check
|
||||
if ($outputEnvCheck) {
|
||||
$zip->addFromString('environment.json', json_encode(array_map(function ($element) {
|
||||
unset($element['advice']);
|
||||
return $element;
|
||||
}, $this->getConfig()->checkEnvironment()), JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
// Output Settings
|
||||
if ($outputSettings) {
|
||||
$zip->addFromString('settings.json', json_encode(array_map(function ($element) {
|
||||
return [$element['setting'] => $element['value']];
|
||||
}, $this->store->select('SELECT setting, `value` FROM `setting`', [])), JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
// Output Displays
|
||||
if ($outputDisplays) {
|
||||
$displays = $this->displayFactory->query(['display']);
|
||||
|
||||
// Output Profiles
|
||||
if ($outputDisplayProfile) {
|
||||
foreach ($displays as $display) {
|
||||
/** @var \Xibo\Entity\Display $display */
|
||||
$display->setUnmatchedProperty('settingProfile', array_map(function ($element) {
|
||||
unset($element['helpText']);
|
||||
return $element;
|
||||
}, $display->getSettings()));
|
||||
}
|
||||
}
|
||||
|
||||
$zip->addFromString('displays.json', json_encode($displays, JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
// Close the ZIP file
|
||||
$zip->close();
|
||||
|
||||
return $this->render($request, SendFile::decorateResponse(
|
||||
$response,
|
||||
$this->getConfig()->getSetting('SENDFILE_MODE'),
|
||||
$tempFileName,
|
||||
'troubleshoot.zip'
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function debugOn(Request $request, Response $response)
|
||||
{
|
||||
$this->getConfig()->changeSetting('audit', 'debug');
|
||||
$this->getConfig()->changeSetting('ELEVATE_LOG_UNTIL', Carbon::now()->addMinutes(30)->format('U'));
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => __('Switched to Debug Mode')
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function debugOff(Request $request, Response $response)
|
||||
{
|
||||
$this->getConfig()->changeSetting('audit', $this->getConfig()->getSetting('RESTING_LOG_LEVEL'));
|
||||
$this->getConfig()->changeSetting('ELEVATE_LOG_UNTIL', '');
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => __('Switched to Normal Mode')
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
564
lib/Controller/Folder.php
Normal file
564
lib/Controller/Folder.php
Normal file
@@ -0,0 +1,564 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\FolderFactory;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
class Folder extends Base
|
||||
{
|
||||
/**
|
||||
* @var FolderFactory
|
||||
*/
|
||||
private $folderFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param FolderFactory $folderFactory
|
||||
*/
|
||||
public function __construct(FolderFactory $folderFactory)
|
||||
{
|
||||
$this->folderFactory = $folderFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'folders-page';
|
||||
$this->getState()->setData([]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns JSON representation of the Folder tree
|
||||
*
|
||||
* @SWG\Get(
|
||||
* path="/folders",
|
||||
* operationId="folderSearch",
|
||||
* tags={"folder"},
|
||||
* summary="Search Folders",
|
||||
* description="Returns JSON representation of the Folder tree",
|
||||
* @SWG\Parameter(
|
||||
* name="folderId",
|
||||
* in="path",
|
||||
* description="Show usage details for the specified Folder Id",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="gridView",
|
||||
* in="query",
|
||||
* description="Flag (0, 1), Show Folders in a standard grid response",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="folderId",
|
||||
* in="query",
|
||||
* description="Use with gridView, Filter by Folder Id",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="folderName",
|
||||
* in="query",
|
||||
* description="Use with gridView, Filter by Folder name",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="exactFolderName",
|
||||
* in="query",
|
||||
* description="Use with gridView, Filter by exact Folder name match",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/Folder")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function grid(Request $request, Response $response, $folderId = null)
|
||||
{
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
if ($params->getInt('gridView') === 1) {
|
||||
$folders = $this->folderFactory->query($this->gridRenderSort($params), $this->gridRenderFilter([
|
||||
'folderName' => $params->getString('folderName'),
|
||||
'folderId' => $params->getInt('folderId'),
|
||||
'exactFolderName' => $params->getInt('exactFolderName'),
|
||||
], $params));
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->folderFactory->countLast();
|
||||
$this->getState()->setData($folders);
|
||||
|
||||
return $this->render($request, $response);
|
||||
} else if ($params->getString('folderName') !== null) {
|
||||
// Search all folders by name
|
||||
$folders = $this->folderFactory->query($this->gridRenderSort($params), $this->gridRenderFilter([
|
||||
'folderName' => $params->getString('folderName'),
|
||||
'exactFolderName' => $params->getInt('exactFolderName'),
|
||||
], $params));
|
||||
|
||||
return $response->withJson($folders);
|
||||
} else if ($folderId !== null) {
|
||||
// Should we return information for a specific folder?
|
||||
$folder = $this->folderFactory->getById($folderId);
|
||||
|
||||
$this->decorateWithButtons($folder);
|
||||
$this->folderFactory->decorateWithHomeFolderCount($folder);
|
||||
$this->folderFactory->decorateWithSharing($folder);
|
||||
$this->folderFactory->decorateWithUsage($folder);
|
||||
|
||||
return $response->withJson($folder);
|
||||
} else {
|
||||
// Show a tree view of all folders.
|
||||
$rootFolder = $this->folderFactory->getById(1);
|
||||
|
||||
// homeFolderId,
|
||||
// do we show tree for current user
|
||||
// or a specified user?
|
||||
$homeFolderId = ($params->getInt('homeFolderId') !== null)
|
||||
? $params->getInt('homeFolderId')
|
||||
: $this->getUser()->homeFolderId;
|
||||
|
||||
$this->buildTreeView($rootFolder, $homeFolderId);
|
||||
return $response->withJson([$rootFolder]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Xibo\Entity\Folder $folder
|
||||
* @param int $homeFolderId
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function buildTreeView(\Xibo\Entity\Folder $folder, int $homeFolderId)
|
||||
{
|
||||
// Set the folder type
|
||||
$folder->type = '';
|
||||
if ($folder->isRoot === 1) {
|
||||
$folder->type = 'root';
|
||||
}
|
||||
|
||||
if ($homeFolderId === $folder->id) {
|
||||
$folder->type = 'home';
|
||||
}
|
||||
|
||||
if (!empty($folder->children)) {
|
||||
$children = array_filter(explode(',', $folder->children));
|
||||
} else {
|
||||
$children = [];
|
||||
}
|
||||
$childrenDetails = [];
|
||||
|
||||
foreach ($children as $childId) {
|
||||
try {
|
||||
$child = $this->folderFactory->getById($childId);
|
||||
|
||||
if ($child->children != null) {
|
||||
$this->buildTreeView($child, $homeFolderId);
|
||||
}
|
||||
|
||||
if (!$this->getUser()->checkViewable($child)) {
|
||||
$child->text = __('Private Folder');
|
||||
$child->type = 'disabled';
|
||||
}
|
||||
|
||||
if ($homeFolderId === $child->id) {
|
||||
$child->type = 'home';
|
||||
}
|
||||
|
||||
$childrenDetails[] = $child;
|
||||
} catch (NotFoundException $exception) {
|
||||
// this should be fine, just log debug message about it.
|
||||
$this->getLog()->debug('User does not have permissions to Folder ID ' . $childId);
|
||||
}
|
||||
}
|
||||
|
||||
$folder->children = $childrenDetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new Folder
|
||||
*
|
||||
* @SWG\Post(
|
||||
* path="/folders",
|
||||
* operationId="folderAdd",
|
||||
* tags={"folder"},
|
||||
* summary="Add Folder",
|
||||
* description="Add a new Folder to the specified parent Folder",
|
||||
* @SWG\Parameter(
|
||||
* name="text",
|
||||
* in="formData",
|
||||
* description="Folder Name",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="parentId",
|
||||
* in="formData",
|
||||
* description="The ID of the parent Folder, if not provided, Folder will be added under Root Folder",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* @SWG\Items(ref="#/definitions/Folder")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function add(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$folder = $this->folderFactory->createEmpty();
|
||||
$folder->text = $sanitizedParams->getString('text');
|
||||
$folder->parentId = $sanitizedParams->getString('parentId', ['default' => 1]);
|
||||
|
||||
$folder->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => sprintf(__('Added %s'), $folder->text),
|
||||
'id' => $folder->id,
|
||||
'data' => $folder
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit existing Folder
|
||||
*
|
||||
* @SWG\Put(
|
||||
* path="/folders/{folderId}",
|
||||
* operationId="folderEdit",
|
||||
* tags={"folder"},
|
||||
* summary="Edit Folder",
|
||||
* description="Edit existing Folder",
|
||||
* @SWG\Parameter(
|
||||
* name="folderId",
|
||||
* in="path",
|
||||
* description="Folder ID to edit",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="text",
|
||||
* in="formData",
|
||||
* description="Folder Name",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* @SWG\Items(ref="#/definitions/Folder")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $folderId
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function edit(Request $request, Response $response, $folderId)
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$folder = $this->folderFactory->getById($folderId);
|
||||
|
||||
if ($folder->isRoot === 1) {
|
||||
throw new InvalidArgumentException(__('Cannot edit root Folder'), 'isRoot');
|
||||
}
|
||||
|
||||
if (!$this->getUser()->checkEditable($folder)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$folder->text = $sanitizedParams->getString('text');
|
||||
|
||||
$folder->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => sprintf(__('Edited %s'), $folder->text),
|
||||
'id' => $folder->id,
|
||||
'data' => $folder
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete existing Folder
|
||||
*
|
||||
* @SWG\Delete(
|
||||
* path="/folders/{folderId}",
|
||||
* operationId="folderDelete",
|
||||
* tags={"folder"},
|
||||
* summary="Delete Folder",
|
||||
* description="Delete existing Folder",
|
||||
* @SWG\Parameter(
|
||||
* name="folderId",
|
||||
* in="path",
|
||||
* description="Folder ID to edit",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* @SWG\Items(ref="#/definitions/Folder")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $folderId
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function delete(Request $request, Response $response, $folderId)
|
||||
{
|
||||
$folder = $this->folderFactory->getById($folderId);
|
||||
$folder->load();
|
||||
|
||||
if ($folder->isRoot === 1) {
|
||||
throw new InvalidArgumentException(__('Cannot remove root Folder'), 'isRoot');
|
||||
}
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($folder)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
if ($folder->isHome()) {
|
||||
throw new InvalidArgumentException(
|
||||
__('Cannot remove Folder set as home Folder for a user'),
|
||||
'folderId',
|
||||
__('Change home Folder for Users using this Folder before deleting')
|
||||
);
|
||||
}
|
||||
|
||||
if ($folder->id == $this->getConfig()->getSetting('DISPLAY_DEFAULT_FOLDER')) {
|
||||
throw new InvalidArgumentException(
|
||||
__('Cannot remove Folder set as default Folder for new Displays'),
|
||||
'folderId',
|
||||
__('Change Default Folder for new Displays before deleting')
|
||||
);
|
||||
}
|
||||
|
||||
// Check if the folder is in use
|
||||
$this->folderFactory->decorateWithUsage($folder);
|
||||
$usage = $folder->getUnmatchedProperty('usage');
|
||||
|
||||
// Prevent deletion if the folder has any usage
|
||||
if (!empty($usage)) {
|
||||
$usageDetails = [];
|
||||
|
||||
// Loop through usage data and construct the formatted message
|
||||
foreach ($usage as $item) {
|
||||
$usageDetails[] = $item['type'] . ' (' . $item['count'] . ')';
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(
|
||||
__('Cannot remove Folder with content: ' . implode(', ', $usageDetails)),
|
||||
'folderId',
|
||||
__('Reassign objects from this Folder before deleting.')
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$folder->delete();
|
||||
} catch (\Exception $exception) {
|
||||
$this->getLog()->debug('Folder delete failed with message: ' . $exception->getMessage());
|
||||
throw new InvalidArgumentException(
|
||||
__('Cannot remove Folder with content'),
|
||||
'folderId',
|
||||
__('Reassign objects from this Folder before deleting.')
|
||||
);
|
||||
}
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => sprintf(__('Deleted %s'), $folder->text)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $folderId
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function getContextMenuButtons(Request $request, Response $response, $folderId)
|
||||
{
|
||||
$folder = $this->folderFactory->getById($folderId);
|
||||
$this->decorateWithButtons($folder);
|
||||
|
||||
return $response->withJson($folder->buttons);
|
||||
}
|
||||
|
||||
private function decorateWithButtons(\Xibo\Entity\Folder $folder)
|
||||
{
|
||||
$user = $this->getUser();
|
||||
|
||||
if ($user->featureEnabled('folder.add')
|
||||
&& $user->checkViewable($folder)
|
||||
&& (!$folder->isRoot() || $user->isSuperAdmin())
|
||||
) {
|
||||
$folder->buttons['create'] = true;
|
||||
}
|
||||
|
||||
$featureModify = $user->featureEnabled('folder.modify');
|
||||
if ($featureModify
|
||||
&& $user->checkEditable($folder)
|
||||
&& !$folder->isRoot()
|
||||
&& ($this->getUser()->isSuperAdmin() || $folder->getId() !== $this->getUser()->homeFolderId)
|
||||
) {
|
||||
$folder->buttons['modify'] = true;
|
||||
}
|
||||
|
||||
if ($featureModify
|
||||
&& $user->checkDeleteable($folder)
|
||||
&& !$folder->isRoot()
|
||||
&& ($this->getUser()->isSuperAdmin() || $folder->getId() !== $this->getUser()->homeFolderId)
|
||||
) {
|
||||
$folder->buttons['delete'] = true;
|
||||
}
|
||||
|
||||
if ($user->isSuperAdmin() && !$folder->isRoot()) {
|
||||
$folder->buttons['share'] = true;
|
||||
}
|
||||
|
||||
if (!$folder->isRoot() && $user->checkViewable($folder) && $user->featureEnabled('folder.modify')) {
|
||||
$folder->buttons['move'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function moveForm(Request $request, Response $response, $folderId)
|
||||
{
|
||||
$folder = $this->folderFactory->getById($folderId, 0);
|
||||
|
||||
if (!$this->getUser()->checkEditable($folder)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'folder-form-move';
|
||||
$this->getState()->setData([
|
||||
'folder' => $folder,
|
||||
'deletable' => $this->getUser()->checkDeleteable($folder)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
public function move(Request $request, Response $response, $folderId)
|
||||
{
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
$folder = $this->folderFactory->getById($folderId);
|
||||
$newParentFolder = $this->folderFactory->getById($params->getInt('folderId'), 0);
|
||||
|
||||
if (!$this->getUser()->checkEditable($folder)
|
||||
|| $folder->isRoot()
|
||||
|| !$this->getUser()->checkViewable($newParentFolder)
|
||||
) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
if ($folder->id === $params->getInt('folderId')) {
|
||||
throw new InvalidArgumentException(
|
||||
__('Please select different folder, cannot move Folder to the same Folder')
|
||||
);
|
||||
}
|
||||
|
||||
if ($folder->isTheSameBranch($newParentFolder->getId())) {
|
||||
throw new InvalidArgumentException(
|
||||
__('Please select different folder, cannot move Folder inside of one of its sub-folders')
|
||||
);
|
||||
}
|
||||
|
||||
if ($folder->parentId === $newParentFolder->getId() && $params->getCheckbox('merge') !== 1) {
|
||||
throw new InvalidArgumentException(__('This Folder is already a sub-folder of the selected Folder, if you wish to move its content to the parent Folder, please check the merge checkbox.'));//phpcs:ignore
|
||||
}
|
||||
|
||||
// if we need to merge contents of the folder, dispatch an event that will move every object inside the folder
|
||||
// to the new folder, any sub-folders will be moved to the new parent folder keeping the tree structure.
|
||||
if ($params->getCheckbox('merge') === 1) {
|
||||
$event = new \Xibo\Event\FolderMovingEvent($folder, $newParentFolder, true);
|
||||
$this->getDispatcher()->dispatch($event, $event::$NAME);
|
||||
|
||||
// after moving event is done, we should be able to safely delete the original folder
|
||||
$folder = $this->folderFactory->getById($folderId, 0);
|
||||
$folder->load();
|
||||
$folder->delete();
|
||||
} else {
|
||||
// if we just want to move the Folder to new parent, we move folder and its sub-folders to the new parent
|
||||
// changing the permissionsFolderId as well if needed.
|
||||
$folder->updateFoldersAfterMove($folder->parentId, $newParentFolder->getId());
|
||||
}
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
593
lib/Controller/Font.php
Normal file
593
lib/Controller/Font.php
Normal file
@@ -0,0 +1,593 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use GuzzleHttp\Psr7\Stream;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Stash\Invalidation;
|
||||
use Xibo\Factory\FontFactory;
|
||||
use Xibo\Helper\ByteFormatter;
|
||||
use Xibo\Helper\HttpCacheProvider;
|
||||
use Xibo\Service\DownloadService;
|
||||
use Xibo\Service\MediaService;
|
||||
use Xibo\Service\MediaServiceInterface;
|
||||
use Xibo\Service\UploadService;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\ConfigurationException;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
class Font extends Base
|
||||
{
|
||||
/**
|
||||
* @var FontFactory
|
||||
*/
|
||||
private $fontFactory;
|
||||
/**
|
||||
* @var MediaServiceInterface
|
||||
*/
|
||||
private $mediaService;
|
||||
|
||||
public function __construct(FontFactory $fontFactory)
|
||||
{
|
||||
$this->fontFactory = $fontFactory;
|
||||
}
|
||||
|
||||
public function useMediaService(MediaServiceInterface $mediaService)
|
||||
{
|
||||
$this->mediaService = $mediaService;
|
||||
}
|
||||
|
||||
public function getMediaService(): MediaServiceInterface
|
||||
{
|
||||
return $this->mediaService->setUser($this->getUser());
|
||||
}
|
||||
|
||||
public function getFontFactory() : FontFactory
|
||||
{
|
||||
return $this->fontFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response)
|
||||
{
|
||||
if (!$this->getUser()->featureEnabled('font.view')) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'fonts-page';
|
||||
$this->getState()->setData([
|
||||
'validExt' => implode('|', $this->getValidExtensions())
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints out a Table of all Font items
|
||||
*
|
||||
* @SWG\Get(
|
||||
* path="/fonts",
|
||||
* operationId="fontSearch",
|
||||
* tags={"font"},
|
||||
* summary="Font Search",
|
||||
* description="Search the available Fonts",
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="query",
|
||||
* description="Filter by Font Id",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="query",
|
||||
* description="Filter by Font Name",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/Font")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function grid(Request $request, Response $response)
|
||||
{
|
||||
$parsedQueryParams = $this->getSanitizer($request->getQueryParams());
|
||||
|
||||
// Construct the SQL
|
||||
$fonts = $this->fontFactory->query($this->gridRenderSort($parsedQueryParams), $this->gridRenderFilter([
|
||||
'id' => $parsedQueryParams->getInt('id'),
|
||||
'name' => $parsedQueryParams->getString('name'),
|
||||
], $parsedQueryParams));
|
||||
|
||||
foreach ($fonts as $font) {
|
||||
$font->setUnmatchedProperty('fileSizeFormatted', ByteFormatter::format($font->size));
|
||||
$font->buttons = [];
|
||||
if ($this->isApi($request)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// download the font file
|
||||
$font->buttons[] = [
|
||||
'id' => 'content_button_download',
|
||||
'linkType' => '_self', 'external' => true,
|
||||
'url' => $this->urlFor($request, 'font.download', ['id' => $font->id]),
|
||||
'text' => __('Download')
|
||||
];
|
||||
|
||||
// font details from fontLib and preview text
|
||||
$font->buttons[] = [
|
||||
'id' => 'font_button_details',
|
||||
'url' => $this->urlFor($request, 'font.details', ['id' => $font->id]),
|
||||
'text' => __('Details')
|
||||
];
|
||||
|
||||
$font->buttons[] = ['divider' => true];
|
||||
|
||||
if ($this->getUser()->featureEnabled('font.delete')) {
|
||||
// Delete Button
|
||||
$font->buttons[] = [
|
||||
'id' => 'content_button_delete',
|
||||
'url' => $this->urlFor($request, 'font.form.delete', ['id' => $font->id]),
|
||||
'text' => __('Delete'),
|
||||
'multi-select' => true,
|
||||
'dataAttributes' => [
|
||||
[
|
||||
'name' => 'commit-url',
|
||||
'value' => $this->urlFor($request, 'font.delete', ['id' => $font->id])
|
||||
],
|
||||
['name' => 'commit-method', 'value' => 'delete'],
|
||||
['name' => 'id', 'value' => 'content_button_delete'],
|
||||
['name' => 'text', 'value' => __('Delete')],
|
||||
['name' => 'sort-group', 'value' => 1],
|
||||
['name' => 'rowtitle', 'value' => $font->name]
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->fontFactory->countLast();
|
||||
$this->getState()->setData($fonts);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Font details provided by FontLib
|
||||
*
|
||||
* @SWG\Get(
|
||||
* path="/fonts/details/{id}",
|
||||
* operationId="fontDetails",
|
||||
* tags={"font"},
|
||||
* summary="Font Details",
|
||||
* description="Get the Font details",
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Font ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* type="object",
|
||||
* additionalProperties={
|
||||
* "title"="details",
|
||||
* "type"="array"
|
||||
* }
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \FontLib\Exception\FontNotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function getFontLibDetails(Request $request, Response $response, $id)
|
||||
{
|
||||
$font = $this->fontFactory->getById($id);
|
||||
$fontLib = \FontLib\Font::load($font->getFilePath());
|
||||
$fontLib->parse();
|
||||
|
||||
$fontDetails = [
|
||||
'Name' => $fontLib->getFontName(),
|
||||
'SubFamily Name' => $fontLib->getFontSubfamily(),
|
||||
'Subfamily ID' => $fontLib->getFontSubfamilyID(),
|
||||
'Full Name' => $fontLib->getFontFullName(),
|
||||
'Version' => $fontLib->getFontVersion(),
|
||||
'Font Weight' => $fontLib->getFontWeight(),
|
||||
'Font Postscript Name' => $fontLib->getFontPostscriptName(),
|
||||
'Font Copyright' => $fontLib->getFontCopyright(),
|
||||
];
|
||||
|
||||
$this->getState()->template = 'fonts-fontlib-details';
|
||||
$this->getState()->setData([
|
||||
'details' => $fontDetails,
|
||||
'fontId' => $font->id
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/fonts/download/{id}",
|
||||
* operationId="fontDownload",
|
||||
* tags={"font"},
|
||||
* summary="Download Font",
|
||||
* description="Download a Font file from the Library",
|
||||
* produces={"application/octet-stream"},
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Font ID to Download",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(type="file"),
|
||||
* @SWG\Header(
|
||||
* header="X-Sendfile",
|
||||
* description="Apache Send file header - if enabled.",
|
||||
* type="string"
|
||||
* ),
|
||||
* @SWG\Header(
|
||||
* header="X-Accel-Redirect",
|
||||
* description="nginx send file header - if enabled.",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function download(Request $request, Response $response, $id)
|
||||
{
|
||||
if (is_numeric($id)) {
|
||||
$font = $this->fontFactory->getById($id);
|
||||
} else {
|
||||
$font = $this->fontFactory->getByName($id)[0];
|
||||
}
|
||||
|
||||
$this->getLog()->debug('Download request for fontId ' . $id);
|
||||
|
||||
$library = $this->getConfig()->getSetting('LIBRARY_LOCATION');
|
||||
$sendFileMode = $this->getConfig()->getSetting('SENDFILE_MODE');
|
||||
$attachmentName = urlencode($font->fileName);
|
||||
$libraryPath = $library . 'fonts' . DIRECTORY_SEPARATOR . $font->fileName;
|
||||
|
||||
$downLoadService = new DownloadService($libraryPath, $sendFileMode);
|
||||
$downLoadService->useLogger($this->getLog()->getLoggerInterface());
|
||||
|
||||
return $downLoadService->returnFile($response, $attachmentName, '/download/fonts/' . $font->fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getValidExtensions()
|
||||
{
|
||||
return ['otf', 'ttf', 'eot', 'svg', 'woff'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Font Upload
|
||||
*
|
||||
* @SWG\Post(
|
||||
* path="/fonts",
|
||||
* operationId="fontUpload",
|
||||
* tags={"font"},
|
||||
* summary="Font Upload",
|
||||
* description="Upload a new Font file",
|
||||
* @SWG\Parameter(
|
||||
* name="files",
|
||||
* in="formData",
|
||||
* description="The Uploaded File",
|
||||
* type="file",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="formData",
|
||||
* description="Optional Font Name",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws ConfigurationException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\DuplicateEntityException
|
||||
*/
|
||||
public function add(Request $request, Response $response)
|
||||
{
|
||||
if (!$this->getUser()->featureEnabled('font.add')) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$libraryFolder = $this->getConfig()->getSetting('LIBRARY_LOCATION');
|
||||
|
||||
// Make sure the library exists
|
||||
MediaService::ensureLibraryExists($libraryFolder);
|
||||
$validExt = $this->getValidExtensions();
|
||||
|
||||
// Make sure there is room in the library
|
||||
$libraryLimit = $this->getConfig()->getSetting('LIBRARY_SIZE_LIMIT_KB') * 1024;
|
||||
|
||||
$options = [
|
||||
'accept_file_types' => '/\.' . implode('|', $validExt) . '$/i',
|
||||
'libraryLimit' => $libraryLimit,
|
||||
'libraryQuotaFull' => ($libraryLimit > 0 && $this->getMediaService()->libraryUsage() > $libraryLimit),
|
||||
];
|
||||
|
||||
// Output handled by UploadHandler
|
||||
$this->setNoOutput(true);
|
||||
|
||||
$this->getLog()->debug('Hand off to Upload Handler with options: ' . json_encode($options));
|
||||
|
||||
// Hand off to the Upload Handler provided by jquery-file-upload
|
||||
$uploadService = new UploadService($libraryFolder . 'temp/', $options, $this->getLog(), $this->getState());
|
||||
$uploadHandler = $uploadService->createUploadHandler();
|
||||
|
||||
$uploadHandler->setPostProcessor(function ($file, $uploadHandler) use ($libraryFolder) {
|
||||
// Return right away if the file already has an error.
|
||||
if (!empty($file->error)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
$this->getUser()->isQuotaFullByUser(true);
|
||||
|
||||
// Get the uploaded file and move it to the right place
|
||||
$filePath = $libraryFolder . 'temp/' . $file->fileName;
|
||||
|
||||
// Add the Font
|
||||
$font = $this->getFontFactory()
|
||||
->createFontFromUpload($filePath, $file->name, $file->fileName, $this->getUser()->userName);
|
||||
$font->save();
|
||||
|
||||
// Test to ensure the final file size is the same as the file size we're expecting
|
||||
if ($file->size != $font->size) {
|
||||
throw new InvalidArgumentException(
|
||||
__('Sorry this is a corrupted upload, the file size doesn\'t match what we\'re expecting.'),
|
||||
'size'
|
||||
);
|
||||
}
|
||||
|
||||
// everything is fine, move the file from temp folder.
|
||||
rename($filePath, $libraryFolder . 'fonts/' . $font->fileName);
|
||||
|
||||
// return
|
||||
$file->id = $font->id;
|
||||
$file->md5 = $font->md5;
|
||||
$file->name = $font->name;
|
||||
|
||||
return $file;
|
||||
});
|
||||
|
||||
// Handle the post request
|
||||
$uploadHandler->post();
|
||||
|
||||
// all done, refresh fonts.css
|
||||
$this->getMediaService()->updateFontsCss();
|
||||
|
||||
// Explicitly set the Content-Type header to application/json
|
||||
$response = $response->withHeader('Content-Type', 'application/json');
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Font Delete Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function deleteForm(Request $request, Response $response, $id)
|
||||
{
|
||||
if (!$this->getUser()->featureEnabled('font.delete')) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
if (is_numeric($id)) {
|
||||
$font = $this->fontFactory->getById($id);
|
||||
} else {
|
||||
$font = $this->fontFactory->getByName($id)[0];
|
||||
}
|
||||
|
||||
$this->getState()->template = 'font-form-delete';
|
||||
$this->getState()->setData([
|
||||
'font' => $font
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Font Delete
|
||||
*
|
||||
* @SWG\Delete(
|
||||
* path="/fonts/{id}/delete",
|
||||
* operationId="fontDelete",
|
||||
* tags={"font"},
|
||||
* summary="Font Delete",
|
||||
* description="Delete existing Font file",
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Font ID to delete",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws ConfigurationException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\DuplicateEntityException
|
||||
*/
|
||||
public function delete(Request $request, Response $response, $id)
|
||||
{
|
||||
if (!$this->getUser()->featureEnabled('font.delete')) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
if (is_numeric($id)) {
|
||||
$font = $this->fontFactory->getById($id);
|
||||
} else {
|
||||
$font = $this->fontFactory->getByName($id)[0];
|
||||
}
|
||||
|
||||
// delete record and file
|
||||
$font->delete();
|
||||
|
||||
// refresh fonts.css
|
||||
$this->getMediaService()->updateFontsCss();
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the CMS flavored font css
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
* @throws ConfigurationException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\DuplicateEntityException
|
||||
*/
|
||||
public function fontCss(Request $request, Response $response)
|
||||
{
|
||||
$tempFileName = $this->getConfig()->getSetting('LIBRARY_LOCATION') . 'fonts/local_fontcss';
|
||||
|
||||
$cacheItem = $this->getMediaService()->getPool()->getItem('localFontCss');
|
||||
$cacheItem->setInvalidationMethod(Invalidation::SLEEP, 5000, 15);
|
||||
|
||||
if ($cacheItem->isMiss()) {
|
||||
$this->getLog()->debug('local font css cache has expired, regenerating');
|
||||
|
||||
$cacheItem->lock(60);
|
||||
$localCss = '';
|
||||
// Regenerate the CSS for fonts
|
||||
foreach ($this->fontFactory->query() as $font) {
|
||||
// Go through all installed fonts each time and regenerate.
|
||||
$fontTemplate = '@font-face {
|
||||
font-family: \'[family]\';
|
||||
src: url(\'[url]\');
|
||||
}';
|
||||
// Css for the local CMS contains the full download path to the font
|
||||
$url = $this->urlFor($request, 'font.download', ['id' => $font->id]);
|
||||
$localCss .= str_replace('[url]', $url, str_replace('[family]', $font->familyName, $fontTemplate));
|
||||
}
|
||||
|
||||
// cache
|
||||
$cacheItem->set($localCss);
|
||||
$cacheItem->expiresAfter(new \DateInterval('P30D'));
|
||||
$this->getMediaService()->getPool()->saveDeferred($cacheItem);
|
||||
} else {
|
||||
$this->getLog()->debug('local font css file served from cache ');
|
||||
$localCss = $cacheItem->get();
|
||||
}
|
||||
|
||||
// Return the CSS to the browser as a file
|
||||
$out = fopen($tempFileName, 'w');
|
||||
if (!$out) {
|
||||
throw new ConfigurationException(__('Unable to write to the library'));
|
||||
}
|
||||
fputs($out, $localCss);
|
||||
fclose($out);
|
||||
|
||||
// Work out the etag
|
||||
$response = HttpCacheProvider::withEtag($response, md5($localCss));
|
||||
|
||||
$this->setNoOutput(true);
|
||||
|
||||
$response = $response->withHeader('Content-Type', 'text/css')
|
||||
->withBody(new Stream(fopen($tempFileName, 'r')));
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
52
lib/Controller/IconDashboard.php
Normal file
52
lib/Controller/IconDashboard.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2021 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Slim\Views\Twig;
|
||||
use Xibo\Helper\SanitizerService;
|
||||
use Xibo\Service\ConfigServiceInterface;
|
||||
use Xibo\Service\LogServiceInterface;
|
||||
|
||||
/**
|
||||
* Class IconDashboard
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class IconDashboard extends Base
|
||||
{
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'dashboard-icon-page';
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
3540
lib/Controller/Layout.php
Normal file
3540
lib/Controller/Layout.php
Normal file
File diff suppressed because it is too large
Load Diff
3007
lib/Controller/Library.php
Normal file
3007
lib/Controller/Library.php
Normal file
File diff suppressed because it is too large
Load Diff
166
lib/Controller/Logging.php
Normal file
166
lib/Controller/Logging.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2023 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\LogFactory;
|
||||
use Xibo\Factory\UserFactory;
|
||||
use Xibo\Helper\DateFormatHelper;
|
||||
use Xibo\Storage\StorageServiceInterface;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
|
||||
/**
|
||||
* Class Logging
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class Logging extends Base
|
||||
{
|
||||
/**
|
||||
* @var LogFactory
|
||||
*/
|
||||
private $logFactory;
|
||||
|
||||
/** @var StorageServiceInterface */
|
||||
private $store;
|
||||
|
||||
/** @var UserFactory */
|
||||
private $userFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param StorageServiceInterface $store
|
||||
* @param LogFactory $logFactory
|
||||
* @param UserFactory $userFactory
|
||||
*/
|
||||
public function __construct($store, $logFactory, $userFactory)
|
||||
{
|
||||
$this->store = $store;
|
||||
$this->logFactory = $logFactory;
|
||||
$this->userFactory = $userFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'log-page';
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
function grid(Request $request, Response $response)
|
||||
{
|
||||
$parsedQueryParams = $this->getSanitizer($request->getQueryParams());
|
||||
|
||||
// Date time criteria
|
||||
$seconds = $parsedQueryParams->getInt('seconds', ['default' => 120]);
|
||||
$intervalType = $parsedQueryParams->getInt('intervalType', ['default' => 1]);
|
||||
$fromDt = $parsedQueryParams->getDate('fromDt', ['default' => Carbon::now()]);
|
||||
|
||||
$logs = $this->logFactory->query($this->gridRenderSort($parsedQueryParams), $this->gridRenderFilter([
|
||||
'fromDt' => $fromDt->clone()->subSeconds($seconds * $intervalType)->format('U'),
|
||||
'toDt' => $fromDt->format('U'),
|
||||
'type' => $parsedQueryParams->getString('level'),
|
||||
'page' => $parsedQueryParams->getString('page'),
|
||||
'channel' => $parsedQueryParams->getString('channel'),
|
||||
'function' => $parsedQueryParams->getString('function'),
|
||||
'displayId' => $parsedQueryParams->getInt('displayId'),
|
||||
'userId' => $parsedQueryParams->getInt('userId'),
|
||||
'excludeLog' => $parsedQueryParams->getCheckbox('excludeLog'),
|
||||
'runNo' => $parsedQueryParams->getString('runNo'),
|
||||
'message' => $parsedQueryParams->getString('message'),
|
||||
'display' => $parsedQueryParams->getString('display'),
|
||||
'useRegexForName' => $parsedQueryParams->getCheckbox('useRegexForName'),
|
||||
'displayGroupId' => $parsedQueryParams->getInt('displayGroupId'),
|
||||
], $parsedQueryParams));
|
||||
|
||||
foreach ($logs as $log) {
|
||||
// Normalise the date
|
||||
$log->logDate = Carbon::createFromTimeString($log->logDate)->format(DateFormatHelper::getSystemFormat());
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->logFactory->countLast();
|
||||
$this->getState()->setData($logs);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate Log Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function truncateForm(Request $request, Response $response)
|
||||
{
|
||||
if ($this->getUser()->userTypeId != 1) {
|
||||
throw new AccessDeniedException(__('Only Administrator Users can truncate the log'));
|
||||
}
|
||||
|
||||
$this->getState()->template = 'log-form-truncate';
|
||||
$this->getState()->autoSubmit = $this->getAutoSubmit('truncateForm');
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate the Log
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function truncate(Request $request, Response $response)
|
||||
{
|
||||
if ($this->getUser()->userTypeId != 1) {
|
||||
throw new AccessDeniedException(__('Only Administrator Users can truncate the log'));
|
||||
}
|
||||
|
||||
$this->store->update('TRUNCATE TABLE log', array());
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => __('Log Truncated')
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
707
lib/Controller/Login.php
Normal file
707
lib/Controller/Login.php
Normal file
@@ -0,0 +1,707 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2025 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use RobThree\Auth\TwoFactorAuth;
|
||||
use Slim\Flash\Messages;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Slim\Routing\RouteContext;
|
||||
use Xibo\Entity\User;
|
||||
use Xibo\Factory\UserFactory;
|
||||
use Xibo\Helper\Environment;
|
||||
use Xibo\Helper\HttpsDetect;
|
||||
use Xibo\Helper\LogoutTrait;
|
||||
use Xibo\Helper\Random;
|
||||
use Xibo\Helper\Session;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\ConfigurationException;
|
||||
use Xibo\Support\Exception\ExpiredException;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class Login
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class Login extends Base
|
||||
{
|
||||
use LogoutTrait;
|
||||
|
||||
/** @var Session */
|
||||
private $session;
|
||||
|
||||
/** @var UserFactory */
|
||||
private $userFactory;
|
||||
|
||||
/** @var \Stash\Interfaces\PoolInterface */
|
||||
private $pool;
|
||||
/**
|
||||
* @var Messages
|
||||
*/
|
||||
private $flash;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param Session $session
|
||||
* @param UserFactory $userFactory
|
||||
* @param \Stash\Interfaces\PoolInterface $pool
|
||||
*/
|
||||
public function __construct($session, $userFactory, $pool)
|
||||
{
|
||||
$this->session = $session;
|
||||
$this->userFactory = $userFactory;
|
||||
$this->pool = $pool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Flash Message
|
||||
*
|
||||
* @return Messages
|
||||
*/
|
||||
protected function getFlash()
|
||||
{
|
||||
return $this->flash;
|
||||
}
|
||||
|
||||
public function setFlash(Messages $messages)
|
||||
{
|
||||
$this->flash = $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output a login form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function loginForm(Request $request, Response $response)
|
||||
{
|
||||
// Sanitize the body
|
||||
$sanitizedRequestBody = $this->getSanitizer($request->getParams());
|
||||
|
||||
// Check to see if the user has provided a special token
|
||||
$nonce = $sanitizedRequestBody->getString('nonce');
|
||||
|
||||
if ($nonce != '') {
|
||||
// We have a nonce provided, so validate that in preference to showing the form.
|
||||
$nonce = explode('::', $nonce);
|
||||
$this->getLog()->debug('Nonce is ' . var_export($nonce, true));
|
||||
|
||||
$cache = $this->pool->getItem('/nonce/' . $nonce[0]);
|
||||
|
||||
$validated = $cache->get();
|
||||
|
||||
if ($cache->isMiss()) {
|
||||
$this->getLog()->error('Expired nonce used.');
|
||||
$this->getFlash()->addMessageNow('login_message', __('This link has expired.'));
|
||||
} else if (!password_verify($nonce[1], $validated['hash'])) {
|
||||
$this->getLog()->error('Invalid nonce used.');
|
||||
$this->getFlash()->addMessageNow('login_message', __('This link has expired.'));
|
||||
} else {
|
||||
// We're valid.
|
||||
$this->pool->deleteItem('/nonce/' . $nonce[0]);
|
||||
|
||||
try {
|
||||
$user = $this->userFactory->getById($validated['userId']);
|
||||
|
||||
// Log in this user
|
||||
$user->touch(true);
|
||||
|
||||
$this->getLog()->info($user->userName . ' user logged in via token.');
|
||||
|
||||
// Set the userId on the log object
|
||||
$this->getLog()->setUserId($user->userId);
|
||||
$this->getLog()->setIpAddress($request->getAttribute('ip_address'));
|
||||
|
||||
// Expire all sessions
|
||||
$session = $this->session;
|
||||
|
||||
// this is a security measure in case the user is logged in somewhere else.
|
||||
// (not this one though, otherwise we will deadlock
|
||||
$session->expireAllSessionsForUser($user->userId);
|
||||
|
||||
// Switch Session ID's
|
||||
$session->setIsExpired(0);
|
||||
$session->regenerateSessionId();
|
||||
$session->setUser($user->userId);
|
||||
$this->getLog()->setSessionHistoryId($session->get('sessionHistoryId'));
|
||||
|
||||
// Audit Log
|
||||
$this->getLog()->audit('User', $user->userId, 'Login Granted via token', [
|
||||
'UserAgent' => $request->getHeader('User-Agent')
|
||||
]);
|
||||
|
||||
return $response->withRedirect($this->urlFor($request, 'home'));
|
||||
} catch (NotFoundException $notFoundException) {
|
||||
$this->getLog()->error('Valid nonce for non-existing user');
|
||||
$this->getFlash()->addMessageNow('login_message', __('This link has expired.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if the password reminder functionality is enabled.
|
||||
$passwordReminderEnabled = $this->getConfig()->getSetting('PASSWORD_REMINDER_ENABLED');
|
||||
$mailFrom = $this->getConfig()->getSetting('mail_from');
|
||||
$authCASEnabled = isset($this->getConfig()->casSettings);
|
||||
|
||||
// Template
|
||||
$this->getState()->template = 'login';
|
||||
$this->getState()->setData([
|
||||
'passwordReminderEnabled' => (($passwordReminderEnabled === 'On' || $passwordReminderEnabled === 'On except Admin') && $mailFrom != ''),
|
||||
'authCASEnabled' => $authCASEnabled,
|
||||
'version' => Environment::$WEBSITE_VERSION_NAME
|
||||
]);
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Login
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Slim\Http\Response
|
||||
* @throws \Xibo\Support\Exception\DuplicateEntityException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function login(Request $request, Response $response): Response
|
||||
{
|
||||
$parsedRequest = $this->getSanitizer($request->getParsedBody());
|
||||
$routeParser = RouteContext::fromRequest($request)->getRouteParser();
|
||||
|
||||
// Capture the prior route (if there is one)
|
||||
$redirect = $this->urlFor($request, 'login');
|
||||
$priorRoute = $parsedRequest->getString('priorRoute');
|
||||
|
||||
try {
|
||||
// Get our username and password
|
||||
$username = $parsedRequest->getString('username');
|
||||
$password = $parsedRequest->getString('password');
|
||||
|
||||
$this->getLog()->debug('Login with username ' . $username);
|
||||
|
||||
// Get our user
|
||||
try {
|
||||
$user = $this->userFactory->getByName($username);
|
||||
|
||||
// Retired user
|
||||
if ($user->retired === 1) {
|
||||
throw new AccessDeniedException(
|
||||
__('Sorry this account does not exist or does not have permission to access the web portal.')
|
||||
);
|
||||
}
|
||||
|
||||
// Check password
|
||||
$user->checkPassword($password);
|
||||
|
||||
// check if 2FA is enabled
|
||||
if ($user->twoFactorTypeId != 0) {
|
||||
$_SESSION['tfaUsername'] = $user->userName;
|
||||
$this->getFlash()->addMessage('priorRoute', $priorRoute);
|
||||
return $response->withRedirect($routeParser->urlFor('tfa'));
|
||||
}
|
||||
|
||||
// We are logged in, so complete the login flow
|
||||
$this->completeLoginFlow($user, $request);
|
||||
} catch (NotFoundException) {
|
||||
throw new AccessDeniedException(__('User not found'));
|
||||
}
|
||||
|
||||
$redirect = $this->getRedirect($request, $priorRoute);
|
||||
} catch (AccessDeniedException $e) {
|
||||
$this->getLog()->warning($e->getMessage());
|
||||
$this->getFlash()->addMessage('login_message', __('Username or Password incorrect'));
|
||||
$this->getFlash()->addMessage('priorRoute', $priorRoute);
|
||||
} catch (ExpiredException $e) {
|
||||
$this->getFlash()->addMessage('priorRoute', $priorRoute);
|
||||
}
|
||||
$this->setNoOutput(true);
|
||||
$this->getLog()->debug('Redirect to ' . $redirect);
|
||||
return $response->withRedirect($redirect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forgotten password link requested
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws ConfigurationException
|
||||
* @throws \PHPMailer\PHPMailer\Exception
|
||||
* @throws \Twig\Error\LoaderError
|
||||
* @throws \Twig\Error\RuntimeError
|
||||
* @throws \Twig\Error\SyntaxError
|
||||
*/
|
||||
public function forgottenPassword(Request $request, Response $response)
|
||||
{
|
||||
// Is this functionality enabled?
|
||||
$passwordReminderEnabled = $this->getConfig()->getSetting('PASSWORD_REMINDER_ENABLED');
|
||||
$mailFrom = $this->getConfig()->getSetting('mail_from');
|
||||
|
||||
$parsedRequest = $this->getSanitizer($request->getParsedBody());
|
||||
$routeParser = RouteContext::fromRequest($request)->getRouteParser();
|
||||
|
||||
if (!(($passwordReminderEnabled === 'On' || $passwordReminderEnabled === 'On except Admin') && $mailFrom != '')) {
|
||||
throw new ConfigurationException(__('This feature has been disabled by your administrator'));
|
||||
}
|
||||
|
||||
// Get our username
|
||||
$username = $parsedRequest->getString('username');
|
||||
|
||||
// Log
|
||||
$this->getLog()->info('Forgotten Password Request for ' . $username);
|
||||
|
||||
// Check to see if the provided username is valid, and if so, record a nonce and send them a link
|
||||
try {
|
||||
// Get our user
|
||||
/* @var User $user */
|
||||
$user = $this->userFactory->getByName($username);
|
||||
|
||||
// Does this user have an email address associated to their user record?
|
||||
if ($user->email == '') {
|
||||
throw new NotFoundException(__('No email'));
|
||||
}
|
||||
|
||||
// Nonce parts (nonce isn't ever stored, only the hash of it is stored, it only exists in the email)
|
||||
$action = 'user-reset-password-' . Random::generateString(10);
|
||||
$nonce = Random::generateString(20);
|
||||
|
||||
// Create a nonce for this user and store it somewhere
|
||||
$cache = $this->pool->getItem('/nonce/' . $action);
|
||||
|
||||
$cache->set([
|
||||
'action' => $action,
|
||||
'hash' => password_hash($nonce, PASSWORD_DEFAULT),
|
||||
'userId' => $user->userId
|
||||
]);
|
||||
$cache->expiresAfter(1800); // 30 minutes?
|
||||
|
||||
// Save cache
|
||||
$this->pool->save($cache);
|
||||
|
||||
// Make a link
|
||||
$link = ((new HttpsDetect())->getRootUrl()) . $routeParser->urlFor('login') . '?nonce=' . $action . '::' . $nonce;
|
||||
|
||||
// Uncomment this to get a debug message showing the link.
|
||||
//$this->getLog()->debug('Link is:' . $link);
|
||||
|
||||
// Send the mail
|
||||
$mail = new \PHPMailer\PHPMailer\PHPMailer();
|
||||
$mail->CharSet = 'UTF-8';
|
||||
$mail->Encoding = 'base64';
|
||||
$mail->From = $mailFrom;
|
||||
$msgFromName = $this->getConfig()->getSetting('mail_from_name');
|
||||
|
||||
if ($msgFromName != null) {
|
||||
$mail->FromName = $msgFromName;
|
||||
}
|
||||
|
||||
$mail->Subject = __('Password Reset');
|
||||
$mail->addAddress($user->email);
|
||||
|
||||
// Body
|
||||
$mail->isHTML(true);
|
||||
|
||||
// We need to specify the style for the pw reset button since mailers usually ignore bootstrap classes
|
||||
$linkButton = '<a href="' . $link . '"
|
||||
style="
|
||||
display: inline-block;
|
||||
padding: 8px 15px;
|
||||
font-size: 15px;
|
||||
color: #FFFFFF;
|
||||
background-color: #428BCA;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
">
|
||||
' . __('Reset Password') . '
|
||||
</a>';
|
||||
|
||||
$mail->Body = $this->generateEmailBody(
|
||||
$mail->Subject,
|
||||
'<p>' . __('You are receiving this email because a password reminder was requested for your account.
|
||||
If you did not make this request, please report this email to your administrator immediately.') . '</p>'
|
||||
. $linkButton
|
||||
. '<p style="margin-top:10px; font-size:14px; color:#555555;">'
|
||||
. __('If the button does not work, copy and paste the following URL into your browser:')
|
||||
. '<br><a href="' . $link . '">' . $link . '</a></p>'
|
||||
);
|
||||
|
||||
if (!$mail->send()) {
|
||||
throw new ConfigurationException('Unable to send password reminder to ' . $user->email);
|
||||
} else {
|
||||
$this->getFlash()->addMessage(
|
||||
'login_message',
|
||||
__('A reminder email will been sent to this user if they exist'),
|
||||
);
|
||||
}
|
||||
|
||||
// Audit Log
|
||||
$this->getLog()->audit('User', $user->userId, 'Password Reset Link Granted', [
|
||||
'UserAgent' => $request->getHeader('User-Agent')
|
||||
]);
|
||||
} catch (GeneralException) {
|
||||
$this->getFlash()->addMessage(
|
||||
'login_message',
|
||||
__('A reminder email will been sent to this user if they exist'),
|
||||
);
|
||||
}
|
||||
|
||||
$this->setNoOutput(true);
|
||||
return $response->withRedirect($routeParser->urlFor('login'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Log out
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
*/
|
||||
public function logout(Request $request, Response $response)
|
||||
{
|
||||
$redirect = true;
|
||||
|
||||
if ($request->getQueryParam('redirect') != null) {
|
||||
$redirect = $request->getQueryParam('redirect');
|
||||
}
|
||||
|
||||
$this->completeLogoutFlow($this->getUser(), $this->session, $this->getLog(), $request);
|
||||
|
||||
if ($redirect) {
|
||||
return $response->withRedirect($this->urlFor($request, 'home'));
|
||||
}
|
||||
|
||||
return $response->withStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ping Pong
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function PingPong(Request $request, Response $response)
|
||||
{
|
||||
$parseRequest = $this->getSanitizer($request->getQueryParams());
|
||||
$this->session->refreshExpiry = ($parseRequest->getCheckbox('refreshSession') == 1);
|
||||
$this->getState()->success = true;
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows information about Xibo
|
||||
*
|
||||
* @SWG\Get(
|
||||
* path="/about",
|
||||
* operationId="about",
|
||||
* tags={"misc"},
|
||||
* summary="About",
|
||||
* description="Information about this API, such as Version code, etc",
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful response",
|
||||
* @SWG\Schema(
|
||||
* type="object",
|
||||
* additionalProperties={
|
||||
* "title"="version",
|
||||
* "type"="string"
|
||||
* }
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function about(Request $request, Response $response)
|
||||
{
|
||||
$state = $this->getState();
|
||||
|
||||
if ($request->isXhr()) {
|
||||
$state->template = 'about-text';
|
||||
} else {
|
||||
$state->template = 'about-page';
|
||||
}
|
||||
|
||||
$state->setData(['version' => Environment::$WEBSITE_VERSION_NAME, 'sourceUrl' => $this->getConfig()->getThemeConfig('cms_source_url')]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an email body
|
||||
* @param $subject
|
||||
* @param $body
|
||||
* @return string
|
||||
* @throws \Twig\Error\LoaderError
|
||||
* @throws \Twig\Error\RuntimeError
|
||||
* @throws \Twig\Error\SyntaxError
|
||||
*/
|
||||
private function generateEmailBody($subject, $body)
|
||||
{
|
||||
return $this->renderTemplateToString('email-template', [
|
||||
'config' => $this->getConfig(),
|
||||
'subject' => $subject, 'body' => $body
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 2FA Auth required
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws \PHPMailer\PHPMailer\Exception
|
||||
* @throws \RobThree\Auth\TwoFactorAuthException
|
||||
* @throws \Twig\Error\LoaderError
|
||||
* @throws \Twig\Error\RuntimeError
|
||||
* @throws \Twig\Error\SyntaxError
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function twoFactorAuthForm(Request $request, Response $response)
|
||||
{
|
||||
if (!isset($_SESSION['tfaUsername'])) {
|
||||
$this->getFlash()->addMessage('login_message', __('Session has expired, please log in again'));
|
||||
return $response->withRedirect($this->urlFor($request, 'login'));
|
||||
}
|
||||
|
||||
$user = $this->userFactory->getByName($_SESSION['tfaUsername']);
|
||||
$message = '';
|
||||
|
||||
// if our user has email two factor enabled, we need to send the email with code now
|
||||
if ($user->twoFactorTypeId === 1) {
|
||||
|
||||
if ($user->email == '') {
|
||||
throw new NotFoundException(__('No email'));
|
||||
}
|
||||
|
||||
$mailFrom = $this->getConfig()->getSetting('mail_from');
|
||||
$issuerSettings = $this->getConfig()->getSetting('TWOFACTOR_ISSUER');
|
||||
$appName = $this->getConfig()->getThemeConfig('app_name');
|
||||
|
||||
if ($issuerSettings !== '') {
|
||||
$issuer = $issuerSettings;
|
||||
} else {
|
||||
$issuer = $appName;
|
||||
}
|
||||
|
||||
if ($mailFrom == '') {
|
||||
throw new InvalidArgumentException(__('Sending email address in CMS Settings is not configured'), 'mail_from');
|
||||
}
|
||||
|
||||
$tfa = new TwoFactorAuth($issuer);
|
||||
|
||||
// Nonce parts (nonce isn't ever stored, only the hash of it is stored, it only exists in the email)
|
||||
$action = 'user-tfa-email-auth' . Random::generateString(10);
|
||||
$nonce = Random::generateString(20);
|
||||
|
||||
// Create a nonce for this user and store it somewhere
|
||||
$cache = $this->pool->getItem('/nonce/' . $action);
|
||||
|
||||
$cache->set([
|
||||
'action' => $action,
|
||||
'hash' => password_hash($nonce, PASSWORD_DEFAULT),
|
||||
'userId' => $user->userId
|
||||
]);
|
||||
$cache->expiresAfter(1800); // 30 minutes?
|
||||
|
||||
// Save cache
|
||||
$this->pool->save($cache);
|
||||
|
||||
// Make a link
|
||||
$code = $tfa->getCode($user->twoFactorSecret);
|
||||
|
||||
// Send the mail
|
||||
$mail = new \PHPMailer\PHPMailer\PHPMailer();
|
||||
$mail->CharSet = 'UTF-8';
|
||||
$mail->Encoding = 'base64';
|
||||
$mail->From = $mailFrom;
|
||||
$msgFromName = $this->getConfig()->getSetting('mail_from_name');
|
||||
|
||||
if ($msgFromName != null) {
|
||||
$mail->FromName = $msgFromName;
|
||||
}
|
||||
|
||||
$mail->Subject = __('Two Factor Authentication');
|
||||
$mail->addAddress($user->email);
|
||||
|
||||
// Body
|
||||
$mail->isHTML(true);
|
||||
$mail->Body = $this->generateEmailBody($mail->Subject,
|
||||
'<p>' . __('You are receiving this email because two factor email authorisation is enabled in your CMS user account. If you did not make this request, please report this email to your administrator immediately.') . '</p>' . '<p>' . $code . '</p>');
|
||||
|
||||
if (!$mail->send()) {
|
||||
$message = __('Unable to send two factor code to email address associated with this user');
|
||||
} else {
|
||||
$message = __('Two factor code email has been sent to your email address');
|
||||
|
||||
// Audit Log
|
||||
$this->getLog()->audit('User', $user->userId, 'Two Factor Code email sent', [
|
||||
'UserAgent' => $request->getHeader('User-Agent')
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Template
|
||||
$this->getState()->template = 'tfa';
|
||||
|
||||
// the flash message do not work well here - need to reload the page to see the message, hence the below
|
||||
$this->getState()->setData(['message' => $message]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Slim\Http\Response
|
||||
* @throws \RobThree\Auth\TwoFactorAuthException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function twoFactorAuthValidate(Request $request, Response $response): Response
|
||||
{
|
||||
$user = $this->userFactory->getByName($_SESSION['tfaUsername']);
|
||||
$result = false;
|
||||
$updatedCodes = [];
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
if (isset($_POST['code'])) {
|
||||
$issuerSettings = $this->getConfig()->getSetting('TWOFACTOR_ISSUER');
|
||||
$appName = $this->getConfig()->getThemeConfig('app_name');
|
||||
|
||||
if ($issuerSettings !== '') {
|
||||
$issuer = $issuerSettings;
|
||||
} else {
|
||||
$issuer = $appName;
|
||||
}
|
||||
|
||||
$tfa = new TwoFactorAuth($issuer);
|
||||
|
||||
if ($user->twoFactorTypeId === 1 && $user->email !== '') {
|
||||
$result = $tfa->verifyCode($user->twoFactorSecret, $sanitizedParams->getString('code'), 9);
|
||||
} else {
|
||||
$result = $tfa->verifyCode($user->twoFactorSecret, $sanitizedParams->getString('code'), 3);
|
||||
}
|
||||
} elseif (isset($_POST['recoveryCode'])) {
|
||||
// get the array of recovery codes, go through them and try to match provided code
|
||||
$codes = $user->twoFactorRecoveryCodes;
|
||||
|
||||
foreach (json_decode($codes) as $code) {
|
||||
// if the provided recovery code matches one stored in the database, we want to log in the user
|
||||
if ($code === $sanitizedParams->getString('recoveryCode')) {
|
||||
$result = true;
|
||||
}
|
||||
|
||||
if ($code !== $sanitizedParams->getString('recoveryCode')) {
|
||||
$updatedCodes[] = $code;
|
||||
}
|
||||
}
|
||||
|
||||
// recovery codes are one time use, as such we want to update user recovery codes and remove the one that
|
||||
// was just used.
|
||||
$user->updateRecoveryCodes(json_encode($updatedCodes));
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
// We are logged in at this point
|
||||
$this->completeLoginFlow($user, $request);
|
||||
|
||||
$this->setNoOutput(true);
|
||||
|
||||
//unset the session tfaUsername
|
||||
unset($_SESSION['tfaUsername']);
|
||||
|
||||
return $response->withRedirect($this->getRedirect($request, $sanitizedParams->getString('priorRoute')));
|
||||
} else {
|
||||
$this->getLog()->error('Authentication code incorrect, redirecting to login page');
|
||||
$this->getFlash()->addMessage('login_message', __('Authentication code incorrect'));
|
||||
return $response->withRedirect($this->urlFor($request, 'login'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Xibo\Entity\User $user
|
||||
* @param Request $request
|
||||
*/
|
||||
private function completeLoginFlow(User $user, Request $request): void
|
||||
{
|
||||
$user->touch();
|
||||
|
||||
$this->getLog()->info($user->userName . ' user logged in.');
|
||||
|
||||
// Set the userId on the log object
|
||||
$this->getLog()->setUserId($user->userId);
|
||||
$this->getLog()->setIpAddress($request->getAttribute('ip_address'));
|
||||
|
||||
// Switch Session ID's
|
||||
$session = $this->session;
|
||||
$session->setIsExpired(0);
|
||||
$session->regenerateSessionId();
|
||||
$session->setUser($user->userId);
|
||||
|
||||
$this->getLog()->setSessionHistoryId($session->get('sessionHistoryId'));
|
||||
|
||||
// Audit Log
|
||||
$this->getLog()->audit('User', $user->userId, 'Login Granted', [
|
||||
'UserAgent' => $request->getHeader('User-Agent')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a redirect link from the given request and prior route
|
||||
* validate the prior route by only taking its path
|
||||
* @param \Slim\Http\ServerRequest $request
|
||||
* @param string|null $priorRoute
|
||||
* @return string
|
||||
*/
|
||||
private function getRedirect(Request $request, ?string $priorRoute): string
|
||||
{
|
||||
$home = $this->urlFor($request, 'home');
|
||||
|
||||
// Parse the prior route
|
||||
$parsedPriorRoute = parse_url($priorRoute);
|
||||
if (!$parsedPriorRoute) {
|
||||
$priorRoute = $home;
|
||||
} else {
|
||||
$priorRoute = $parsedPriorRoute['path'];
|
||||
}
|
||||
|
||||
// Certain routes always lead home
|
||||
if ($priorRoute == ''
|
||||
|| $priorRoute == '/'
|
||||
|| str_contains($priorRoute, $this->urlFor($request, 'login'))
|
||||
) {
|
||||
$redirectTo = $home;
|
||||
} else {
|
||||
$redirectTo = $priorRoute;
|
||||
}
|
||||
|
||||
return $redirectTo;
|
||||
}
|
||||
}
|
||||
288
lib/Controller/Maintenance.php
Normal file
288
lib/Controller/Maintenance.php
Normal file
@@ -0,0 +1,288 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2023 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Event\MediaDeleteEvent;
|
||||
use Xibo\Factory\MediaFactory;
|
||||
use Xibo\Service\MediaServiceInterface;
|
||||
use Xibo\Storage\StorageServiceInterface;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\ControllerNotImplemented;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
|
||||
/**
|
||||
* Class Maintenance
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class Maintenance extends Base
|
||||
{
|
||||
/** @var StorageServiceInterface */
|
||||
private $store;
|
||||
|
||||
/** @var MediaFactory */
|
||||
private $mediaFactory;
|
||||
|
||||
/** @var MediaServiceInterface */
|
||||
private $mediaService;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param StorageServiceInterface $store
|
||||
* @param MediaFactory $mediaFactory
|
||||
* @param MediaServiceInterface $mediaService
|
||||
*/
|
||||
public function __construct($store, $mediaFactory, MediaServiceInterface $mediaService)
|
||||
{
|
||||
$this->store = $store;
|
||||
$this->mediaFactory = $mediaFactory;
|
||||
$this->mediaService = $mediaService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tidy Library Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function tidyLibraryForm(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'maintenance-form-tidy';
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tidies up the library
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function tidyLibrary(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$tidyOldRevisions = $sanitizedParams->getCheckbox('tidyOldRevisions');
|
||||
$cleanUnusedFiles = $sanitizedParams->getCheckbox('cleanUnusedFiles');
|
||||
$tidyGenericFiles = $sanitizedParams->getCheckbox('tidyGenericFiles');
|
||||
|
||||
if ($this->getConfig()->getSetting('SETTING_LIBRARY_TIDY_ENABLED') != 1) {
|
||||
throw new AccessDeniedException(__('Sorry this function is disabled.'));
|
||||
}
|
||||
|
||||
$this->getLog()->audit('Media', 0, 'Tidy library started from Settings', [
|
||||
'tidyOldRevisions' => $tidyOldRevisions,
|
||||
'cleanUnusedFiles' => $cleanUnusedFiles,
|
||||
'tidyGenericFiles' => $tidyGenericFiles,
|
||||
'initiator' => $this->getUser()->userId
|
||||
]);
|
||||
|
||||
// Also run a script to tidy up orphaned media in the library
|
||||
$library = $this->getConfig()->getSetting('LIBRARY_LOCATION');
|
||||
$this->getLog()->debug('Library Location: ' . $library);
|
||||
|
||||
// Remove temporary files
|
||||
$this->mediaService->removeTempFiles();
|
||||
|
||||
$media = [];
|
||||
$unusedMedia = [];
|
||||
$unusedRevisions = [];
|
||||
|
||||
// DataSets with library images
|
||||
$dataSetSql = '
|
||||
SELECT dataset.dataSetId, datasetcolumn.heading
|
||||
FROM dataset
|
||||
INNER JOIN datasetcolumn
|
||||
ON datasetcolumn.DataSetID = dataset.DataSetID
|
||||
WHERE DataTypeID = 5 AND DataSetColumnTypeID <> 2;
|
||||
';
|
||||
|
||||
$dataSets = $this->store->select($dataSetSql, []);
|
||||
|
||||
// Run a query to get an array containing all of the media in the library
|
||||
// this must contain ALL media, so that we can delete files in the storage that aren;t in the table
|
||||
$sql = '
|
||||
SELECT media.mediaid, media.storedAs, media.type, media.isedited,
|
||||
SUM(CASE WHEN IFNULL(lkwidgetmedia.widgetId, 0) = 0 THEN 0 ELSE 1 END) AS UsedInLayoutCount,
|
||||
SUM(CASE WHEN IFNULL(lkmediadisplaygroup.mediaId, 0) = 0 THEN 0 ELSE 1 END) AS UsedInDisplayCount,
|
||||
SUM(CASE WHEN IFNULL(layout.layoutId, 0) = 0 THEN 0 ELSE 1 END) AS UsedInBackgroundImageCount,
|
||||
SUM(CASE WHEN IFNULL(menu_category.menuCategoryId, 0) = 0 THEN 0 ELSE 1 END) AS UsedInMenuBoardCategoryCount,
|
||||
SUM(CASE WHEN IFNULL(menu_product.menuProductId, 0) = 0 THEN 0 ELSE 1 END) AS UsedInMenuBoardProductCount
|
||||
';
|
||||
|
||||
if (count($dataSets) > 0) {
|
||||
$sql .= ' , SUM(CASE WHEN IFNULL(dataSetImages.mediaId, 0) = 0 THEN 0 ELSE 1 END) AS UsedInDataSetCount ';
|
||||
} else {
|
||||
$sql .= ' , 0 AS UsedInDataSetCount ';
|
||||
}
|
||||
|
||||
$sql .= '
|
||||
FROM `media`
|
||||
LEFT OUTER JOIN `lkwidgetmedia`
|
||||
ON lkwidgetmedia.mediaid = media.mediaid
|
||||
LEFT OUTER JOIN `lkmediadisplaygroup`
|
||||
ON lkmediadisplaygroup.mediaid = media.mediaid
|
||||
LEFT OUTER JOIN `layout`
|
||||
ON `layout`.backgroundImageId = `media`.mediaId
|
||||
LEFT OUTER JOIN `menu_category`
|
||||
ON `menu_category`.mediaId = `media`.mediaId
|
||||
LEFT OUTER JOIN `menu_product`
|
||||
ON `menu_product`.mediaId = `media`.mediaId
|
||||
';
|
||||
|
||||
if (count($dataSets) > 0) {
|
||||
|
||||
$sql .= ' LEFT OUTER JOIN (';
|
||||
|
||||
$first = true;
|
||||
foreach ($dataSets as $dataSet) {
|
||||
$sanitizedDataSet = $this->getSanitizer($dataSet);
|
||||
if (!$first)
|
||||
$sql .= ' UNION ALL ';
|
||||
|
||||
$first = false;
|
||||
|
||||
$dataSetId = $sanitizedDataSet->getInt('dataSetId');
|
||||
$heading = $sanitizedDataSet->getString('heading');
|
||||
|
||||
$sql .= ' SELECT `' . $heading . '` AS mediaId FROM `dataset_' . $dataSetId . '`';
|
||||
}
|
||||
|
||||
$sql .= ') dataSetImages
|
||||
ON dataSetImages.mediaId = `media`.mediaId
|
||||
';
|
||||
}
|
||||
|
||||
$sql .= '
|
||||
GROUP BY media.mediaid, media.storedAs, media.type, media.isedited
|
||||
';
|
||||
|
||||
foreach ($this->store->select($sql, []) as $row) {
|
||||
$media[$row['storedAs']] = $row;
|
||||
$sanitizedRow = $this->getSanitizer($row);
|
||||
|
||||
$type = $sanitizedRow->getString('type');
|
||||
|
||||
// Ignore any module files or fonts
|
||||
if ($type == 'module'
|
||||
|| $type == 'font'
|
||||
|| $type == 'playersoftware'
|
||||
|| ($type == 'genericfile' && $tidyGenericFiles != 1)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Collect media revisions that aren't used
|
||||
if ($tidyOldRevisions && $this->isSafeToDelete($row) && $row['isedited'] > 0) {
|
||||
$unusedRevisions[$row['storedAs']] = $row;
|
||||
}
|
||||
// Collect any files that aren't used
|
||||
else if ($cleanUnusedFiles && $this->isSafeToDelete($row)) {
|
||||
$unusedMedia[$row['storedAs']] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
|
||||
// Library location
|
||||
$libraryLocation = $this->getConfig()->getSetting("LIBRARY_LOCATION");
|
||||
|
||||
// Get a list of all media files
|
||||
foreach(scandir($library) as $file) {
|
||||
|
||||
if ($file == '.' || $file == '..')
|
||||
continue;
|
||||
|
||||
if (is_dir($library . $file))
|
||||
continue;
|
||||
|
||||
// Ignore thumbnails
|
||||
if (strstr($file, 'tn_'))
|
||||
continue;
|
||||
|
||||
// Ignore XLF files
|
||||
if (strstr($file, '.xlf'))
|
||||
continue;
|
||||
|
||||
$i++;
|
||||
|
||||
// Is this file in the system anywhere?
|
||||
if (!array_key_exists($file, $media)) {
|
||||
// Totally missing
|
||||
$this->getLog()->alert('tidyLibrary: Deleting file which is not in the media table: ' . $file);
|
||||
|
||||
// If not, delete it
|
||||
unlink($libraryLocation . $file);
|
||||
} else if (array_key_exists($file, $unusedRevisions)) {
|
||||
// It exists but isn't being used anymore
|
||||
$this->getLog()->alert('tidyLibrary: Deleting unused revision media: ' . $media[$file]['mediaid']);
|
||||
|
||||
$mediaToDelete = $this->mediaFactory->getById($media[$file]['mediaid']);
|
||||
$this->getDispatcher()->dispatch(new MediaDeleteEvent($mediaToDelete), MediaDeleteEvent::$NAME);
|
||||
$mediaToDelete->delete();
|
||||
} else if (array_key_exists($file, $unusedMedia)) {
|
||||
// It exists but isn't being used anymore
|
||||
$this->getLog()->alert('tidyLibrary: Deleting unused media: ' . $media[$file]['mediaid']);
|
||||
|
||||
$mediaToDelete = $this->mediaFactory->getById($media[$file]['mediaid']);
|
||||
$this->getDispatcher()->dispatch(new MediaDeleteEvent($mediaToDelete), MediaDeleteEvent::$NAME);
|
||||
$mediaToDelete->delete();
|
||||
} else {
|
||||
$i--;
|
||||
}
|
||||
}
|
||||
|
||||
$this->getLog()->audit('Media', 0, 'Tidy library from settings complete', [
|
||||
'countDeleted' => $i,
|
||||
'initiator' => $this->getUser()->userId
|
||||
]);
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => __('Library Tidy Complete'),
|
||||
'data' => [
|
||||
'tidied' => $i
|
||||
]
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
private function isSafeToDelete($row): bool
|
||||
{
|
||||
return ($row['UsedInLayoutCount'] <= 0
|
||||
&& $row['UsedInDisplayCount'] <= 0
|
||||
&& $row['UsedInBackgroundImageCount'] <= 0
|
||||
&& $row['UsedInDataSetCount'] <= 0
|
||||
&& $row['UsedInMenuBoardCategoryCount'] <= 0
|
||||
&& $row['UsedInMenuBoardProductCount'] <= 0
|
||||
);
|
||||
}
|
||||
}
|
||||
137
lib/Controller/MediaManager.php
Normal file
137
lib/Controller/MediaManager.php
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2023 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\MediaFactory;
|
||||
use Xibo\Factory\ModuleFactory;
|
||||
use Xibo\Helper\ByteFormatter;
|
||||
use Xibo\Storage\StorageServiceInterface;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class MediaManager
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class MediaManager extends Base
|
||||
{
|
||||
private StorageServiceInterface $store;
|
||||
private ModuleFactory $moduleFactory;
|
||||
private MediaFactory $mediaFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
*/
|
||||
public function __construct(
|
||||
StorageServiceInterface $store,
|
||||
ModuleFactory $moduleFactory,
|
||||
MediaFactory $mediaFactory
|
||||
) {
|
||||
$this->store = $store;
|
||||
$this->moduleFactory = $moduleFactory;
|
||||
$this->mediaFactory = $mediaFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'media-manager-page';
|
||||
$this->getState()->setData([
|
||||
'library' => $this->getLibraryUsage()
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the library usage
|
||||
* @return array
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
private function getLibraryUsage(): array
|
||||
{
|
||||
// Set up some suffixes
|
||||
$suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||
$params = [];
|
||||
|
||||
// Library Size in Bytes
|
||||
$sql = '
|
||||
SELECT COUNT(`mediaId`) AS countOf,
|
||||
IFNULL(SUM(`FileSize`), 0) AS SumSize,
|
||||
`type`
|
||||
FROM `media`
|
||||
WHERE 1 = 1 ';
|
||||
|
||||
$this->mediaFactory->viewPermissionSql(
|
||||
'Xibo\Entity\Media',
|
||||
$sql,
|
||||
$params,
|
||||
'`media`.mediaId',
|
||||
'`media`.userId',
|
||||
[],
|
||||
'media.permissionsFolderId'
|
||||
);
|
||||
$sql .= ' GROUP BY type ';
|
||||
$sql .= ' ORDER BY 2 ';
|
||||
|
||||
$results = $this->store->select($sql, $params);
|
||||
|
||||
$libraryUsage = [];
|
||||
$totalCount = 0;
|
||||
$totalSize = 0;
|
||||
foreach ($results as $library) {
|
||||
$bytes = doubleval($library['SumSize']);
|
||||
$totalSize += $bytes;
|
||||
$totalCount += $library['countOf'];
|
||||
|
||||
try {
|
||||
$title = $this->moduleFactory->getByType($library['type'])->name;
|
||||
} catch (NotFoundException) {
|
||||
$title = $library['type'] === 'module' ? __('Widget cache') : ucfirst($library['type']);
|
||||
}
|
||||
$libraryUsage[] = [
|
||||
'title' => $title,
|
||||
'count' => $library['countOf'],
|
||||
'size' => $bytes,
|
||||
];
|
||||
}
|
||||
|
||||
// Decide what our units are going to be, based on the size
|
||||
$base = ($totalSize === 0) ? 0 : floor(log($totalSize) / log(1024));
|
||||
|
||||
return [
|
||||
'countOf' => $totalCount,
|
||||
'size' => ByteFormatter::format($totalSize, 1, true),
|
||||
'types' => $libraryUsage,
|
||||
'typesSuffix' => $suffixes[$base],
|
||||
'typesBase' => $base,
|
||||
];
|
||||
}
|
||||
}
|
||||
629
lib/Controller/MenuBoard.php
Normal file
629
lib/Controller/MenuBoard.php
Normal file
@@ -0,0 +1,629 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2023 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\FolderFactory;
|
||||
use Xibo\Factory\MenuBoardFactory;
|
||||
use Xibo\Factory\UserFactory;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Menu Board Controller
|
||||
*/
|
||||
class MenuBoard extends Base
|
||||
{
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param MenuBoardFactory $menuBoardFactory
|
||||
* @param FolderFactory $folderFactory
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly MenuBoardFactory $menuBoardFactory,
|
||||
private readonly FolderFactory $folderFactory
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the Menu Board Page
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response)
|
||||
{
|
||||
// Call to render the template
|
||||
$this->getState()->template = 'menuboard-page';
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Grid of Menu Boards
|
||||
*
|
||||
* @SWG\Get(
|
||||
* path="/menuboards",
|
||||
* operationId="menuBoardSearch",
|
||||
* tags={"menuBoard"},
|
||||
* summary="Search Menu Boards",
|
||||
* description="Search all Menu Boards this user has access to",
|
||||
* @SWG\Parameter(
|
||||
* name="menuId",
|
||||
* in="query",
|
||||
* description="Filter by Menu board Id",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="userId",
|
||||
* in="query",
|
||||
* description="Filter by Owner Id",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="folderId",
|
||||
* in="query",
|
||||
* description="Filter by Folder Id",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="query",
|
||||
* description="Filter by name",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="code",
|
||||
* in="query",
|
||||
* description="Filter by code",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/MenuBoard")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function grid(Request $request, Response $response): Response
|
||||
{
|
||||
$parsedParams = $this->getSanitizer($request->getQueryParams());
|
||||
|
||||
$filter = [
|
||||
'menuId' => $parsedParams->getInt('menuId'),
|
||||
'userId' => $parsedParams->getInt('userId'),
|
||||
'name' => $parsedParams->getString('name'),
|
||||
'code' => $parsedParams->getString('code'),
|
||||
'folderId' => $parsedParams->getInt('folderId'),
|
||||
'logicalOperatorName' => $parsedParams->getString('logicalOperatorName'),
|
||||
];
|
||||
|
||||
$menuBoards = $this->menuBoardFactory->query(
|
||||
$this->gridRenderSort($parsedParams),
|
||||
$this->gridRenderFilter($filter, $parsedParams)
|
||||
);
|
||||
|
||||
foreach ($menuBoards as $menuBoard) {
|
||||
if ($this->isApi($request)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$menuBoard->includeProperty('buttons');
|
||||
$menuBoard->buttons = [];
|
||||
|
||||
if ($this->getUser()->featureEnabled('menuBoard.modify') && $this->getUser()->checkEditable($menuBoard)) {
|
||||
$menuBoard->buttons[] = [
|
||||
'id' => 'menuBoard_button_viewcategories',
|
||||
'url' => $this->urlFor($request, 'menuBoard.category.view', ['id' => $menuBoard->menuId]),
|
||||
'class' => 'XiboRedirectButton',
|
||||
'text' => __('View Categories')
|
||||
];
|
||||
|
||||
$menuBoard->buttons[] = [
|
||||
'id' => 'menuBoard_edit_button',
|
||||
'url' => $this->urlFor($request, 'menuBoard.edit.form', ['id' => $menuBoard->menuId]),
|
||||
'text' => __('Edit')
|
||||
];
|
||||
|
||||
if ($this->getUser()->featureEnabled('folder.view')) {
|
||||
// Select Folder
|
||||
$menuBoard->buttons[] = [
|
||||
'id' => 'menuBoard_button_selectfolder',
|
||||
'url' => $this->urlFor($request, 'menuBoard.selectfolder.form', ['id' => $menuBoard->menuId]),
|
||||
'text' => __('Select Folder'),
|
||||
'multi-select' => true,
|
||||
'dataAttributes' => [
|
||||
[
|
||||
'name' => 'commit-url',
|
||||
'value' => $this->urlFor($request, 'menuBoard.selectfolder', ['id' => $menuBoard->menuId])
|
||||
],
|
||||
['name' => 'commit-method', 'value' => 'put'],
|
||||
['name' => 'id', 'value' => 'menuBoard_button_selectfolder'],
|
||||
['name' => 'text', 'value' => __('Move to Folder')],
|
||||
['name' => 'rowtitle', 'value' => $menuBoard->name],
|
||||
['name' => 'form-callback', 'value' => 'moveFolderMultiSelectFormOpen']
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->getUser()->featureEnabled('menuBoard.modify') && $this->getUser()->checkPermissionsModifyable($menuBoard)) {
|
||||
$menuBoard->buttons[] = ['divider' => true];
|
||||
|
||||
// Share button
|
||||
$menuBoard->buttons[] = [
|
||||
'id' => 'menuBoard_button_permissions',
|
||||
'url' => $this->urlFor($request, 'user.permissions.form', ['entity' => 'MenuBoard', 'id' => $menuBoard->menuId]),
|
||||
'text' => __('Share'),
|
||||
'dataAttributes' => [
|
||||
[
|
||||
'name' => 'commit-url',
|
||||
'value' => $this->urlFor($request, 'user.permissions.multi', ['entity' => 'MenuBoard', 'id' => $menuBoard->menuId])
|
||||
],
|
||||
['name' => 'commit-method', 'value' => 'post'],
|
||||
['name' => 'id', 'value' => 'menuBoard_button_permissions'],
|
||||
['name' => 'text', 'value' => __('Share')],
|
||||
['name' => 'rowtitle', 'value' => $menuBoard->name],
|
||||
['name' => 'sort-group', 'value' => 2],
|
||||
['name' => 'custom-handler', 'value' => 'XiboMultiSelectPermissionsFormOpen'],
|
||||
[
|
||||
'name' => 'custom-handler-url',
|
||||
'value' => $this->urlFor($request, 'user.permissions.multi.form', ['entity' => 'MenuBoard'])
|
||||
],
|
||||
['name' => 'content-id-name', 'value' => 'menuId']
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->getUser()->featureEnabled('menuBoard.modify')
|
||||
&& $this->getUser()->checkDeleteable($menuBoard)
|
||||
) {
|
||||
$menuBoard->buttons[] = ['divider' => true];
|
||||
|
||||
$menuBoard->buttons[] = [
|
||||
'id' => 'menuBoard_delete_button',
|
||||
'url' => $this->urlFor($request, 'menuBoard.delete.form', ['id' => $menuBoard->menuId]),
|
||||
'text' => __('Delete')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->menuBoardFactory->countLast();
|
||||
$this->getState()->setData($menuBoards);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu Board Add Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function addForm(Request $request, Response $response): Response
|
||||
{
|
||||
$this->getState()->template = 'menuboard-form-add';
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new Menu Board
|
||||
*
|
||||
* @SWG\Post(
|
||||
* path="/menuboard",
|
||||
* operationId="menuBoardAdd",
|
||||
* tags={"menuBoard"},
|
||||
* summary="Add Menu Board",
|
||||
* description="Add a new Menu Board",
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="formData",
|
||||
* description="Menu Board name",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="description",
|
||||
* in="formData",
|
||||
* description="Menu Board description",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="code",
|
||||
* in="formData",
|
||||
* description="Menu Board code identifier",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="folderId",
|
||||
* in="formData",
|
||||
* description="Menu Board Folder Id",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/MenuBoard"),
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new record",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function add(Request $request, Response $response): Response
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$name = $sanitizedParams->getString('name');
|
||||
$description = $sanitizedParams->getString('description');
|
||||
$code = $sanitizedParams->getString('code');
|
||||
$folderId = $sanitizedParams->getInt('folderId');
|
||||
|
||||
if ($folderId === 1) {
|
||||
$this->checkRootFolderAllowSave();
|
||||
}
|
||||
|
||||
if (empty($folderId) || !$this->getUser()->featureEnabled('folder.view')) {
|
||||
$folderId = $this->getUser()->homeFolderId;
|
||||
}
|
||||
|
||||
$folder = $this->folderFactory->getById($folderId, 0);
|
||||
|
||||
$menuBoard = $this->menuBoardFactory->create($name, $description, $code);
|
||||
$menuBoard->folderId = $folder->getId();
|
||||
$menuBoard->permissionsFolderId = $folder->getPermissionFolderIdOrThis();
|
||||
$menuBoard->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => __('Added Menu Board'),
|
||||
'httpStatus' => 201,
|
||||
'id' => $menuBoard->menuId,
|
||||
'data' => $menuBoard,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param int $id
|
||||
* @return Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function editForm(Request $request, Response $response, $id): Response
|
||||
{
|
||||
$menuBoard = $this->menuBoardFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($menuBoard)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'menuboard-form-edit';
|
||||
$this->getState()->setData([
|
||||
'menuBoard' => $menuBoard
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Put(
|
||||
* path="/menuboard/{menuId}",
|
||||
* operationId="menuBoardEdit",
|
||||
* tags={"menuBoard"},
|
||||
* summary="Edit Menu Board",
|
||||
* description="Edit existing Menu Board",
|
||||
* @SWG\Parameter(
|
||||
* name="menuId",
|
||||
* in="path",
|
||||
* description="The Menu Board ID to Edit",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="formData",
|
||||
* description="Menu Board name",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="description",
|
||||
* in="formData",
|
||||
* description="Menu Board description",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="code",
|
||||
* in="formData",
|
||||
* description="Menu Board code identifier",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="folderId",
|
||||
* in="formData",
|
||||
* description="Menu Board Folder Id",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param int $id
|
||||
* @return Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function edit(Request $request, Response $response, $id): Response
|
||||
{
|
||||
$menuBoard = $this->menuBoardFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($menuBoard)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$menuBoard->name = $sanitizedParams->getString('name');
|
||||
$menuBoard->description = $sanitizedParams->getString('description');
|
||||
$menuBoard->code = $sanitizedParams->getString('code');
|
||||
$menuBoard->folderId = $sanitizedParams->getInt('folderId', ['default' => $menuBoard->folderId]);
|
||||
|
||||
if ($menuBoard->hasPropertyChanged('folderId')) {
|
||||
if ($menuBoard->folderId === 1) {
|
||||
$this->checkRootFolderAllowSave();
|
||||
}
|
||||
$folder = $this->folderFactory->getById($menuBoard->folderId);
|
||||
$menuBoard->permissionsFolderId = ($folder->getPermissionFolderId() == null) ? $folder->id : $folder->getPermissionFolderId();
|
||||
}
|
||||
|
||||
$menuBoard->save();
|
||||
|
||||
// Success
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 200,
|
||||
'message' => sprintf(__('Edited %s'), $menuBoard->name),
|
||||
'id' => $menuBoard->menuId,
|
||||
'data' => $menuBoard
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param int $id
|
||||
* @return Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function deleteForm(Request $request, Response $response, $id): Response
|
||||
{
|
||||
$menuBoard = $this->menuBoardFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($menuBoard)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'menuboard-form-delete';
|
||||
$this->getState()->setData([
|
||||
'menuBoard' => $menuBoard
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Delete(
|
||||
* path="/menuboard/{menuId}",
|
||||
* operationId="menuBoardDelete",
|
||||
* tags={"menuBoard"},
|
||||
* summary="Delete Menu Board",
|
||||
* description="Delete existing Menu Board",
|
||||
* @SWG\Parameter(
|
||||
* name="menuId",
|
||||
* in="path",
|
||||
* description="The Menu Board ID to Delete",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function delete(Request $request, Response $response, $id): Response
|
||||
{
|
||||
$menuBoard = $this->menuBoardFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($menuBoard)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Issue the delete
|
||||
$menuBoard->delete();
|
||||
|
||||
// Success
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Deleted %s'), $menuBoard->name)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select Folder Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param int $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function selectFolderForm(Request $request, Response $response, $id)
|
||||
{
|
||||
// Get the Menu Board
|
||||
$menuBoard = $this->menuBoardFactory->getById($id);
|
||||
|
||||
// Check Permissions
|
||||
if (!$this->getUser()->checkEditable($menuBoard)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$data = [
|
||||
'menuBoard' => $menuBoard
|
||||
];
|
||||
|
||||
$this->getState()->template = 'menuboard-form-selectfolder';
|
||||
$this->getState()->setData($data);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Put(
|
||||
* path="/menuboard/{id}/selectfolder",
|
||||
* operationId="menuBoardSelectFolder",
|
||||
* tags={"menuBoard"},
|
||||
* summary="Menu Board Select folder",
|
||||
* description="Select Folder for Menu Board",
|
||||
* @SWG\Parameter(
|
||||
* name="menuId",
|
||||
* in="path",
|
||||
* description="The Menu Board ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="folderId",
|
||||
* in="formData",
|
||||
* description="Folder ID to which this object should be assigned to",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/MenuBoard")
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param int $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function selectFolder(Request $request, Response $response, $id)
|
||||
{
|
||||
// Get the Menu Board
|
||||
$menuBoard = $this->menuBoardFactory->getById($id);
|
||||
|
||||
// Check Permissions
|
||||
if (!$this->getUser()->checkEditable($menuBoard)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$folderId = $this->getSanitizer($request->getParams())->getInt('folderId');
|
||||
|
||||
if ($folderId === 1) {
|
||||
$this->checkRootFolderAllowSave();
|
||||
}
|
||||
|
||||
$menuBoard->folderId = $folderId;
|
||||
$folder = $this->folderFactory->getById($menuBoard->folderId);
|
||||
$menuBoard->permissionsFolderId = ($folder->getPermissionFolderId() == null) ? $folder->id : $folder->getPermissionFolderId();
|
||||
|
||||
// Save
|
||||
$menuBoard->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Menu Board %s moved to Folder %s'), $menuBoard->name, $folder->text)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
524
lib/Controller/MenuBoardCategory.php
Normal file
524
lib/Controller/MenuBoardCategory.php
Normal file
@@ -0,0 +1,524 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2023 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\MediaFactory;
|
||||
use Xibo\Factory\MenuBoardCategoryFactory;
|
||||
use Xibo\Factory\MenuBoardFactory;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
class MenuBoardCategory extends Base
|
||||
{
|
||||
/**
|
||||
* @var MenuBoardFactory
|
||||
*/
|
||||
private $menuBoardFactory;
|
||||
|
||||
/**
|
||||
* @var MenuBoardCategoryFactory
|
||||
*/
|
||||
private $menuBoardCategoryFactory;
|
||||
|
||||
/**
|
||||
* @var MediaFactory
|
||||
*/
|
||||
private $mediaFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param MenuBoardFactory $menuBoardFactory
|
||||
* @param $menuBoardCategoryFactory
|
||||
* @param MediaFactory $mediaFactory
|
||||
*/
|
||||
public function __construct(
|
||||
$menuBoardFactory,
|
||||
$menuBoardCategoryFactory,
|
||||
$mediaFactory
|
||||
) {
|
||||
$this->menuBoardFactory = $menuBoardFactory;
|
||||
$this->menuBoardCategoryFactory = $menuBoardCategoryFactory;
|
||||
$this->mediaFactory = $mediaFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the Menu Board Categories Page
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param int $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response, $id)
|
||||
{
|
||||
$menuBoard = $this->menuBoardFactory->getById($id);
|
||||
|
||||
// Call to render the template
|
||||
$this->getState()->template = 'menuboard-category-page';
|
||||
$this->getState()->setData([
|
||||
'menuBoard' => $menuBoard
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Grid of Menu Board Categories
|
||||
*
|
||||
* @SWG\Get(
|
||||
* path="/menuboard/{menuId}/categories",
|
||||
* operationId="menuBoardCategorySearch",
|
||||
* tags={"menuBoard"},
|
||||
* summary="Search Menu Board Categories",
|
||||
* description="Search all Menu Boards Categories this user has access to",
|
||||
* @SWG\Parameter(
|
||||
* name="menuId",
|
||||
* in="path",
|
||||
* description="Filter by Menu board Id",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="menuCategoryId",
|
||||
* in="query",
|
||||
* description="Filter by Menu Board Category Id",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="query",
|
||||
* description="Filter by name",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="code",
|
||||
* in="query",
|
||||
* description="Filter by code",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/MenuBoard")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param int $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function grid(Request $request, Response $response, $id): Response
|
||||
{
|
||||
$parsedParams = $this->getSanitizer($request->getQueryParams());
|
||||
$menuBoard = $this->menuBoardFactory->getById($id);
|
||||
|
||||
$filter = [
|
||||
'menuId' => $menuBoard->menuId,
|
||||
'menuCategoryId' => $parsedParams->getInt('menuCategoryId'),
|
||||
'name' => $parsedParams->getString('name'),
|
||||
'code' => $parsedParams->getString('code')
|
||||
];
|
||||
|
||||
$menuBoardCategories = $this->menuBoardCategoryFactory->query(
|
||||
$this->gridRenderSort($parsedParams),
|
||||
$this->gridRenderFilter($filter, $parsedParams)
|
||||
);
|
||||
|
||||
|
||||
foreach ($menuBoardCategories as $menuBoardCategory) {
|
||||
if ($this->isApi($request)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($menuBoardCategory->mediaId != 0) {
|
||||
$menuBoardCategory->setUnmatchedProperty(
|
||||
'thumbnail',
|
||||
$this->urlFor(
|
||||
$request,
|
||||
'library.download',
|
||||
['id' => $menuBoardCategory->mediaId],
|
||||
['preview' => 1],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$menuBoardCategory->includeProperty('buttons');
|
||||
$menuBoardCategory->buttons = [];
|
||||
|
||||
if ($this->getUser()->featureEnabled('menuBoard.modify') && $this->getUser()->checkEditable($menuBoard)) {
|
||||
$menuBoardCategory->buttons[] = [
|
||||
'id' => 'menuBoardCategory_button_viewproducts',
|
||||
'url' => $this->urlFor($request, 'menuBoard.product.view', ['id' => $menuBoardCategory->menuCategoryId]),
|
||||
'class' => 'XiboRedirectButton',
|
||||
'text' => __('View Products')
|
||||
];
|
||||
|
||||
$menuBoardCategory->buttons[] = [
|
||||
'id' => 'menuBoardCategory_edit_button',
|
||||
'url' => $this->urlFor($request, 'menuBoard.category.edit.form', ['id' => $menuBoardCategory->menuCategoryId]),
|
||||
'text' => __('Edit')
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->getUser()->featureEnabled('menuBoard.modify') && $this->getUser()->checkDeleteable($menuBoard)) {
|
||||
$menuBoardCategory->buttons[] = ['divider' => true];
|
||||
|
||||
$menuBoardCategory->buttons[] = [
|
||||
'id' => 'menuBoardCategory_delete_button',
|
||||
'url' => $this->urlFor($request, 'menuBoard.category.delete.form', ['id' => $menuBoardCategory->menuCategoryId]),
|
||||
'text' => __('Delete')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->menuBoardCategoryFactory->countLast();
|
||||
$this->getState()->setData($menuBoardCategories);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu Board Category Add Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function addForm(Request $request, Response $response, $id): Response
|
||||
{
|
||||
$menuBoard = $this->menuBoardFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($menuBoard)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'menuboard-category-form-add';
|
||||
$this->getState()->setData([
|
||||
'menuBoard' => $menuBoard
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new Menu Board Category
|
||||
*
|
||||
* @SWG\Post(
|
||||
* path="/menuboard/{menuId}/category",
|
||||
* operationId="menuBoardCategoryAdd",
|
||||
* tags={"menuBoard"},
|
||||
* summary="Add Menu Board",
|
||||
* description="Add a new Menu Board Category",
|
||||
* @SWG\Parameter(
|
||||
* name="menuId",
|
||||
* in="path",
|
||||
* description="The Menu Board ID to which we want to add this Category to",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="formData",
|
||||
* description="Menu Board Category name",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="mediaId",
|
||||
* in="formData",
|
||||
* description="Media ID associated with this Menu Board Category",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="code",
|
||||
* in="formData",
|
||||
* description="Menu Board Category code identifier",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="description",
|
||||
* in="formData",
|
||||
* description="Menu Board Category description",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/MenuBoard"),
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new record",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param int $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function add(Request $request, Response $response, $id): Response
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$menuBoard = $this->menuBoardFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($menuBoard)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$name = $sanitizedParams->getString('name');
|
||||
$mediaId = $sanitizedParams->getInt('mediaId');
|
||||
$code = $sanitizedParams->getString('code');
|
||||
$description = $sanitizedParams->getString('description');
|
||||
|
||||
$menuBoardCategory = $this->menuBoardCategoryFactory->create($id, $name, $mediaId, $code, $description);
|
||||
$menuBoardCategory->save();
|
||||
$menuBoard->save(['audit' => false]);
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => __('Added Menu Board Category'),
|
||||
'httpStatus' => 201,
|
||||
'id' => $menuBoardCategory->menuCategoryId,
|
||||
'data' => $menuBoardCategory,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param int $id
|
||||
* @return Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function editForm(Request $request, Response $response, $id): Response
|
||||
{
|
||||
$menuBoard = $this->menuBoardFactory->getByMenuCategoryId($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($menuBoard)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$menuBoardCategory = $this->menuBoardCategoryFactory->getById($id);
|
||||
|
||||
$this->getState()->template = 'menuboard-category-form-edit';
|
||||
$this->getState()->setData([
|
||||
'menuBoardCategory' => $menuBoardCategory,
|
||||
'media' => $menuBoardCategory->mediaId != null ? $this->mediaFactory->getById($menuBoardCategory->mediaId) : null
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Put(
|
||||
* path="/menuboard/{menuCategoryId}/category",
|
||||
* operationId="menuBoardCategoryEdit",
|
||||
* tags={"menuBoard"},
|
||||
* summary="Edit Menu Board Category",
|
||||
* description="Edit existing Menu Board Category",
|
||||
* @SWG\Parameter(
|
||||
* name="menuCategoryId",
|
||||
* in="path",
|
||||
* description="The Menu Board Category ID to Edit",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="formData",
|
||||
* description="Menu Board name",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="mediaId",
|
||||
* in="formData",
|
||||
* description="Media ID from CMS Library to associate with this Menu Board Category",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="code",
|
||||
* in="formData",
|
||||
* description="Menu Board Category code identifier",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="description",
|
||||
* in="formData",
|
||||
* description="Menu Board Category description",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param int $id
|
||||
* @return Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function edit(Request $request, Response $response, $id): Response
|
||||
{
|
||||
$menuBoard = $this->menuBoardFactory->getByMenuCategoryId($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($menuBoard)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
$menuBoardCategory = $this->menuBoardCategoryFactory->getById($id);
|
||||
|
||||
$menuBoardCategory->name = $sanitizedParams->getString('name');
|
||||
$menuBoardCategory->mediaId = $sanitizedParams->getInt('mediaId');
|
||||
$menuBoardCategory->code = $sanitizedParams->getString('code');
|
||||
$menuBoardCategory->description = $sanitizedParams->getString('description');
|
||||
$menuBoardCategory->save();
|
||||
$menuBoard->save();
|
||||
|
||||
// Success
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 200,
|
||||
'message' => sprintf(__('Edited %s'), $menuBoardCategory->name),
|
||||
'id' => $menuBoardCategory->menuCategoryId,
|
||||
'data' => $menuBoardCategory
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param int $id
|
||||
* @return Response
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function deleteForm(Request $request, Response $response, $id): Response
|
||||
{
|
||||
$menuBoard = $this->menuBoardFactory->getByMenuCategoryId($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($menuBoard)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$menuBoardCategory = $this->menuBoardCategoryFactory->getById($id);
|
||||
|
||||
$this->getState()->template = 'menuboard-category-form-delete';
|
||||
$this->getState()->setData([
|
||||
'menuBoardCategory' => $menuBoardCategory
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Delete(
|
||||
* path="/menuboard/{menuCategoryId}/category",
|
||||
* operationId="menuBoardCategoryDelete",
|
||||
* tags={"menuBoard"},
|
||||
* summary="Delete Menu Board Category",
|
||||
* description="Delete existing Menu Board Category",
|
||||
* @SWG\Parameter(
|
||||
* name="menuId",
|
||||
* in="path",
|
||||
* description="The menuId to Delete",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function delete(Request $request, Response $response, $id): Response
|
||||
{
|
||||
$menuBoard = $this->menuBoardFactory->getByMenuCategoryId($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($menuBoard)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$menuBoardCategory = $this->menuBoardCategoryFactory->getById($id);
|
||||
|
||||
// Issue the delete
|
||||
$menuBoardCategory->delete();
|
||||
|
||||
// Success
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Deleted %s'), $menuBoardCategory->name)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
727
lib/Controller/MenuBoardProduct.php
Normal file
727
lib/Controller/MenuBoardProduct.php
Normal file
@@ -0,0 +1,727 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2023 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\MediaFactory;
|
||||
use Xibo\Factory\MenuBoardCategoryFactory;
|
||||
use Xibo\Factory\MenuBoardFactory;
|
||||
use Xibo\Factory\MenuBoardProductOptionFactory;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
class MenuBoardProduct extends Base
|
||||
{
|
||||
/**
|
||||
* @var MenuBoardFactory
|
||||
*/
|
||||
private $menuBoardFactory;
|
||||
|
||||
/**
|
||||
* @var MenuBoardCategoryFactory
|
||||
*/
|
||||
private $menuBoardCategoryFactory;
|
||||
|
||||
/**
|
||||
* @var MenuBoardProductOptionFactory
|
||||
*/
|
||||
private $menuBoardProductOptionFactory;
|
||||
|
||||
/**
|
||||
* @var MediaFactory
|
||||
*/
|
||||
private $mediaFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param MenuBoardFactory $menuBoardFactory
|
||||
* @param MenuBoardCategoryFactory $menuBoardCategoryFactory
|
||||
* @param MenuBoardProductOptionFactory $menuBoardProductOptionFactory
|
||||
* @param MediaFactory $mediaFactory
|
||||
*/
|
||||
public function __construct(
|
||||
$menuBoardFactory,
|
||||
$menuBoardCategoryFactory,
|
||||
$menuBoardProductOptionFactory,
|
||||
$mediaFactory
|
||||
) {
|
||||
$this->menuBoardFactory = $menuBoardFactory;
|
||||
$this->menuBoardCategoryFactory = $menuBoardCategoryFactory;
|
||||
$this->menuBoardProductOptionFactory = $menuBoardProductOptionFactory;
|
||||
$this->mediaFactory = $mediaFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the Menu Board Page
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param int $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response, $id)
|
||||
{
|
||||
$menuBoard = $this->menuBoardFactory->getByMenuCategoryId($id);
|
||||
$menuBoardCategory = $this->menuBoardCategoryFactory->getById($id);
|
||||
$categories = $this->menuBoardCategoryFactory->getByMenuId($menuBoard->menuId);
|
||||
|
||||
// Call to render the template
|
||||
$this->getState()->template = 'menuboard-product-page';
|
||||
$this->getState()->setData([
|
||||
'menuBoard' => $menuBoard,
|
||||
'menuBoardCategory' => $menuBoardCategory,
|
||||
'categories' => $categories
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Grid of Menu Board Products
|
||||
*
|
||||
* @SWG\Get(
|
||||
* path="/menuboard/{menuCategoryId}/products",
|
||||
* operationId="menuBoardProductsSearch",
|
||||
* tags={"menuBoard"},
|
||||
* summary="Search Menu Board Products",
|
||||
* description="Search all Menu Boards Products this user has access to",
|
||||
* @SWG\Parameter(
|
||||
* name="menuCategoryId",
|
||||
* in="path",
|
||||
* description="Filter by Menu Board Category Id",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="menuId",
|
||||
* in="query",
|
||||
* description="Filter by Menu board Id",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="query",
|
||||
* description="Filter by name",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="code",
|
||||
* in="query",
|
||||
* description="Filter by code",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/MenuBoard")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param int $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function grid(Request $request, Response $response, $id): Response
|
||||
{
|
||||
$parsedParams = $this->getSanitizer($request->getQueryParams());
|
||||
$menuBoard = $this->menuBoardFactory->getByMenuCategoryId($id);
|
||||
|
||||
$filter = [
|
||||
'menuProductId' => $parsedParams->getInt('menuProductId'),
|
||||
'menuCategoryId' => $id,
|
||||
'name' => $parsedParams->getString('name'),
|
||||
'code' => $parsedParams->getString('code')
|
||||
];
|
||||
|
||||
$menuBoardProducts = $this->menuBoardCategoryFactory->getProductData(
|
||||
$this->gridRenderSort($parsedParams),
|
||||
$this->gridRenderFilter($filter, $parsedParams)
|
||||
);
|
||||
|
||||
foreach ($menuBoardProducts as $menuBoardProduct) {
|
||||
if ($this->isApi($request)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$menuBoardProduct->includeProperty('buttons');
|
||||
$menuBoardProduct->buttons = [];
|
||||
|
||||
if ($menuBoardProduct->mediaId != 0) {
|
||||
$menuBoardProduct->setUnmatchedProperty(
|
||||
'thumbnail',
|
||||
$this->urlFor($request, 'library.download', ['id' => $menuBoardProduct->mediaId], ['preview' => 1]),
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->getUser()->featureEnabled('menuBoard.modify') && $this->getUser()->checkEditable($menuBoard)) {
|
||||
$menuBoardProduct->buttons[] = [
|
||||
'id' => 'menuBoardProduct_edit_button',
|
||||
'url' => $this->urlFor($request, 'menuBoard.product.edit.form', ['id' => $menuBoardProduct->menuProductId]),
|
||||
'text' => __('Edit')
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->getUser()->featureEnabled('menuBoard.modify') && $this->getUser()->checkDeleteable($menuBoard)) {
|
||||
$menuBoardProduct->buttons[] = ['divider' => true];
|
||||
|
||||
$menuBoardProduct->buttons[] = [
|
||||
'id' => 'menuBoardProduct_delete_button',
|
||||
'url' => $this->urlFor($request, 'menuBoard.product.delete.form', ['id' => $menuBoardProduct->menuProductId]),
|
||||
'text' => __('Delete'),
|
||||
'multi-select' => true,
|
||||
'dataAttributes' => [
|
||||
['name' => 'commit-url', 'value' => $this->urlFor($request, 'menuBoard.product.delete', ['id' => $menuBoardProduct->menuProductId])],
|
||||
['name' => 'commit-method', 'value' => 'delete'],
|
||||
['name' => 'id', 'value' => 'menuBoardProduct_delete_button'],
|
||||
['name' => 'text', 'value' => __('Delete')],
|
||||
['name' => 'sort-group', 'value' => 1],
|
||||
['name' => 'rowtitle', 'value' => $menuBoardProduct->name]
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$menuBoard->setActive();
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->menuBoardCategoryFactory->countLast();
|
||||
$this->getState()->setData($menuBoardProducts);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
public function productsForWidget(Request $request, Response $response): Response
|
||||
{
|
||||
$parsedParams = $this->getSanitizer($request->getQueryParams());
|
||||
$categories = $parsedParams->getString('categories');
|
||||
|
||||
$filter = [
|
||||
'menuId' => $parsedParams->getInt('menuId'),
|
||||
'menuProductId' => $parsedParams->getInt('menuProductId'),
|
||||
'menuCategoryId' => $parsedParams->getInt('menuCategoryId'),
|
||||
'name' => $parsedParams->getString('name'),
|
||||
'availability' => $parsedParams->getInt('availability'),
|
||||
'categories' => $categories
|
||||
];
|
||||
|
||||
$menuBoardProducts = $this->menuBoardCategoryFactory->getProductData(
|
||||
$this->gridRenderSort($parsedParams),
|
||||
$this->gridRenderFilter($filter, $parsedParams)
|
||||
);
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->menuBoardCategoryFactory->countLast();
|
||||
$this->getState()->setData($menuBoardProducts);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu Board Category Add Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function addForm(Request $request, Response $response, $id): Response
|
||||
{
|
||||
$menuBoard = $this->menuBoardFactory->getByMenuCategoryId($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($menuBoard)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$menuBoardCategory = $this->menuBoardCategoryFactory->getById($id);
|
||||
|
||||
$this->getState()->template = 'menuboard-product-form-add';
|
||||
$this->getState()->setData([
|
||||
'menuBoard' => $menuBoard,
|
||||
'menuBoardCategory' => $menuBoardCategory
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new Menu Board Product
|
||||
*
|
||||
* @SWG\Post(
|
||||
* path="/menuboard/{menuCategoryId}/product",
|
||||
* operationId="menuBoardProductAdd",
|
||||
* tags={"menuBoard"},
|
||||
* summary="Add Menu Board Product",
|
||||
* description="Add a new Menu Board Product",
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="formData",
|
||||
* description="Menu Board Product name",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="description",
|
||||
* in="formData",
|
||||
* description="Menu Board Product description",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="price",
|
||||
* in="formData",
|
||||
* description="Menu Board Product price",
|
||||
* type="number",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="allergyInfo",
|
||||
* in="formData",
|
||||
* description="Menu Board Product allergyInfo",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="calories",
|
||||
* in="formData",
|
||||
* description="Menu Board Product calories",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="displayOrder",
|
||||
* in="formData",
|
||||
* description="Menu Board Product Display Order, used for sorting",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="availability",
|
||||
* in="formData",
|
||||
* description="Menu Board Product availability",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="mediaId",
|
||||
* in="formData",
|
||||
* description="Media ID from CMS Library to associate with this Menu Board Product",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="code",
|
||||
* in="formData",
|
||||
* description="Menu Board Product code",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="productOptions",
|
||||
* in="formData",
|
||||
* description="An array of optional Product Option names",
|
||||
* type="array",
|
||||
* required=false,
|
||||
* @SWG\Items(type="string")
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="productValues",
|
||||
* in="formData",
|
||||
* description="An array of optional Product Option values",
|
||||
* type="array",
|
||||
* required=false,
|
||||
* @SWG\Items(type="string")
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/MenuBoard"),
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new record",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param int $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function add(Request $request, Response $response, $id): Response
|
||||
{
|
||||
$menuBoard = $this->menuBoardFactory->getByMenuCategoryId($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($menuBoard)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$menuBoardCategory = $this->menuBoardCategoryFactory->getById($id);
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$name = $sanitizedParams->getString('name');
|
||||
$mediaId = $sanitizedParams->getInt('mediaId');
|
||||
$price = $sanitizedParams->getDouble('price');
|
||||
$description = $sanitizedParams->getString('description');
|
||||
$allergyInfo = $sanitizedParams->getString('allergyInfo');
|
||||
$calories = $sanitizedParams->getInt('calories');
|
||||
$displayOrder = $sanitizedParams->getInt('displayOrder');
|
||||
$availability = $sanitizedParams->getCheckbox('availability');
|
||||
$productOptions = $sanitizedParams->getArray('productOptions', ['default' => []]);
|
||||
$productValues = $sanitizedParams->getArray('productValues', ['default' => []]);
|
||||
$code = $sanitizedParams->getString('code');
|
||||
|
||||
// If the display order is empty, get the next highest one.
|
||||
if ($displayOrder === null) {
|
||||
$displayOrder = $this->menuBoardCategoryFactory->getNextDisplayOrder($menuBoardCategory->menuCategoryId);
|
||||
}
|
||||
|
||||
$menuBoardProduct = $this->menuBoardCategoryFactory->createProduct(
|
||||
$menuBoard->menuId,
|
||||
$menuBoardCategory->menuCategoryId,
|
||||
$name,
|
||||
$price,
|
||||
$description,
|
||||
$allergyInfo,
|
||||
$calories,
|
||||
$displayOrder,
|
||||
$availability,
|
||||
$mediaId,
|
||||
$code
|
||||
);
|
||||
$menuBoardProduct->save();
|
||||
|
||||
if (!empty(array_filter($productOptions)) && !empty(array_filter($productValues))) {
|
||||
$productDetails = array_filter(array_combine($productOptions, $productValues));
|
||||
$parsedDetails = $this->getSanitizer($productDetails);
|
||||
|
||||
foreach ($productDetails as $option => $value) {
|
||||
$productOption = $this->menuBoardProductOptionFactory->create(
|
||||
$menuBoardProduct->menuProductId,
|
||||
$option,
|
||||
$parsedDetails->getDouble($option)
|
||||
);
|
||||
$productOption->save();
|
||||
}
|
||||
}
|
||||
$menuBoardProduct->productOptions = $menuBoardProduct->getOptions();
|
||||
$menuBoard->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => __('Added Menu Board Product'),
|
||||
'httpStatus' => 201,
|
||||
'id' => $menuBoardProduct->menuProductId,
|
||||
'data' => $menuBoardProduct
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param int $id
|
||||
* @return Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function editForm(Request $request, Response $response, $id): Response
|
||||
{
|
||||
$menuBoardProduct = $this->menuBoardCategoryFactory->getByProductId($id);
|
||||
$menuBoard = $this->menuBoardFactory->getById($menuBoardProduct->menuId);
|
||||
|
||||
if (!$this->getUser()->checkEditable($menuBoard)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'menuboard-product-form-edit';
|
||||
$this->getState()->setData([
|
||||
'menuBoardProduct' => $menuBoardProduct,
|
||||
'media' => $menuBoardProduct->mediaId != null ? $this->mediaFactory->getById($menuBoardProduct->mediaId) : null
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Put(
|
||||
* path="/menuboard/{menuProductId}/product",
|
||||
* operationId="menuBoardProductEdit",
|
||||
* tags={"menuBoard"},
|
||||
* summary="Edit Menu Board Product",
|
||||
* description="Edit existing Menu Board Product",
|
||||
* @SWG\Parameter(
|
||||
* name="menuProductId",
|
||||
* in="path",
|
||||
* description="The Menu Board Product ID to Edit",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="formData",
|
||||
* description="Menu Board Product name",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="description",
|
||||
* in="formData",
|
||||
* description="Menu Board Product description",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="price",
|
||||
* in="formData",
|
||||
* description="Menu Board Product price",
|
||||
* type="number",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="allergyInfo",
|
||||
* in="formData",
|
||||
* description="Menu Board Product allergyInfo",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="calories",
|
||||
* in="formData",
|
||||
* description="Menu Board Product calories",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="displayOrder",
|
||||
* in="formData",
|
||||
* description="Menu Board Product Display Order, used for sorting",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="availability",
|
||||
* in="formData",
|
||||
* description="Menu Board Product availability",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="mediaId",
|
||||
* in="formData",
|
||||
* description="Media ID from CMS Library to associate with this Menu Board Product",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="code",
|
||||
* in="formData",
|
||||
* description="Menu Board Product code",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="productOptions",
|
||||
* in="formData",
|
||||
* description="An array of optional Product Option names",
|
||||
* type="array",
|
||||
* required=false,
|
||||
* @SWG\Items(type="string")
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="productValues",
|
||||
* in="formData",
|
||||
* description="An array of optional Product Option values",
|
||||
* type="array",
|
||||
* required=false,
|
||||
* @SWG\Items(type="string")
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param int $id
|
||||
* @return Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function edit(Request $request, Response $response, $id): Response
|
||||
{
|
||||
$menuBoardProduct = $this->menuBoardCategoryFactory->getByProductId($id);
|
||||
$menuBoard = $this->menuBoardFactory->getById($menuBoardProduct->menuId);
|
||||
|
||||
if (!$this->getUser()->checkEditable($menuBoard)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$menuBoardProduct->name = $sanitizedParams->getString('name');
|
||||
$menuBoardProduct->description = $sanitizedParams->getString('description');
|
||||
$menuBoardProduct->price = $sanitizedParams->getDouble('price');
|
||||
$menuBoardProduct->allergyInfo = $sanitizedParams->getString('allergyInfo');
|
||||
$menuBoardProduct->calories = $sanitizedParams->getInt('calories');
|
||||
$menuBoardProduct->displayOrder = $sanitizedParams->getInt('displayOrder');
|
||||
$menuBoardProduct->availability = $sanitizedParams->getCheckbox('availability');
|
||||
$menuBoardProduct->mediaId = $sanitizedParams->getInt('mediaId');
|
||||
$menuBoardProduct->code = $sanitizedParams->getString('code');
|
||||
$productOptions = $sanitizedParams->getArray('productOptions', ['default' => []]);
|
||||
$productValues = $sanitizedParams->getArray('productValues', ['default' => []]);
|
||||
|
||||
if (!empty(array_filter($productOptions)) && !empty(array_filter($productValues))) {
|
||||
$productDetails = array_filter(array_combine($productOptions, $productValues));
|
||||
$parsedDetails = $this->getSanitizer($productDetails);
|
||||
if (count($menuBoardProduct->getOptions()) > count($productDetails)) {
|
||||
$menuBoardProduct->removeOptions();
|
||||
}
|
||||
|
||||
foreach ($productDetails as $option => $value) {
|
||||
$productOption = $this->menuBoardProductOptionFactory->create(
|
||||
$menuBoardProduct->menuProductId,
|
||||
$option,
|
||||
$parsedDetails->getDouble($option)
|
||||
);
|
||||
$productOption->save();
|
||||
}
|
||||
} else {
|
||||
$menuBoardProduct->removeOptions();
|
||||
}
|
||||
$menuBoardProduct->productOptions = $menuBoardProduct->getOptions();
|
||||
$menuBoardProduct->save();
|
||||
$menuBoard->save();
|
||||
|
||||
// Success
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 200,
|
||||
'message' => sprintf(__('Edited %s'), $menuBoardProduct->name),
|
||||
'id' => $menuBoardProduct->menuProductId,
|
||||
'data' => $menuBoardProduct
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param int $id
|
||||
* @return Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function deleteForm(Request $request, Response $response, $id): Response
|
||||
{
|
||||
$menuBoardProduct = $this->menuBoardCategoryFactory->getByProductId($id);
|
||||
$menuBoardCategory = $this->menuBoardCategoryFactory->getById($menuBoardProduct->menuCategoryId);
|
||||
$menuBoard = $this->menuBoardFactory->getById($menuBoardProduct->menuId);
|
||||
|
||||
if (!$this->getUser()->checkEditable($menuBoard)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'menuboard-product-form-delete';
|
||||
$this->getState()->setData([
|
||||
'menuBoard' => $menuBoard,
|
||||
'menuBoardCategory' => $menuBoardCategory,
|
||||
'menuBoardProduct' => $menuBoardProduct
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Delete(
|
||||
* path="/menuboard/{menuProductId}/product",
|
||||
* operationId="menuBoardProductDelete",
|
||||
* tags={"menuBoard"},
|
||||
* summary="Delete Menu Board",
|
||||
* description="Delete existing Menu Board Product",
|
||||
* @SWG\Parameter(
|
||||
* name="menuProductId",
|
||||
* in="path",
|
||||
* description="The Menu Board Product ID to Delete",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function delete(Request $request, Response $response, $id): Response
|
||||
{
|
||||
$menuBoardProduct = $this->menuBoardCategoryFactory->getByProductId($id);
|
||||
$menuBoard = $this->menuBoardFactory->getById($menuBoardProduct->menuId);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($menuBoard)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Issue the delete
|
||||
$menuBoardProduct->delete();
|
||||
|
||||
// Success
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Deleted %s'), $menuBoardProduct->name)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
464
lib/Controller/Module.php
Normal file
464
lib/Controller/Module.php
Normal file
@@ -0,0 +1,464 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\ModuleFactory;
|
||||
use Xibo\Factory\ModuleTemplateFactory;
|
||||
use Xibo\Storage\StorageServiceInterface;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\ControllerNotImplemented;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class Module
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class Module extends Base
|
||||
{
|
||||
/** @var ModuleFactory */
|
||||
private $moduleFactory;
|
||||
|
||||
/** @var \Xibo\Factory\ModuleTemplateFactory */
|
||||
private $moduleTemplateFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param StorageServiceInterface $store
|
||||
* @param ModuleFactory $moduleFactory
|
||||
*/
|
||||
public function __construct(
|
||||
ModuleFactory $moduleFactory,
|
||||
ModuleTemplateFactory $moduleTemplateFactory
|
||||
) {
|
||||
$this->moduleFactory = $moduleFactory;
|
||||
$this->moduleTemplateFactory = $moduleTemplateFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the module page
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'module-page';
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/module",
|
||||
* operationId="moduleSearch",
|
||||
* tags={"module"},
|
||||
* summary="Module Search",
|
||||
* description="Get a list of all modules available to this CMS",
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/Module")
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function grid(Request $request, Response $response)
|
||||
{
|
||||
$parsedQueryParams = $this->getSanitizer($request->getQueryParams());
|
||||
|
||||
$filter = [
|
||||
'name' => $parsedQueryParams->getString('name'),
|
||||
'extension' => $parsedQueryParams->getString('extension'),
|
||||
'moduleId' => $parsedQueryParams->getInt('moduleId')
|
||||
];
|
||||
|
||||
$modules = $this->moduleFactory->getAllExceptCanvas($filter);
|
||||
|
||||
foreach ($modules as $module) {
|
||||
/* @var \Xibo\Entity\Module $module */
|
||||
|
||||
if ($this->isApi($request)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$module->includeProperty('buttons');
|
||||
|
||||
// Edit button
|
||||
$module->buttons[] = [
|
||||
'id' => 'module_button_edit',
|
||||
'url' => $this->urlFor($request, 'module.settings.form', ['id' => $module->moduleId]),
|
||||
'text' => __('Configure')
|
||||
];
|
||||
|
||||
// Clear cache
|
||||
if ($module->regionSpecific == 1) {
|
||||
$module->buttons[] = [
|
||||
'id' => 'module_button_clear_cache',
|
||||
'url' => $this->urlFor($request, 'module.clear.cache.form', ['id' => $module->moduleId]),
|
||||
'text' => __('Clear Cache'),
|
||||
'dataAttributes' => [
|
||||
['name' => 'auto-submit', 'value' => true],
|
||||
[
|
||||
'name' => 'commit-url',
|
||||
'value' => $this->urlFor($request, 'module.clear.cache', ['id' => $module->moduleId])
|
||||
],
|
||||
['name' => 'commit-method', 'value' => 'PUT']
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = 0;
|
||||
$this->getState()->setData($modules);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/module/properties/{id}",
|
||||
* operationId="getModuleProperties",
|
||||
* tags={"module"},
|
||||
* summary="Get Module Properties",
|
||||
* description="Get a module properties which are needed to for the editWidget call",
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The ModuleId",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/Property")
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
// phpcs:enable
|
||||
public function getProperties(Request $request, Response $response, $id)
|
||||
{
|
||||
// Get properties, but return a key->value object for easy parsing.
|
||||
$props = [];
|
||||
foreach ($this->moduleFactory->getById($id)->properties as $property) {
|
||||
$props[$property->id] = [
|
||||
'type' => $property->type,
|
||||
'title' => $property->title,
|
||||
'helpText' => $property->helpText,
|
||||
'options' => $property->options,
|
||||
];
|
||||
}
|
||||
|
||||
$this->getState()->setData($props);
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function settingsForm(Request $request, Response $response, $id)
|
||||
{
|
||||
// Can we edit?
|
||||
if (!$this->getUser()->userTypeId == 1) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$module = $this->moduleFactory->getById($id);
|
||||
|
||||
// Pass to view
|
||||
$this->getState()->template = 'module-form-settings';
|
||||
$this->getState()->setData([
|
||||
'moduleId' => $id,
|
||||
'module' => $module,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function settings(Request $request, Response $response, $id)
|
||||
{
|
||||
if (!$this->getUser()->isSuperAdmin()) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
// Get the module
|
||||
$module = $this->moduleFactory->getById($id);
|
||||
|
||||
// Default settings
|
||||
$module->enabled = $sanitizedParams->getCheckbox('enabled');
|
||||
$module->previewEnabled = $sanitizedParams->getCheckbox('previewEnabled');
|
||||
$module->defaultDuration = $sanitizedParams->getInt('defaultDuration');
|
||||
|
||||
// Parse out any settings we ought to expect.
|
||||
foreach ($module->settings as $setting) {
|
||||
$setting->setValueByType($sanitizedParams, null, true);
|
||||
}
|
||||
|
||||
// Preview is not allowed for generic file type
|
||||
if ($module->allowPreview === 0 && $sanitizedParams->getCheckbox('previewEnabled') == 1) {
|
||||
throw new InvalidArgumentException(__('Preview is disabled'));
|
||||
}
|
||||
|
||||
// Save
|
||||
$module->save();
|
||||
|
||||
// Successful
|
||||
$this->getState()->hydrate([
|
||||
'message' => sprintf(__('Configured %s'), $module->name),
|
||||
'id' => $module->moduleId,
|
||||
'data' => $module
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear Cache Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function clearCacheForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$module = $this->moduleFactory->getById($id);
|
||||
|
||||
$this->getState()->template = 'module-form-clear-cache';
|
||||
$this->getState()->autoSubmit = $this->getAutoSubmit('clearCache');
|
||||
$this->getState()->setData([
|
||||
'module' => $module,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear Cache
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function clearCache(Request $request, Response $response, $id)
|
||||
{
|
||||
$module = $this->moduleFactory->getById($id);
|
||||
if ($module->isDataProviderExpected()) {
|
||||
$this->moduleFactory->clearCacheForDataType($module->dataType);
|
||||
}
|
||||
|
||||
$this->getState()->hydrate([
|
||||
'message' => __('Cleared the Cache')
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/module/templates/{dataType}",
|
||||
* operationId="moduleTemplateSearch",
|
||||
* tags={"module"},
|
||||
* summary="Module Template Search",
|
||||
* description="Get a list of templates available for a particular data type",
|
||||
* @SWG\Parameter(
|
||||
* name="dataType",
|
||||
* in="path",
|
||||
* description="DataType to return templates for",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="type",
|
||||
* in="query",
|
||||
* description="Type to return templates for",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="An array of module templates for the provided datatype",
|
||||
* @SWG\Schema(ref="#/definitions/ModuleTemplate")
|
||||
* )
|
||||
* )
|
||||
* @param \Slim\Http\ServerRequest $request
|
||||
* @param \Slim\Http\Response $response
|
||||
* @param string $dataType
|
||||
* @return \Slim\Http\Response
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function templateGrid(Request $request, Response $response, string $dataType): Response
|
||||
{
|
||||
if (empty($dataType)) {
|
||||
throw new InvalidArgumentException(__('Please provide a datatype'), 'dataType');
|
||||
}
|
||||
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
$type = $params->getString('type');
|
||||
|
||||
$templates = !empty($type)
|
||||
? $this->moduleTemplateFactory->getByTypeAndDataType($type, $dataType)
|
||||
: $this->moduleTemplateFactory->getByDataType($dataType);
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = 0;
|
||||
$this->getState()->setData($templates);
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/module/template/{dataType}/properties/{id}",
|
||||
* operationId="getModuleProperties",
|
||||
* tags={"module"},
|
||||
* summary="Get Module Template Properties",
|
||||
* description="Get a module template properties which are needed to for the editWidget call",
|
||||
* @SWG\Parameter(
|
||||
* name="dataType",
|
||||
* in="path",
|
||||
* description="The Template DataType",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Template Id",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* type="object",
|
||||
* additionalProperties={"id":"string", "type":"string", "title":"string", "helpText":"string", "options":"array"}
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param string $dataType
|
||||
* @param string $id
|
||||
* @return ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws ControllerNotImplemented
|
||||
*/
|
||||
// phpcs:enable
|
||||
public function getTemplateProperties(Request $request, Response $response, string $dataType, string $id)
|
||||
{
|
||||
// Get properties, but return a key->value object for easy parsing.
|
||||
$props = [];
|
||||
foreach ($this->moduleTemplateFactory->getByDataTypeAndId($dataType, $id)->properties as $property) {
|
||||
$props[$property->id] = [
|
||||
'id' => $property->id,
|
||||
'type' => $property->type,
|
||||
'title' => $property->title,
|
||||
'helpText' => $property->helpText,
|
||||
'options' => $property->options,
|
||||
];
|
||||
}
|
||||
|
||||
$this->getState()->setData($props);
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve an asset
|
||||
* @param \Slim\Http\ServerRequest $request
|
||||
* @param \Slim\Http\Response $response
|
||||
* @param string $assetId the ID of the asset to serve
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function assetDownload(Request $request, Response $response, string $assetId): Response
|
||||
{
|
||||
if (empty($assetId)) {
|
||||
throw new InvalidArgumentException(__('Please provide an assetId'), 'assetId');
|
||||
}
|
||||
|
||||
// Get this asset from somewhere
|
||||
$asset = $this->moduleFactory->getAssetsFromAnywhereById(
|
||||
$assetId,
|
||||
$this->moduleTemplateFactory,
|
||||
$this->getSanitizer($request->getParams())->getCheckbox('isAlias')
|
||||
);
|
||||
$asset->updateAssetCache($this->getConfig()->getSetting('LIBRARY_LOCATION'));
|
||||
|
||||
$this->getLog()->debug('assetDownload: found appropriate asset for assetId ' . $assetId);
|
||||
|
||||
// The asset can serve itself.
|
||||
return $asset->psrResponse($request, $response, $this->getConfig()->getSetting('SENDFILE_MODE'));
|
||||
}
|
||||
}
|
||||
850
lib/Controller/Notification.php
Normal file
850
lib/Controller/Notification.php
Normal file
@@ -0,0 +1,850 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Entity\UserGroup;
|
||||
use Xibo\Factory\DisplayGroupFactory;
|
||||
use Xibo\Factory\NotificationFactory;
|
||||
use Xibo\Factory\UserGroupFactory;
|
||||
use Xibo\Factory\UserNotificationFactory;
|
||||
use Xibo\Helper\AttachmentUploadHandler;
|
||||
use Xibo\Helper\DateFormatHelper;
|
||||
use Xibo\Helper\SendFile;
|
||||
use Xibo\Service\DisplayNotifyService;
|
||||
use Xibo\Service\MediaService;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\ConfigurationException;
|
||||
|
||||
/**
|
||||
* Class Notification
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class Notification extends Base
|
||||
{
|
||||
/** @var NotificationFactory */
|
||||
private $notificationFactory;
|
||||
|
||||
/** @var UserNotificationFactory */
|
||||
private $userNotificationFactory;
|
||||
|
||||
/** @var DisplayGroupFactory */
|
||||
private $displayGroupFactory;
|
||||
|
||||
/** @var UserGroupFactory */
|
||||
private $userGroupFactory;
|
||||
|
||||
/** @var DisplayNotifyService */
|
||||
private $displayNotifyService;
|
||||
|
||||
/**
|
||||
* Notification constructor.
|
||||
* @param NotificationFactory $notificationFactory
|
||||
* @param UserNotificationFactory $userNotificationFactory
|
||||
* @param DisplayGroupFactory $displayGroupFactory
|
||||
* @param UserGroupFactory $userGroupFactory
|
||||
* @param DisplayNotifyService $displayNotifyService
|
||||
*/
|
||||
public function __construct(
|
||||
$notificationFactory,
|
||||
$userNotificationFactory,
|
||||
$displayGroupFactory,
|
||||
$userGroupFactory,
|
||||
$displayNotifyService
|
||||
) {
|
||||
$this->notificationFactory = $notificationFactory;
|
||||
$this->userNotificationFactory = $userNotificationFactory;
|
||||
$this->displayGroupFactory = $displayGroupFactory;
|
||||
$this->userGroupFactory = $userGroupFactory;
|
||||
$this->displayNotifyService = $displayNotifyService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response)
|
||||
{
|
||||
// Call to render the template
|
||||
$this->getState()->template = 'notification-page';
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a notification
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function interrupt(Request $request, Response $response, $id)
|
||||
{
|
||||
$notification = $this->userNotificationFactory->getByNotificationId($id);
|
||||
|
||||
// Mark it as read
|
||||
$notification->setRead(Carbon::now()->format('U'));
|
||||
$notification->save();
|
||||
|
||||
$this->getState()->template = 'notification-interrupt';
|
||||
$this->getState()->setData(['notification' => $notification]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a notification
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function show(Request $request, Response $response, $id)
|
||||
{
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
$notification = $this->userNotificationFactory->getByNotificationId($id);
|
||||
|
||||
// Mark it as read
|
||||
$notification->setRead(Carbon::now()->format('U'));
|
||||
$notification->save();
|
||||
|
||||
if ($params->getCheckbox('multiSelect')) {
|
||||
return $response->withStatus(201);
|
||||
} else {
|
||||
$this->getState()->template = 'notification-form-show';
|
||||
$this->getState()->setData(['notification' => $notification]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/notification",
|
||||
* operationId="notificationSearch",
|
||||
* tags={"notification"},
|
||||
* summary="Notification Search",
|
||||
* description="Search this users Notifications",
|
||||
* @SWG\Parameter(
|
||||
* name="notificationId",
|
||||
* in="query",
|
||||
* description="Filter by Notification Id",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="subject",
|
||||
* in="query",
|
||||
* description="Filter by Subject",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="embed",
|
||||
* in="query",
|
||||
* description="Embed related data such as userGroups,displayGroups",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/Notification")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function grid(Request $request, Response $response): Response|\Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
$sanitizedQueryParams = $this->getSanitizer($request->getQueryParams());
|
||||
|
||||
$filter = [
|
||||
'notificationId' => $sanitizedQueryParams->getInt('notificationId'),
|
||||
'subject' => $sanitizedQueryParams->getString('subject'),
|
||||
'read' => $sanitizedQueryParams->getInt('read'),
|
||||
'releaseDt' => $sanitizedQueryParams->getDate('releaseDt')?->format('U'),
|
||||
'type' => $sanitizedQueryParams->getString('type'),
|
||||
];
|
||||
$embed = ($sanitizedQueryParams->getString('embed') != null)
|
||||
? explode(',', $sanitizedQueryParams->getString('embed'))
|
||||
: [];
|
||||
|
||||
$notifications = $this->notificationFactory->query(
|
||||
$this->gridRenderSort($sanitizedQueryParams),
|
||||
$this->gridRenderFilter($filter, $sanitizedQueryParams)
|
||||
);
|
||||
|
||||
foreach ($notifications as $notification) {
|
||||
if (in_array('userGroups', $embed) || in_array('displayGroups', $embed)) {
|
||||
$notification->load([
|
||||
'loadUserGroups' => in_array('userGroups', $embed),
|
||||
'loadDisplayGroups' => in_array('displayGroups', $embed),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->isApi($request)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$notification->includeProperty('buttons');
|
||||
|
||||
// View Notification
|
||||
$notification->buttons[] = [
|
||||
'id' => 'notification_button_view',
|
||||
'url' => $this->urlFor(
|
||||
$request,
|
||||
'notification.show',
|
||||
['id' => $notification->notificationId]
|
||||
),
|
||||
'text' => __('View'),
|
||||
'multi-select' => true,
|
||||
'dataAttributes' => [
|
||||
[
|
||||
'name' => 'commit-url',
|
||||
'value' => $this->urlFor(
|
||||
$request,
|
||||
'notification.show',
|
||||
['id' => $notification->notificationId, 'multiSelect' => true]
|
||||
),
|
||||
],
|
||||
['name' => 'commit-method', 'value' => 'get'],
|
||||
['name' => 'id', 'value' => 'notification_button_view'],
|
||||
['name' => 'text', 'value' => __('Mark as read?')],
|
||||
['name' => 'sort-group', 'value' => 1],
|
||||
['name' => 'rowtitle', 'value' => $notification->subject]
|
||||
]
|
||||
];
|
||||
|
||||
|
||||
// Edit Notification
|
||||
if ($this->getUser()->checkEditable($notification) &&
|
||||
$this->getUser()->featureEnabled('notification.modify')
|
||||
) {
|
||||
$notification->buttons[] = [
|
||||
'id' => 'notification_button_edit',
|
||||
'url' => $this->urlFor(
|
||||
$request,
|
||||
'notification.edit.form',
|
||||
['id' => $notification->notificationId]
|
||||
),
|
||||
'text' => __('Edit')
|
||||
];
|
||||
}
|
||||
|
||||
// Delete Notifications
|
||||
if ($this->getUser()->checkDeleteable($notification) &&
|
||||
$this->getUser()->featureEnabled('notification.modify')
|
||||
) {
|
||||
$notification->buttons[] = ['divider' => true];
|
||||
|
||||
$notification->buttons[] = [
|
||||
'id' => 'notification_button_delete',
|
||||
'url' => $this->urlFor(
|
||||
$request,
|
||||
'notification.delete.form',
|
||||
['id' => $notification->notificationId]
|
||||
),
|
||||
'text' => __('Delete'),
|
||||
'multi-select' => true,
|
||||
'dataAttributes' => [
|
||||
[
|
||||
'name' => 'commit-url',
|
||||
'value' => $this->urlFor(
|
||||
$request,
|
||||
'notification.delete',
|
||||
['id' => $notification->notificationId]
|
||||
)
|
||||
],
|
||||
['name' => 'commit-method', 'value' => 'delete'],
|
||||
['name' => 'id', 'value' => 'notification_button_delete'],
|
||||
['name' => 'text', 'value' => __('Delete?')],
|
||||
['name' => 'sort-group', 'value' => 2],
|
||||
['name' => 'rowtitle', 'value' => $notification->subject]
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->notificationFactory->countLast();
|
||||
$this->getState()->setData($notifications);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Notification Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function addForm(Request $request, Response $response)
|
||||
{
|
||||
$groups = [];
|
||||
$displays = [];
|
||||
$userGroups = [];
|
||||
$users = [];
|
||||
|
||||
foreach ($this->displayGroupFactory->query(['displayGroup'], ['isDisplaySpecific' => -1]) as $displayGroup) {
|
||||
/* @var \Xibo\Entity\DisplayGroup $displayGroup */
|
||||
|
||||
if ($displayGroup->isDisplaySpecific == 1) {
|
||||
$displays[] = $displayGroup;
|
||||
} else {
|
||||
$groups[] = $displayGroup;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->userGroupFactory->query(['`group`'], ['isUserSpecific' => -1]) as $userGroup) {
|
||||
/* @var UserGroup $userGroup */
|
||||
|
||||
if ($userGroup->isUserSpecific == 0) {
|
||||
$userGroups[] = $userGroup;
|
||||
} else {
|
||||
$users[] = $userGroup;
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'notification-form-add';
|
||||
$this->getState()->setData([
|
||||
'displays' => $displays,
|
||||
'displayGroups' => $groups,
|
||||
'users' => $users,
|
||||
'userGroups' => $userGroups,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Notification Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function editForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$notification = $this->notificationFactory->getById($id);
|
||||
$notification->load();
|
||||
|
||||
// Adjust the dates
|
||||
$notification->createDt = Carbon::createFromTimestamp($notification->createDt)
|
||||
->format(DateFormatHelper::getSystemFormat());
|
||||
$notification->releaseDt = Carbon::createFromTimestamp($notification->releaseDt)
|
||||
->format(DateFormatHelper::getSystemFormat());
|
||||
|
||||
if (!$this->getUser()->checkEditable($notification)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$groups = [];
|
||||
$displays = [];
|
||||
$userGroups = [];
|
||||
$users = [];
|
||||
|
||||
foreach ($this->displayGroupFactory->query(['displayGroup'], ['isDisplaySpecific' => -1]) as $displayGroup) {
|
||||
/* @var \Xibo\Entity\DisplayGroup $displayGroup */
|
||||
|
||||
if ($displayGroup->isDisplaySpecific == 1) {
|
||||
$displays[] = $displayGroup;
|
||||
} else {
|
||||
$groups[] = $displayGroup;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->userGroupFactory->query(['`group`'], ['isUserSpecific' => -1]) as $userGroup) {
|
||||
/* @var UserGroup $userGroup */
|
||||
|
||||
if ($userGroup->isUserSpecific == 0) {
|
||||
$userGroups[] = $userGroup;
|
||||
} else {
|
||||
$users[] = $userGroup;
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'notification-form-edit';
|
||||
$this->getState()->setData([
|
||||
'notification' => $notification,
|
||||
'displays' => $displays,
|
||||
'displayGroups' => $groups,
|
||||
'users' => $users,
|
||||
'userGroups' => $userGroups,
|
||||
'displayGroupIds' => array_map(function ($element) {
|
||||
return $element->displayGroupId;
|
||||
}, $notification->displayGroups),
|
||||
'userGroupIds' => array_map(function ($element) {
|
||||
return $element->groupId;
|
||||
}, $notification->userGroups)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Notification Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function deleteForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$notification = $this->notificationFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($notification)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'notification-form-delete';
|
||||
$this->getState()->setData([
|
||||
'notification' => $notification
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add attachment
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|\Slim\Http\Response
|
||||
* @throws \Xibo\Support\Exception\ConfigurationException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function addAttachment(Request $request, Response $response)
|
||||
{
|
||||
$libraryFolder = $this->getConfig()->getSetting('LIBRARY_LOCATION');
|
||||
|
||||
// Make sure the library exists
|
||||
MediaService::ensureLibraryExists($this->getConfig()->getSetting('LIBRARY_LOCATION'));
|
||||
|
||||
$options = [
|
||||
'userId' => $this->getUser()->userId,
|
||||
'controller' => $this,
|
||||
'accept_file_types' => '/\.jpg|.jpeg|.png|.bmp|.gif|.zip|.pdf/i'
|
||||
];
|
||||
|
||||
// Output handled by UploadHandler
|
||||
$this->setNoOutput(true);
|
||||
|
||||
$this->getLog()->debug('Hand off to Upload Handler with options: ' . json_encode($options));
|
||||
|
||||
// Hand off to the Upload Handler provided by jquery-file-upload
|
||||
new AttachmentUploadHandler($libraryFolder . 'temp/', $this->getLog()->getLoggerInterface(), $options);
|
||||
|
||||
// Explicitly set the Content-Type header to application/json
|
||||
$response = $response->withHeader('Content-Type', 'application/json');
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Notification
|
||||
*
|
||||
* @SWG\Post(
|
||||
* path="/notification",
|
||||
* operationId="notificationAdd",
|
||||
* tags={"notification"},
|
||||
* summary="Notification Add",
|
||||
* description="Add a Notification",
|
||||
* @SWG\Parameter(
|
||||
* name="subject",
|
||||
* in="formData",
|
||||
* description="The Subject",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="body",
|
||||
* in="formData",
|
||||
* description="The Body",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="releaseDt",
|
||||
* in="formData",
|
||||
* description="ISO date representing the release date for this notification",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="isInterrupt",
|
||||
* in="formData",
|
||||
* description="Flag indication whether this notification should interrupt the web portal nativation/login",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="displayGroupIds",
|
||||
* in="formData",
|
||||
* description="The display group ids to assign this notification to",
|
||||
* type="array",
|
||||
* required=true,
|
||||
* @SWG\Items(type="integer")
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="userGroupIds",
|
||||
* in="formData",
|
||||
* description="The user group ids to assign to this notification",
|
||||
* type="array",
|
||||
* required=true,
|
||||
* @SWG\Items(type="integer")
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/Notification"),
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new record",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws ConfigurationException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function add(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$notification = $this->notificationFactory->createEmpty();
|
||||
$notification->subject = $sanitizedParams->getString('subject');
|
||||
$notification->body = $request->getParam('body', '');
|
||||
$notification->createDt = Carbon::now()->format('U');
|
||||
$notification->releaseDt = $sanitizedParams->getDate('releaseDt');
|
||||
|
||||
if ($notification->releaseDt !== null) {
|
||||
$notification->releaseDt = $notification->releaseDt->format('U');
|
||||
} else {
|
||||
$notification->releaseDt = $notification->createDt;
|
||||
}
|
||||
|
||||
$notification->isInterrupt = $sanitizedParams->getCheckbox('isInterrupt');
|
||||
$notification->userId = $this->getUser()->userId;
|
||||
$notification->nonusers = $sanitizedParams->getString('nonusers');
|
||||
$notification->type = 'custom';
|
||||
|
||||
// Displays and Users to link
|
||||
foreach ($sanitizedParams->getIntArray('displayGroupIds', ['default' => [] ]) as $displayGroupId) {
|
||||
$notification->assignDisplayGroup($this->displayGroupFactory->getById($displayGroupId));
|
||||
|
||||
// Notify (don't collect)
|
||||
$this->displayNotifyService->collectLater()->notifyByDisplayGroupId($displayGroupId);
|
||||
}
|
||||
|
||||
foreach ($sanitizedParams->getIntArray('userGroupIds', ['default' => [] ]) as $userGroupId) {
|
||||
$notification->assignUserGroup($this->userGroupFactory->getById($userGroupId));
|
||||
}
|
||||
|
||||
$notification->save();
|
||||
|
||||
$attachedFilename = $sanitizedParams->getString('attachedFilename', ['defaultOnEmptyString' => true]);
|
||||
$libraryFolder = $this->getConfig()->getSetting('LIBRARY_LOCATION');
|
||||
|
||||
if (!empty($attachedFilename)) {
|
||||
$saveName = $notification->notificationId .'_' .$attachedFilename;
|
||||
$notification->filename = $saveName;
|
||||
$notification->originalFileName = $attachedFilename;
|
||||
// Move the file into the library
|
||||
// Try to move the file first
|
||||
$from = $libraryFolder . 'temp/' . $attachedFilename;
|
||||
$to = $libraryFolder . 'attachment/' . $saveName;
|
||||
|
||||
$moved = rename($from, $to);
|
||||
|
||||
if (!$moved) {
|
||||
$this->getLog()->info(
|
||||
'Cannot move file: ' . $from . ' to ' . $to . ', will try and copy/delete instead.'
|
||||
);
|
||||
|
||||
// Copy
|
||||
$moved = copy($from, $to);
|
||||
|
||||
// Delete
|
||||
if (!@unlink($from)) {
|
||||
$this->getLog()->error('Cannot delete file: ' . $from . ' after copying to ' . $to);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$moved) {
|
||||
throw new ConfigurationException(__('Problem moving uploaded file into the Attachment Folder'));
|
||||
}
|
||||
|
||||
$notification->save();
|
||||
}
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => sprintf(__('Added %s'), $notification->subject),
|
||||
'id' => $notification->notificationId,
|
||||
'data' => $notification
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Notification
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
* @SWG\Put(
|
||||
* path="/notification/{notificationId}",
|
||||
* operationId="notificationEdit",
|
||||
* tags={"notification"},
|
||||
* summary="Notification Edit",
|
||||
* description="Edit a Notification",
|
||||
* @SWG\Parameter(
|
||||
* name="notificationId",
|
||||
* in="path",
|
||||
* description="The NotificationId",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="subject",
|
||||
* in="formData",
|
||||
* description="The Subject",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="body",
|
||||
* in="formData",
|
||||
* description="The Body",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="releaseDt",
|
||||
* in="formData",
|
||||
* description="ISO date representing the release date for this notification",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="isInterrupt",
|
||||
* in="formData",
|
||||
* description="Flag indication whether this notification should interrupt the web portal nativation/login",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="displayGroupIds",
|
||||
* in="formData",
|
||||
* description="The display group ids to assign this notification to",
|
||||
* type="array",
|
||||
* required=true,
|
||||
* @SWG\Items(type="integer")
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="userGroupIds",
|
||||
* in="formData",
|
||||
* description="The user group ids to assign to this notification",
|
||||
* type="array",
|
||||
* required=true,
|
||||
* @SWG\Items(type="integer")
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/Notification")
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function edit(Request $request, Response $response, $id)
|
||||
{
|
||||
$notification = $this->notificationFactory->getById($id);
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
$notification->load();
|
||||
|
||||
// Check Permissions
|
||||
if (!$this->getUser()->checkEditable($notification)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$notification->subject = $sanitizedParams->getString('subject');
|
||||
$notification->body = $request->getParam('body', '');
|
||||
$notification->createDt = Carbon::now()->format('U');
|
||||
$notification->releaseDt = $sanitizedParams->getDate('releaseDt')->format('U');
|
||||
$notification->isInterrupt = $sanitizedParams->getCheckbox('isInterrupt');
|
||||
$notification->userId = $this->getUser()->userId;
|
||||
$notification->nonusers = $sanitizedParams->getString('nonusers');
|
||||
|
||||
// Clear existing assignments
|
||||
$notification->displayGroups = [];
|
||||
$notification->userGroups = [];
|
||||
|
||||
// Displays and Users to link
|
||||
foreach ($sanitizedParams->getIntArray('displayGroupIds', ['default' => []]) as $displayGroupId) {
|
||||
$notification->assignDisplayGroup($this->displayGroupFactory->getById($displayGroupId));
|
||||
|
||||
// Notify (don't collect)
|
||||
$this->displayNotifyService->collectLater()->notifyByDisplayGroupId($displayGroupId);
|
||||
}
|
||||
|
||||
foreach ($sanitizedParams->getIntArray('userGroupIds', ['default' => []]) as $userGroupId) {
|
||||
$notification->assignUserGroup($this->userGroupFactory->getById($userGroupId));
|
||||
}
|
||||
|
||||
$notification->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => sprintf(__('Edited %s'), $notification->subject),
|
||||
'id' => $notification->notificationId,
|
||||
'data' => $notification
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Notification
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
* @SWG\Delete(
|
||||
* path="/notification/{notificationId}",
|
||||
* operationId="notificationDelete",
|
||||
* tags={"notification"},
|
||||
* summary="Delete Notification",
|
||||
* description="Delete the provided notification",
|
||||
* @SWG\Parameter(
|
||||
* name="notificationId",
|
||||
* in="path",
|
||||
* description="The Notification Id to Delete",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function delete(Request $request, Response $response, $id)
|
||||
{
|
||||
$notification = $this->notificationFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($notification)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$notification->delete();
|
||||
|
||||
/*Delete the attachment*/
|
||||
if (!empty($notification->filename)) {
|
||||
// Library location
|
||||
$attachmentLocation = $this->getConfig()->getSetting('LIBRARY_LOCATION'). 'attachment/';
|
||||
if (file_exists($attachmentLocation . $notification->filename)) {
|
||||
unlink($attachmentLocation . $notification->filename);
|
||||
}
|
||||
}
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Deleted %s'), $notification->subject)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function exportAttachment(Request $request, Response $response, $id)
|
||||
{
|
||||
$notification = $this->notificationFactory->getById($id);
|
||||
|
||||
$fileName = $this->getConfig()->getSetting('LIBRARY_LOCATION') . 'attachment/' . $notification->filename;
|
||||
|
||||
// Return the file with PHP
|
||||
$this->setNoOutput(true);
|
||||
|
||||
return $this->render($request, SendFile::decorateResponse(
|
||||
$response,
|
||||
$this->getConfig()->getSetting('SENDFILE_MODE'),
|
||||
$fileName
|
||||
));
|
||||
}
|
||||
}
|
||||
73
lib/Controller/PlayerFault.php
Normal file
73
lib/Controller/PlayerFault.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2021 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\PlayerFaultFactory;
|
||||
|
||||
class PlayerFault extends Base
|
||||
{
|
||||
/** @var PlayerFaultFactory */
|
||||
private $playerFaultFactory;
|
||||
|
||||
/**
|
||||
* PlayerFault constructor.
|
||||
* @param PlayerFaultFactory $playerFaultFactory
|
||||
*/
|
||||
public function __construct(PlayerFaultFactory $playerFaultFactory)
|
||||
{
|
||||
$this->playerFaultFactory = $playerFaultFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param int $displayId
|
||||
* @return Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function grid(Request $request, Response $response, int $displayId) : Response
|
||||
{
|
||||
$parsedParams = $this->getSanitizer($request->getQueryParams());
|
||||
|
||||
if ($displayId != null) {
|
||||
$playerFaults = $this->playerFaultFactory->getByDisplayId($displayId, $this->gridRenderSort($parsedParams));
|
||||
} else {
|
||||
$filter = [
|
||||
'code' => $parsedParams->getInt('code'),
|
||||
'incidentDt' => $parsedParams->getDate('incidentDt'),
|
||||
'displayId' => $parsedParams->getInt('displayId')
|
||||
];
|
||||
|
||||
$playerFaults = $this->playerFaultFactory->query($this->gridRenderSort($parsedParams), $this->gridRenderFilter($filter, $parsedParams));
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->playerFaultFactory->countLast();
|
||||
$this->getState()->setData($playerFaults);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
752
lib/Controller/PlayerSoftware.php
Normal file
752
lib/Controller/PlayerSoftware.php
Normal file
@@ -0,0 +1,752 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2025 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\DisplayFactory;
|
||||
use Xibo\Factory\DisplayProfileFactory;
|
||||
use Xibo\Factory\MediaFactory;
|
||||
use Xibo\Factory\ModuleFactory;
|
||||
use Xibo\Factory\PlayerVersionFactory;
|
||||
use Xibo\Helper\ByteFormatter;
|
||||
use Xibo\Service\DownloadService;
|
||||
use Xibo\Service\MediaService;
|
||||
use Xibo\Service\MediaServiceInterface;
|
||||
use Xibo\Service\UploadService;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\ConfigurationException;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class PlayerSoftware
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class PlayerSoftware extends Base
|
||||
{
|
||||
/** @var \Stash\Interfaces\PoolInterface */
|
||||
private $pool;
|
||||
|
||||
/** @var DisplayProfileFactory */
|
||||
private $displayProfileFactory;
|
||||
|
||||
/** @var PlayerVersionFactory */
|
||||
private $playerVersionFactory;
|
||||
|
||||
/** @var DisplayFactory */
|
||||
private $displayFactory;
|
||||
/**
|
||||
* @var MediaServiceInterface
|
||||
*/
|
||||
private $mediaService;
|
||||
|
||||
/**
|
||||
* Notification constructor.
|
||||
* @param MediaFactory $mediaFactory
|
||||
* @param PlayerVersionFactory $playerVersionFactory
|
||||
* @param DisplayProfileFactory $displayProfileFactory
|
||||
* @param ModuleFactory $moduleFactory
|
||||
* @param DisplayFactory $displayFactory
|
||||
*/
|
||||
public function __construct($pool, $playerVersionFactory, $displayProfileFactory, $displayFactory)
|
||||
{
|
||||
$this->pool = $pool;
|
||||
$this->playerVersionFactory = $playerVersionFactory;
|
||||
$this->displayProfileFactory = $displayProfileFactory;
|
||||
$this->displayFactory = $displayFactory;
|
||||
}
|
||||
|
||||
public function getPlayerVersionFactory() : PlayerVersionFactory
|
||||
{
|
||||
return $this->playerVersionFactory;
|
||||
}
|
||||
|
||||
public function useMediaService(MediaServiceInterface $mediaService)
|
||||
{
|
||||
$this->mediaService = $mediaService;
|
||||
}
|
||||
|
||||
public function getMediaService(): MediaServiceInterface
|
||||
{
|
||||
return $this->mediaService->setUser($this->getUser());
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the page logic
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
function displayPage(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'playersoftware-page';
|
||||
$this->getState()->setData([
|
||||
'types' => array_map(function ($element) {
|
||||
return $element->jsonSerialize();
|
||||
}, $this->playerVersionFactory->getDistinctType()),
|
||||
'versions' => $this->playerVersionFactory->getDistinctVersion(),
|
||||
'validExt' => implode('|', $this->getValidExtensions()),
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
function grid(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedQueryParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$filter = [
|
||||
'playerType' => $sanitizedQueryParams->getString('playerType'),
|
||||
'playerVersion' => $sanitizedQueryParams->getString('playerVersion'),
|
||||
'playerCode' => $sanitizedQueryParams->getInt('playerCode'),
|
||||
'versionId' => $sanitizedQueryParams->getInt('versionId'),
|
||||
'useRegexForName' => $sanitizedQueryParams->getCheckbox('useRegexForName'),
|
||||
'playerShowVersion' => $sanitizedQueryParams->getString('playerShowVersion')
|
||||
];
|
||||
|
||||
$versions = $this->playerVersionFactory->query($this->gridRenderSort($sanitizedQueryParams), $this->gridRenderFilter($filter, $sanitizedQueryParams));
|
||||
|
||||
// add row buttons
|
||||
foreach ($versions as $version) {
|
||||
$version->setUnmatchedProperty('fileSizeFormatted', ByteFormatter::format($version->size));
|
||||
if ($this->isApi($request)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$version->includeProperty('buttons');
|
||||
$version->buttons = [];
|
||||
|
||||
// Buttons
|
||||
|
||||
// Edit
|
||||
$version->buttons[] = [
|
||||
'id' => 'content_button_edit',
|
||||
'url' => $this->urlFor($request, 'playersoftware.edit.form', ['id' => $version->versionId]),
|
||||
'text' => __('Edit')
|
||||
];
|
||||
|
||||
// Delete Button
|
||||
$version->buttons[] = [
|
||||
'id' => 'content_button_delete',
|
||||
'url' => $this->urlFor($request, 'playersoftware.delete.form', ['id' => $version->versionId]),
|
||||
'text' => __('Delete'),
|
||||
'multi-select' => true,
|
||||
'dataAttributes' => [
|
||||
[
|
||||
'name' => 'commit-url',
|
||||
'value' => $this->urlFor($request, 'playersoftware.delete', ['id' => $version->versionId])
|
||||
],
|
||||
['name' => 'commit-method', 'value' => 'delete'],
|
||||
['name' => 'id', 'value' => 'content_button_delete'],
|
||||
['name' => 'text', 'value' => __('Delete')],
|
||||
['name' => 'sort-group', 'value' => 1],
|
||||
['name' => 'rowtitle', 'value' => $version->fileName]
|
||||
]
|
||||
];
|
||||
|
||||
|
||||
// Download
|
||||
$version->buttons[] = array(
|
||||
'id' => 'content_button_download',
|
||||
'linkType' => '_self',
|
||||
'external' => true,
|
||||
'url' => $this->urlFor($request, 'playersoftware.download', ['id' => $version->versionId]) . '?attachment=' . $version->fileName,
|
||||
'text' => __('Download')
|
||||
);
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->playerVersionFactory->countLast();
|
||||
$this->getState()->setData($versions);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Version Delete Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function deleteForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$version = $this->playerVersionFactory->getById($id);
|
||||
|
||||
$version->load();
|
||||
|
||||
$this->getState()->template = 'playersoftware-form-delete';
|
||||
$this->getState()->setData([
|
||||
'version' => $version,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Version
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @SWG\Delete(
|
||||
* path="/playersoftware/{versionId}",
|
||||
* operationId="playerSoftwareDelete",
|
||||
* tags={"Player Software"},
|
||||
* summary="Delete Version",
|
||||
* description="Delete Version file from the Library and Player Versions table",
|
||||
* @SWG\Parameter(
|
||||
* name="versionId",
|
||||
* in="path",
|
||||
* description="The Version ID to Delete",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function delete(Request $request, Response $response, $id)
|
||||
{
|
||||
$version = $this->playerVersionFactory->getById($id);
|
||||
|
||||
$version->load();
|
||||
|
||||
// Unset player version from Display Profile
|
||||
$displayProfiles = $this->displayProfileFactory->query();
|
||||
|
||||
foreach ($displayProfiles as $displayProfile) {
|
||||
if (in_array($displayProfile->type, ['android', 'lg', 'sssp'])) {
|
||||
$currentVersionId = $displayProfile->getSetting('versionMediaId');
|
||||
|
||||
if ($currentVersionId === $version->versionId) {
|
||||
$displayProfile->setSetting('versionMediaId', null);
|
||||
$displayProfile->save();
|
||||
}
|
||||
} else if ($displayProfile->type === 'chromeOS') {
|
||||
$currentVersionId = $displayProfile->getSetting('playerVersionId');
|
||||
|
||||
if ($currentVersionId === $version->versionId) {
|
||||
$displayProfile->setSetting('playerVersionId', null);
|
||||
$displayProfile->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete
|
||||
$version->delete();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Deleted %s'), $version->playerShowVersion)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function editForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$version = $this->playerVersionFactory->getById($id);
|
||||
|
||||
$this->getState()->template = 'playersoftware-form-edit';
|
||||
$this->getState()->setData([
|
||||
'version' => $version,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Player Version
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @SWG\Put(
|
||||
* path="/playersoftware/{versionId}",
|
||||
* operationId="playersoftwareEdit",
|
||||
* tags={"Player Software"},
|
||||
* summary="Edit Player Version",
|
||||
* description="Edit a Player Version file information",
|
||||
* @SWG\Parameter(
|
||||
* name="versionId",
|
||||
* in="path",
|
||||
* description="The Version ID to Edit",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="playerShowVersion",
|
||||
* in="formData",
|
||||
* description="The Name of the player version application, this will be displayed in Version dropdowns in Display Profile and Display",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="version",
|
||||
* in="formData",
|
||||
* description="The Version number",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="code",
|
||||
* in="formData",
|
||||
* description="The Code number",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/Media")
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function edit(Request $request, Response $response, $id)
|
||||
{
|
||||
$version = $this->playerVersionFactory->getById($id);
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$version->version = $sanitizedParams->getString('version');
|
||||
$version->code = $sanitizedParams->getInt('code');
|
||||
$version->playerShowVersion = $sanitizedParams->getString('playerShowVersion');
|
||||
$version->modifiedBy = $this->getUser()->userName;
|
||||
|
||||
$version->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => sprintf(__('Edited %s'), $version->playerShowVersion),
|
||||
'id' => $version->versionId,
|
||||
'data' => $version
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Install Route for SSSP XML
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function getSsspInstall(Request $request, Response $response)
|
||||
{
|
||||
// Get the default SSSP display profile
|
||||
$profile = $this->displayProfileFactory->getDefaultByType('sssp');
|
||||
|
||||
// See if it has a version file (if not or we can't load it, 404)
|
||||
$versionId = $profile->getSetting('versionMediaId');
|
||||
|
||||
if ($versionId !== null) {
|
||||
$version = $this->playerVersionFactory->getById($versionId);
|
||||
|
||||
$xml = $this->outputSsspXml($version->version . '.' . $version->code, $version->size);
|
||||
$response = $response
|
||||
->withHeader('Content-Type', 'application/xml')
|
||||
->write($xml);
|
||||
} else {
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
|
||||
$this->setNoOutput(true);
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Install Route for SSSP WGT
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function getSsspInstallDownload(Request $request, Response $response)
|
||||
{
|
||||
// Get the default SSSP display profile
|
||||
$profile = $this->displayProfileFactory->getDefaultByType('sssp');
|
||||
|
||||
// See if it has a version file (if not, or we can't load it, 404)
|
||||
$versionId = $profile->getSetting('versionMediaId');
|
||||
|
||||
if ($versionId !== null) {
|
||||
$response = $this->download($request, $response, $versionId);
|
||||
} else {
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
|
||||
$this->setNoOutput();
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade Route for SSSP XML
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $nonce
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function getSssp(Request $request, Response $response, $nonce)
|
||||
{
|
||||
// Use the cache to get the displayId for this nonce
|
||||
$cache = $this->pool->getItem('/playerVersion/' . $nonce);
|
||||
|
||||
if ($cache->isMiss()) {
|
||||
$response = $response->withStatus(404);
|
||||
$this->setNoOutput(true);
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
$displayId = $cache->get();
|
||||
|
||||
// Get the Display
|
||||
$display = $this->displayFactory->getById($displayId);
|
||||
|
||||
// Check if display is SSSP, throw Exception if it's not
|
||||
if ($display->clientType != 'sssp') {
|
||||
throw new InvalidArgumentException(__('File available only for SSSP displays'), 'clientType');
|
||||
}
|
||||
|
||||
// Add the correct header
|
||||
$response = $response->withHeader('content-type', 'application/xml');
|
||||
|
||||
// get the media ID from display profile
|
||||
$versionId = $display->getSetting('versionMediaId', null, ['displayOverride' => true]);
|
||||
|
||||
if ($versionId !== null) {
|
||||
$versionInformation = $this->playerVersionFactory->getById($versionId);
|
||||
|
||||
$xml = $this->outputSsspXml($versionInformation->version . '.' . $versionInformation->code, $versionInformation->size);
|
||||
$response = $response->write($xml);
|
||||
} else {
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
|
||||
$this->setNoOutput(true);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade Route for SSSP WGT
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $nonce
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function getVersionFile(Request $request, Response $response, $nonce)
|
||||
{
|
||||
// Use the cache to get the displayId for this nonce
|
||||
$cache = $this->pool->getItem('/playerVersion/' . $nonce);
|
||||
|
||||
if ($cache->isMiss()) {
|
||||
$response = $response->withStatus(404);
|
||||
$this->setNoOutput(true);
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
$displayId = $cache->get();
|
||||
|
||||
// Get display and media
|
||||
$display = $this->displayFactory->getById($displayId);
|
||||
$versionId = $display->getSetting('versionMediaId', null, ['displayOverride' => true]);
|
||||
|
||||
if ($versionId !== null) {
|
||||
$response = $this->download($request, $response, $versionId);
|
||||
} else {
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
|
||||
$this->setNoOutput(true);
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Player Software Upload
|
||||
*
|
||||
* @SWG\Post(
|
||||
* path="/playersoftware",
|
||||
* operationId="playersoftwareUpload",
|
||||
* tags={"Player Software"},
|
||||
* summary="Player Software Upload",
|
||||
* description="Upload a new Player version file",
|
||||
* @SWG\Parameter(
|
||||
* name="files",
|
||||
* in="formData",
|
||||
* description="The Uploaded File",
|
||||
* type="file",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws ConfigurationException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\DuplicateEntityException
|
||||
*/
|
||||
public function add(Request $request, Response $response)
|
||||
{
|
||||
if (!$this->getUser()->featureEnabled('playersoftware.add')) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$libraryFolder = $this->getConfig()->getSetting('LIBRARY_LOCATION');
|
||||
|
||||
// Make sure the library exists
|
||||
MediaService::ensureLibraryExists($libraryFolder);
|
||||
$validExt = $this->getValidExtensions();
|
||||
|
||||
// Make sure there is room in the library
|
||||
$libraryLimit = $this->getConfig()->getSetting('LIBRARY_SIZE_LIMIT_KB') * 1024;
|
||||
|
||||
$options = [
|
||||
'accept_file_types' => '/\.' . implode('|', $validExt) . '$/i',
|
||||
'libraryLimit' => $libraryLimit,
|
||||
'libraryQuotaFull' => ($libraryLimit > 0 && $this->getMediaService()->libraryUsage() > $libraryLimit),
|
||||
];
|
||||
|
||||
// Output handled by UploadHandler
|
||||
$this->setNoOutput(true);
|
||||
|
||||
$this->getLog()->debug('Hand off to Upload Handler with options: ' . json_encode($options));
|
||||
|
||||
// Hand off to the Upload Handler provided by jquery-file-upload
|
||||
$uploadService = new UploadService($libraryFolder . 'temp/', $options, $this->getLog(), $this->getState());
|
||||
$uploadHandler = $uploadService->createUploadHandler();
|
||||
|
||||
$uploadHandler->setPostProcessor(function ($file, $uploadHandler) use ($libraryFolder, $request) {
|
||||
// Return right away if the file already has an error.
|
||||
if (!empty($file->error)) {
|
||||
$this->getState()->setCommitState(false);
|
||||
return $file;
|
||||
}
|
||||
|
||||
$this->getUser()->isQuotaFullByUser(true);
|
||||
|
||||
// Get the uploaded file and move it to the right place
|
||||
$filePath = $libraryFolder . 'temp/' . $file->fileName;
|
||||
|
||||
// Add the Player Software record
|
||||
$playerSoftware = $this->getPlayerVersionFactory()->createEmpty();
|
||||
$playerSoftware->modifiedBy = $this->getUser()->userName;
|
||||
|
||||
// SoC players have issues parsing fileNames with spaces in them
|
||||
// replace any unexpected character in fileName with -
|
||||
$playerSoftware->fileName = preg_replace('/[^a-zA-Z0-9_.]+/', '-', $file->fileName);
|
||||
$playerSoftware->size = filesize($filePath);
|
||||
$playerSoftware->md5 = md5_file($filePath);
|
||||
$playerSoftware->decorateRecord();
|
||||
|
||||
// if the name was provided on upload use that here.
|
||||
if (!empty($file->name)) {
|
||||
$playerSoftware->playerShowVersion = $file->name;
|
||||
}
|
||||
|
||||
$playerSoftware->save();
|
||||
|
||||
// Test to ensure the final file size is the same as the file size we're expecting
|
||||
if ($file->size != $playerSoftware->size) {
|
||||
throw new InvalidArgumentException(
|
||||
__('Sorry this is a corrupted upload, the file size doesn\'t match what we\'re expecting.'),
|
||||
'size'
|
||||
);
|
||||
}
|
||||
|
||||
// everything is fine, move the file from temp folder.
|
||||
rename($filePath, $libraryFolder . 'playersoftware/' . $playerSoftware->fileName);
|
||||
|
||||
// Unpack if necessary
|
||||
$playerSoftware->unpack($libraryFolder, $request);
|
||||
|
||||
// return
|
||||
$file->id = $playerSoftware->versionId;
|
||||
$file->md5 = $playerSoftware->md5;
|
||||
$file->name = $playerSoftware->fileName;
|
||||
|
||||
return $file;
|
||||
});
|
||||
|
||||
$uploadHandler->post();
|
||||
|
||||
// Explicitly set the Content-Type header to application/json
|
||||
$response = $response->withHeader('Content-Type', 'application/json');
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/playersoftware/download/{id}",
|
||||
* operationId="playersoftwareDownload",
|
||||
* tags={"Player Software"},
|
||||
* summary="Download Player Version file",
|
||||
* description="Download Player Version file",
|
||||
* produces={"application/octet-stream"},
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Player Version ID to Download",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(type="file"),
|
||||
* @SWG\Header(
|
||||
* header="X-Sendfile",
|
||||
* description="Apache Send file header - if enabled.",
|
||||
* type="string"
|
||||
* ),
|
||||
* @SWG\Header(
|
||||
* header="X-Accel-Redirect",
|
||||
* description="nginx send file header - if enabled.",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function download(Request $request, Response $response, $id)
|
||||
{
|
||||
$playerVersion = $this->playerVersionFactory->getById($id);
|
||||
|
||||
$this->getLog()->debug('Download request for player software versionId: ' . $id);
|
||||
|
||||
$library = $this->getConfig()->getSetting('LIBRARY_LOCATION');
|
||||
$sendFileMode = $this->getConfig()->getSetting('SENDFILE_MODE');
|
||||
$libraryPath = $library . 'playersoftware' . DIRECTORY_SEPARATOR . $playerVersion->fileName;
|
||||
$attachmentName = urlencode($playerVersion->fileName);
|
||||
|
||||
$downLoadService = new DownloadService($libraryPath, $sendFileMode);
|
||||
$downLoadService->useLogger($this->getLog()->getLoggerInterface());
|
||||
|
||||
return $downLoadService->returnFile(
|
||||
$response,
|
||||
$attachmentName,
|
||||
'/download/playersoftware/' . $playerVersion->fileName
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the SSSP XML
|
||||
* @param $version
|
||||
* @param $size
|
||||
* @return string
|
||||
*/
|
||||
private function outputSsspXml($version, $size)
|
||||
{
|
||||
// create sssp_config XML file with provided information
|
||||
$ssspDocument = new \DOMDocument('1.0', 'UTF-8');
|
||||
$versionNode = $ssspDocument->createElement('widget');
|
||||
$version = $ssspDocument->createElement('ver', $version);
|
||||
$size = $ssspDocument->createElement('size', $size);
|
||||
|
||||
// Our widget name is always sssp_dl (this is appended to both the install and upgrade routes)
|
||||
$name = $ssspDocument->createElement('widgetname', 'sssp_dl');
|
||||
|
||||
$ssspDocument->appendChild($versionNode);
|
||||
$versionNode->appendChild($version);
|
||||
$versionNode->appendChild($size);
|
||||
$versionNode->appendChild($name);
|
||||
$versionNode->appendChild($ssspDocument->createElement('webtype', 'tizen'));
|
||||
$ssspDocument->formatOutput = true;
|
||||
|
||||
return $ssspDocument->saveXML();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getValidExtensions()
|
||||
{
|
||||
return ['apk', 'ipk', 'wgt', 'chrome'];
|
||||
}
|
||||
}
|
||||
2113
lib/Controller/Playlist.php
Normal file
2113
lib/Controller/Playlist.php
Normal file
File diff suppressed because it is too large
Load Diff
261
lib/Controller/PlaylistDashboard.php
Normal file
261
lib/Controller/PlaylistDashboard.php
Normal file
@@ -0,0 +1,261 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2023 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Event\SubPlaylistItemsEvent;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class PlaylistDashboard
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class PlaylistDashboard extends Base
|
||||
{
|
||||
/** @var \Xibo\Factory\PlaylistFactory */
|
||||
private $playlistFactory;
|
||||
|
||||
/** @var \Xibo\Factory\ModuleFactory */
|
||||
private $moduleFactory;
|
||||
|
||||
/** @var \Xibo\Factory\WidgetFactory */
|
||||
private $widgetFactory;
|
||||
|
||||
/** @var \Xibo\Factory\MediaFactory */
|
||||
private $mediaFactory;
|
||||
|
||||
/** @var ContainerInterface */
|
||||
private $container;
|
||||
|
||||
/**
|
||||
* PlaylistDashboard constructor.
|
||||
* @param $playlistFactory
|
||||
* @param $moduleFactory
|
||||
* @param $widgetFactory
|
||||
* @param \Xibo\Factory\MediaFactory $mediaFactory
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct($playlistFactory, $moduleFactory, $widgetFactory, $mediaFactory, ContainerInterface $container)
|
||||
{
|
||||
$this->playlistFactory = $playlistFactory;
|
||||
$this->moduleFactory = $moduleFactory;
|
||||
$this->widgetFactory = $widgetFactory;
|
||||
$this->mediaFactory = $mediaFactory;
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Slim\Http\ServerRequest $request
|
||||
* @param \Slim\Http\Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|\Slim\Http\Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response)
|
||||
{
|
||||
// Do we have a Playlist already in our User Preferences?
|
||||
$playlist = null;
|
||||
try {
|
||||
$playlistId = $this->getUser()->getOption('playlistDashboardSelectedPlaylistId');
|
||||
if ($playlistId->value != 0) {
|
||||
$playlist = $this->playlistFactory->getById($playlistId->value);
|
||||
}
|
||||
} catch (NotFoundException $notFoundException) {
|
||||
// this is fine, no need to throw errors here.
|
||||
$this->getLog()->debug(
|
||||
'Problem getting playlistDashboardSelectedPlaylistId user option. e = ' .
|
||||
$notFoundException->getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
$this->getState()->template = 'playlist-dashboard';
|
||||
$this->getState()->setData([
|
||||
'playlist' => $playlist,
|
||||
'validExtensions' => implode('|', $this->moduleFactory->getValidExtensions())
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grid used for the Playlist drop down list
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function grid(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
// Playlists
|
||||
$playlists = $this->playlistFactory->query($this->gridRenderSort($sanitizedParams), $this->gridRenderFilter([
|
||||
'name' => $this->getSanitizer($request->getParams())->getString('name'),
|
||||
'regionSpecific' => 0
|
||||
], $sanitizedParams));
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->playlistFactory->countLast();
|
||||
$this->getState()->setData($playlists);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a particular playlist
|
||||
* the output from this is very much like a form.
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function show(Request $request, Response $response, $id)
|
||||
{
|
||||
// Record this Playlist as the one we have currently selected.
|
||||
try {
|
||||
$this->getUser()->setOptionValue('playlistDashboardSelectedPlaylistId', $id);
|
||||
$this->getUser()->save();
|
||||
} catch (GeneralException $exception) {
|
||||
$this->getLog()->error('Problem setting playlistDashboardSelectedPlaylistId user option. e = ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
// Spots
|
||||
$spotsFound = 0;
|
||||
|
||||
$playlist = $this->playlistFactory->getById($id);
|
||||
|
||||
// Only edit permissions
|
||||
if (!$this->getUser()->checkEditable($playlist)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getLog()->debug('show: testing to see if ' . $playlist->name . ' / ' . $playlist->playlistId
|
||||
. ' is the first playlist in any other ones.');
|
||||
|
||||
// Work out the slot size of the first sub-playlist we are in.
|
||||
foreach ($this->playlistFactory->query(null, [
|
||||
'childId' => $playlist->playlistId,
|
||||
'depth' => 1,
|
||||
'disableUserCheck' => 1
|
||||
]) as $parent) {
|
||||
// $parent is a playlist to which we belong.
|
||||
$this->getLog()->debug('show: This playlist is a sub-playlist in ' . $parent->name . '.');
|
||||
$parent->load();
|
||||
|
||||
foreach ($parent->widgets as $parentWidget) {
|
||||
if ($parentWidget->type === 'subplaylist') {
|
||||
$this->getLog()->debug('show: matched against a sub playlist widget ' . $parentWidget->widgetId . '.');
|
||||
|
||||
// Get the sub-playlist widgets
|
||||
$event = new SubPlaylistItemsEvent($parentWidget);
|
||||
$this->getDispatcher()->dispatch($event, SubPlaylistItemsEvent::$NAME);
|
||||
|
||||
foreach ($event->getItems() as $subPlaylistItem) {
|
||||
$this->getLog()->debug('show: Assessing playlist ' . $subPlaylistItem->playlistId . ' on ' . $playlist->name);
|
||||
if ($subPlaylistItem->playlistId == $playlist->playlistId) {
|
||||
// Take the highest number of Spots we can find out of all the assignments.
|
||||
$spotsFound = max($subPlaylistItem->spots ?? 0, $spotsFound);
|
||||
|
||||
// Assume this one isn't in the list more than one time.
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
|
||||
$this->getLog()->debug('show: no matching playlists found.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load my Playlist and information about its widgets
|
||||
if ($spotsFound > 0) {
|
||||
// We are in a sub-playlist with spots, so now we load our widgets.
|
||||
$playlist->load();
|
||||
$user = $this->getUser();
|
||||
|
||||
foreach ($playlist->widgets as $widget) {
|
||||
// Create a module for the widget and load in some extra data
|
||||
$module = $this->moduleFactory->getByType($widget->type);
|
||||
$widget->setUnmatchedProperty('name', $widget->getOptionValue('name', $module->name));
|
||||
$widget->setUnmatchedProperty('regionSpecific', $module->regionSpecific);
|
||||
$widget->setUnmatchedProperty('moduleIcon', $module->icon);
|
||||
|
||||
// Check my permissions
|
||||
if ($module->regionSpecific == 0) {
|
||||
$media = $this->mediaFactory->getById($widget->getPrimaryMediaId());
|
||||
$widget->setUnmatchedProperty('viewble', $user->checkViewable($media));
|
||||
$widget->setUnmatchedProperty('editable', $user->checkEditable($media));
|
||||
$widget->setUnmatchedProperty('deletable', $user->checkDeleteable($media));
|
||||
} else {
|
||||
$widget->setUnmatchedProperty('viewble', $user->checkViewable($widget));
|
||||
$widget->setUnmatchedProperty('editable', $user->checkEditable($widget));
|
||||
$widget->setUnmatchedProperty('deletable', $user->checkDeleteable($widget));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'playlist-dashboard-spots';
|
||||
$this->getState()->setData([
|
||||
'playlist' => $playlist,
|
||||
'spotsFound' => $spotsFound
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Playlist Widget Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function deletePlaylistWidgetForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$widget = $this->widgetFactory->loadByWidgetId($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($widget)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Pass to view
|
||||
$this->getState()->template = 'playlist-module-form-delete';
|
||||
$this->getState()->setData([
|
||||
'widget' => $widget,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
155
lib/Controller/Preview.php
Normal file
155
lib/Controller/Preview.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\LayoutFactory;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
|
||||
/**
|
||||
* Class Preview
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class Preview extends Base
|
||||
{
|
||||
/**
|
||||
* @var LayoutFactory
|
||||
*/
|
||||
private $layoutFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param LayoutFactory $layoutFactory
|
||||
*/
|
||||
public function __construct($layoutFactory)
|
||||
{
|
||||
$this->layoutFactory = $layoutFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout Preview
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function show(Request $request, Response $response, $id)
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
// Get the layout
|
||||
if ($sanitizedParams->getInt('findByCode') === 1) {
|
||||
$layout = $this->layoutFactory->getByCode($id);
|
||||
} else {
|
||||
$layout = $this->layoutFactory->getById($id);
|
||||
}
|
||||
|
||||
if (!$this->getUser()->checkViewable($layout)
|
||||
|| !$this->getUser()->featureEnabled(['layout.view', 'playlist.view', 'campaign.view'])
|
||||
) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Do we want to preview the draft version of this Layout?
|
||||
if ($sanitizedParams->getCheckbox('isPreviewDraft') && $layout->hasDraft()) {
|
||||
$layout = $this->layoutFactory->getByParentId($layout->layoutId);
|
||||
}
|
||||
|
||||
$this->getState()->template = 'layout-renderer';
|
||||
$this->getState()->setData([
|
||||
'layout' => $layout,
|
||||
'previewOptions' => [
|
||||
'getXlfUrl' => $this->urlFor($request, 'layout.getXlf', ['id' => $layout->layoutId]),
|
||||
'getResourceUrl' => $this->urlFor($request, 'module.getResource', [
|
||||
'regionId' => ':regionId', 'id' => ':id'
|
||||
]),
|
||||
'libraryDownloadUrl' => $this->urlFor($request, 'library.download', ['id' => ':id']),
|
||||
'layoutBackgroundDownloadUrl' => $this->urlFor($request, 'layout.download.background', ['id' => ':id']),
|
||||
'loaderUrl' => $this->getConfig()->uri('img/loader.gif'),
|
||||
'layoutPreviewUrl' => $this->urlFor($request, 'layout.preview', ['id' => '[layoutCode]'])
|
||||
]
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the XLF for a Layout
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
echo file_get_contents($layout->xlfToDisk([
|
||||
'notify' => false,
|
||||
'collectNow' => false,
|
||||
]));
|
||||
|
||||
$this->setNoOutput();
|
||||
} finally {
|
||||
// Release lock
|
||||
$this->layoutFactory->concurrentRequestRelease($layout);
|
||||
}
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the player bundle
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
*/
|
||||
public function playerBundle(Request $request, Response $response)
|
||||
{
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
$isMap = $params->getCheckbox('map');
|
||||
if ($isMap) {
|
||||
$bundle = file_get_contents(PROJECT_ROOT . '/modules/bundle.min.js.map');
|
||||
} else {
|
||||
$bundle = file_get_contents(PROJECT_ROOT . '/modules/bundle.min.js');
|
||||
}
|
||||
|
||||
$response->getBody()->write($bundle);
|
||||
return $response->withStatus(200)
|
||||
->withHeader('Content-Size', strlen($bundle))
|
||||
->withHeader('Content-Type', 'application/javascript');
|
||||
}
|
||||
}
|
||||
207
lib/Controller/Pwa.php
Normal file
207
lib/Controller/Pwa.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\DisplayFactory;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Xmds\Soap7;
|
||||
|
||||
/**
|
||||
* PWA
|
||||
* routes for a PWA to download resources which live in an iframe
|
||||
*/
|
||||
class Pwa extends Base
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DisplayFactory $displayFactory,
|
||||
private readonly ContainerInterface $container
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Xibo\Support\Exception\AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function getResource(Request $request, Response $response): Response
|
||||
{
|
||||
// Create a Soap client and call it.
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
|
||||
try {
|
||||
// Which version are we?
|
||||
$version = $params->getInt('v', [
|
||||
'default' => 7,
|
||||
'throw' => function () {
|
||||
throw new InvalidArgumentException(__('Missing Version'), 'v');
|
||||
}
|
||||
]);
|
||||
|
||||
if ($version < 7) {
|
||||
throw new InvalidArgumentException(__('PWA supported from XMDS schema 7 onward.'), 'v');
|
||||
}
|
||||
|
||||
// Validate that this display should call this service.
|
||||
$hardwareKey = $params->getString('hardwareKey');
|
||||
$display = $this->displayFactory->getByLicence($hardwareKey);
|
||||
if (!$display->isPwa()) {
|
||||
throw new AccessDeniedException(__('Please use XMDS API'), 'hardwareKey');
|
||||
}
|
||||
|
||||
// Check it is still authorised.
|
||||
if ($display->licensed == 0) {
|
||||
throw new AccessDeniedException(__('Display unauthorised'));
|
||||
}
|
||||
|
||||
/** @var Soap7 $soap */
|
||||
$soap = $this->getSoap($version);
|
||||
|
||||
$this->getLog()->debug('getResource: passing to Soap class');
|
||||
|
||||
$body = $soap->GetResource(
|
||||
$params->getString('serverKey'),
|
||||
$params->getString('hardwareKey'),
|
||||
$params->getInt('layoutId'),
|
||||
$params->getInt('regionId') . '',
|
||||
$params->getInt('mediaId') . '',
|
||||
);
|
||||
|
||||
$response->getBody()->write($body);
|
||||
|
||||
return $response
|
||||
->withoutHeader('Content-Security-Policy');
|
||||
} catch (\SoapFault $e) {
|
||||
throw new GeneralException($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function getData(Request $request, Response $response): Response
|
||||
{
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
|
||||
try {
|
||||
$version = $params->getInt('v', [
|
||||
'default' => 7,
|
||||
'throw' => function () {
|
||||
throw new InvalidArgumentException(__('Missing Version'), 'v');
|
||||
}
|
||||
]);
|
||||
|
||||
if ($version < 7) {
|
||||
throw new InvalidArgumentException(__('PWA supported from XMDS schema 7 onward.'), 'v');
|
||||
}
|
||||
|
||||
// Validate that this display should call this service.
|
||||
$hardwareKey = $params->getString('hardwareKey');
|
||||
$display = $this->displayFactory->getByLicence($hardwareKey);
|
||||
if (!$display->isPwa()) {
|
||||
throw new AccessDeniedException(__('Please use XMDS API'), 'hardwareKey');
|
||||
}
|
||||
|
||||
// Check it is still authorised.
|
||||
if ($display->licensed == 0) {
|
||||
throw new AccessDeniedException(__('Display unauthorised'));
|
||||
}
|
||||
|
||||
/** @var Soap7 $soap */
|
||||
$soap = $this->getSoap($version);
|
||||
$body = $soap->GetData(
|
||||
$params->getString('serverKey'),
|
||||
$params->getString('hardwareKey'),
|
||||
$params->getInt('widgetId'),
|
||||
);
|
||||
|
||||
$response->getBody()->write($body);
|
||||
|
||||
return $response
|
||||
->withoutHeader('Content-Security-Policy');
|
||||
} catch (\SoapFault $e) {
|
||||
throw new GeneralException($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function getSoap(int $version): mixed
|
||||
{
|
||||
$class = '\Xibo\Xmds\Soap' . $version;
|
||||
if (!class_exists($class)) {
|
||||
throw new InvalidArgumentException(__('Unknown version'), 'version');
|
||||
}
|
||||
|
||||
// Overwrite the logger
|
||||
$uidProcessor = new \Monolog\Processor\UidProcessor(7);
|
||||
$logProcessor = new \Xibo\Xmds\LogProcessor(
|
||||
$this->container->get('logger'),
|
||||
$uidProcessor->getUid()
|
||||
);
|
||||
$this->container->get('logger')->pushProcessor($logProcessor);
|
||||
|
||||
return new $class(
|
||||
$logProcessor,
|
||||
$this->container->get('pool'),
|
||||
$this->container->get('store'),
|
||||
$this->container->get('timeSeriesStore'),
|
||||
$this->container->get('logService'),
|
||||
$this->container->get('sanitizerService'),
|
||||
$this->container->get('configService'),
|
||||
$this->container->get('requiredFileFactory'),
|
||||
$this->container->get('moduleFactory'),
|
||||
$this->container->get('layoutFactory'),
|
||||
$this->container->get('dataSetFactory'),
|
||||
$this->displayFactory,
|
||||
$this->container->get('userGroupFactory'),
|
||||
$this->container->get('bandwidthFactory'),
|
||||
$this->container->get('mediaFactory'),
|
||||
$this->container->get('widgetFactory'),
|
||||
$this->container->get('regionFactory'),
|
||||
$this->container->get('notificationFactory'),
|
||||
$this->container->get('displayEventFactory'),
|
||||
$this->container->get('scheduleFactory'),
|
||||
$this->container->get('dayPartFactory'),
|
||||
$this->container->get('playerVersionFactory'),
|
||||
$this->container->get('dispatcher'),
|
||||
$this->container->get('campaignFactory'),
|
||||
$this->container->get('syncGroupFactory'),
|
||||
$this->container->get('playerFaultFactory')
|
||||
);
|
||||
}
|
||||
}
|
||||
836
lib/Controller/Region.php
Normal file
836
lib/Controller/Region.php
Normal file
@@ -0,0 +1,836 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2023 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Event\RegionAddedEvent;
|
||||
use Xibo\Event\SubPlaylistWidgetsEvent;
|
||||
use Xibo\Factory\LayoutFactory;
|
||||
use Xibo\Factory\ModuleFactory;
|
||||
use Xibo\Factory\RegionFactory;
|
||||
use Xibo\Factory\TransitionFactory;
|
||||
use Xibo\Factory\WidgetFactory;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\ControllerNotImplemented;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class Region
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class Region extends Base
|
||||
{
|
||||
/**
|
||||
* @var RegionFactory
|
||||
*/
|
||||
private $regionFactory;
|
||||
|
||||
/** @var WidgetFactory */
|
||||
private $widgetFactory;
|
||||
|
||||
/**
|
||||
* @var ModuleFactory
|
||||
*/
|
||||
private $moduleFactory;
|
||||
|
||||
/**
|
||||
* @var LayoutFactory
|
||||
*/
|
||||
private $layoutFactory;
|
||||
|
||||
/**
|
||||
* @var TransitionFactory
|
||||
*/
|
||||
private $transitionFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param RegionFactory $regionFactory
|
||||
* @param WidgetFactory $widgetFactory
|
||||
* @param TransitionFactory $transitionFactory
|
||||
* @param ModuleFactory $moduleFactory
|
||||
* @param LayoutFactory $layoutFactory
|
||||
*/
|
||||
public function __construct(
|
||||
$regionFactory,
|
||||
$widgetFactory,
|
||||
$transitionFactory,
|
||||
$moduleFactory,
|
||||
$layoutFactory
|
||||
) {
|
||||
$this->regionFactory = $regionFactory;
|
||||
$this->widgetFactory = $widgetFactory;
|
||||
$this->transitionFactory = $transitionFactory;
|
||||
$this->layoutFactory = $layoutFactory;
|
||||
$this->moduleFactory = $moduleFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get region by id
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws ControllerNotImplemented
|
||||
*/
|
||||
public function get(Request $request, Response $response, $id)
|
||||
{
|
||||
$region = $this->regionFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($region)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->setData([
|
||||
'region' => $region,
|
||||
'layout' => $this->layoutFactory->getById($region->layoutId),
|
||||
'transitions' => $this->transitionData(),
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a region
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws ControllerNotImplemented
|
||||
* @SWG\Post(
|
||||
* path="/region/{id}",
|
||||
* operationId="regionAdd",
|
||||
* tags={"layout"},
|
||||
* summary="Add Region",
|
||||
* description="Add a Region to a Layout",
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Layout ID to add the Region to",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="type",
|
||||
* in="formData",
|
||||
* description="The type of region this should be, zone, frame, playlist or canvas. Default = frame.",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="width",
|
||||
* in="formData",
|
||||
* description="The Width, default 250",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="height",
|
||||
* in="formData",
|
||||
* description="The Height",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="top",
|
||||
* in="formData",
|
||||
* description="The Top Coordinate",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="left",
|
||||
* in="formData",
|
||||
* description="The Left Coordinate",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/Region"),
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new record",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function add(Request $request, Response $response, $id)
|
||||
{
|
||||
$layout = $this->layoutFactory->getById($id);
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
if (!$this->getUser()->checkEditable($layout)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
if (!$layout->isChild()) {
|
||||
throw new InvalidArgumentException(__('This Layout is not a Draft, please checkout.'), 'layoutId');
|
||||
}
|
||||
|
||||
$layout->load([
|
||||
'loadPlaylists' => true,
|
||||
'loadTags' => false,
|
||||
'loadPermissions' => true,
|
||||
'loadCampaigns' => false
|
||||
]);
|
||||
|
||||
// Add a new region
|
||||
$region = $this->regionFactory->create(
|
||||
$sanitizedParams->getString('type', ['default' => 'frame']),
|
||||
$this->getUser()->userId,
|
||||
'',
|
||||
$sanitizedParams->getInt('width', ['default' => 250]),
|
||||
$sanitizedParams->getInt('height', ['default' => 250]),
|
||||
$sanitizedParams->getInt('top', ['default' => 50]),
|
||||
$sanitizedParams->getInt('left', ['default' => 50]),
|
||||
$sanitizedParams->getInt('zIndex', ['default' => 0])
|
||||
);
|
||||
|
||||
$layout->regions[] = $region;
|
||||
$layout->save([
|
||||
'saveTags' => false
|
||||
]);
|
||||
|
||||
// Dispatch an event to say that we have added a region
|
||||
$this->getDispatcher()->dispatch(new RegionAddedEvent($layout, $region), RegionAddedEvent::$NAME);
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => sprintf(__('Added %s'), $region->name),
|
||||
'id' => $region->regionId,
|
||||
'data' => $region
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws ControllerNotImplemented
|
||||
* @SWG\Put(
|
||||
* path="/region/{id}",
|
||||
* operationId="regionEdit",
|
||||
* tags={"layout"},
|
||||
* summary="Edit Region",
|
||||
* description="Edit Region",
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Region ID to Edit",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="width",
|
||||
* in="formData",
|
||||
* description="The Width, default 250",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="height",
|
||||
* in="formData",
|
||||
* description="The Height",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="top",
|
||||
* in="formData",
|
||||
* description="The Top Coordinate",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="left",
|
||||
* in="formData",
|
||||
* description="The Left Coordinate",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="zIndex",
|
||||
* in="formData",
|
||||
* description="The Layer for this Region",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="transitionType",
|
||||
* in="formData",
|
||||
* description="The Transition Type. Must be a valid transition code as returned by /transition",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="transitionDuration",
|
||||
* in="formData",
|
||||
* description="The transition duration in milliseconds if required by the transition type",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="transitionDirection",
|
||||
* in="formData",
|
||||
* description="The transition direction if required by the transition type.",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="loop",
|
||||
* in="formData",
|
||||
* description="Flag indicating whether this region should loop if there is only 1 media item in the timeline",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/Region")
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function edit(Request $request, Response $response, $id)
|
||||
{
|
||||
$region = $this->regionFactory->getById($id);
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
if (!$this->getUser()->checkEditable($region)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Check that this Regions Layout is in an editable state
|
||||
$layout = $this->layoutFactory->getById($region->layoutId);
|
||||
|
||||
if (!$layout->isChild()) {
|
||||
throw new InvalidArgumentException(__('This Layout is not a Draft, please checkout.'), 'layoutId');
|
||||
}
|
||||
|
||||
// Load before we save
|
||||
$region->load();
|
||||
|
||||
$region->name = $sanitizedParams->getString('name');
|
||||
$region->width = $sanitizedParams->getDouble('width');
|
||||
$region->height = $sanitizedParams->getDouble('height');
|
||||
$region->top = $sanitizedParams->getDouble('top', ['default' => 0]);
|
||||
$region->left = $sanitizedParams->getDouble('left', ['default' => 0]);
|
||||
$region->zIndex = $sanitizedParams->getInt('zIndex');
|
||||
$region->type = $sanitizedParams->getString('type');
|
||||
$region->syncKey = $sanitizedParams->getString('syncKey', ['defaultOnEmptyString' => true]);
|
||||
|
||||
// Loop
|
||||
$region->setOptionValue('loop', $sanitizedParams->getCheckbox('loop'));
|
||||
|
||||
// Transitions
|
||||
$region->setOptionValue('transitionType', $sanitizedParams->getString('transitionType'));
|
||||
$region->setOptionValue('transitionDuration', $sanitizedParams->getInt('transitionDuration'));
|
||||
$region->setOptionValue('transitionDirection', $sanitizedParams->getString('transitionDirection'));
|
||||
|
||||
// Save
|
||||
$region->save();
|
||||
|
||||
// Mark the layout as needing rebuild
|
||||
$layout->load(\Xibo\Entity\Layout::$loadOptionsMinimum);
|
||||
|
||||
$saveOptions = \Xibo\Entity\Layout::$saveOptionsMinimum;
|
||||
$saveOptions['setBuildRequired'] = true;
|
||||
|
||||
$layout->save($saveOptions);
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => sprintf(__('Edited %s'), $region->name),
|
||||
'id' => $region->regionId,
|
||||
'data' => $region
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a region
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws ControllerNotImplemented
|
||||
* @SWG\Delete(
|
||||
* path="/region/{regionId}",
|
||||
* operationId="regionDelete",
|
||||
* tags={"layout"},
|
||||
* summary="Region Delete",
|
||||
* description="Delete an existing region",
|
||||
* @SWG\Parameter(
|
||||
* name="regionId",
|
||||
* in="path",
|
||||
* description="The Region ID to Delete",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function delete(Request $request, Response $response, $id)
|
||||
{
|
||||
$region = $this->regionFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($region)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Check that this Regions Layout is in an editable state
|
||||
$layout = $this->layoutFactory->getById($region->layoutId);
|
||||
|
||||
if (!$layout->isChild())
|
||||
throw new InvalidArgumentException(__('This Layout is not a Draft, please checkout.'), 'layoutId');
|
||||
|
||||
$region->delete();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Deleted %s'), $region->name)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Positions
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws ControllerNotImplemented
|
||||
* @SWG\Put(
|
||||
* path="/region/position/all/{layoutId}",
|
||||
* operationId="regionPositionAll",
|
||||
* tags={"layout"},
|
||||
* summary="Position Regions",
|
||||
* description="Position all regions for a Layout",
|
||||
* @SWG\Parameter(
|
||||
* name="layoutId",
|
||||
* in="path",
|
||||
* description="The Layout ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="regions",
|
||||
* in="formData",
|
||||
* description="Array of regions and their new positions. Each array element should be json encoded and have regionId, top, left, width and height.",
|
||||
* type="array",
|
||||
* required=true,
|
||||
* @SWG\Items(
|
||||
* type="string"
|
||||
* )
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/Layout")
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
function positionAll(Request $request, Response $response, $id)
|
||||
{
|
||||
// Create the layout
|
||||
$layout = $this->layoutFactory->loadById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($layout)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Check that this Layout is a Draft
|
||||
if (!$layout->isChild()) {
|
||||
throw new InvalidArgumentException(__('This Layout is not a Draft, please checkout.'), 'layoutId');
|
||||
}
|
||||
|
||||
// Pull in the regions and convert them to stdObjects
|
||||
$regions = $request->getParam('regions', null);
|
||||
|
||||
if ($regions == null) {
|
||||
throw new InvalidArgumentException(__('No regions present'));
|
||||
}
|
||||
$regions = json_decode($regions);
|
||||
|
||||
// Go through each region and update the region in the layout we have
|
||||
foreach ($regions as $newCoordinates) {
|
||||
// TODO attempt to sanitize?
|
||||
// Check that the properties we are expecting do actually exist
|
||||
if (!property_exists($newCoordinates, 'regionid'))
|
||||
throw new InvalidArgumentException(__('Missing regionid property'));
|
||||
|
||||
if (!property_exists($newCoordinates, 'top'))
|
||||
throw new InvalidArgumentException(__('Missing top property'));
|
||||
|
||||
if (!property_exists($newCoordinates, 'left'))
|
||||
throw new InvalidArgumentException(__('Missing left property'));
|
||||
|
||||
if (!property_exists($newCoordinates, 'width'))
|
||||
throw new InvalidArgumentException(__('Missing width property'));
|
||||
|
||||
if (!property_exists($newCoordinates, 'height'))
|
||||
throw new InvalidArgumentException(__('Missing height property'));
|
||||
|
||||
$regionId = $newCoordinates->regionid;
|
||||
|
||||
// Load the region
|
||||
$region = $layout->getRegion($regionId);
|
||||
|
||||
// Check Permissions
|
||||
if (!$this->getUser()->checkEditable($region)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// New coordinates
|
||||
$region->top = $newCoordinates->top;
|
||||
$region->left = $newCoordinates->left;
|
||||
$region->width = $newCoordinates->width;
|
||||
$region->height = $newCoordinates->height;
|
||||
$region->zIndex = $newCoordinates->zIndex;
|
||||
$this->getLog()->debug('Set ' . $region);
|
||||
}
|
||||
|
||||
// Mark the layout as having changed
|
||||
$layout->status = 0;
|
||||
$layout->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => sprintf(__('Edited %s'), $layout->layout),
|
||||
'id' => $layout->layoutId,
|
||||
'data' => $layout
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the Preview inside the Layout Designer
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Twig\Error\LoaderError
|
||||
* @throws \Twig\Error\RuntimeError
|
||||
* @throws \Twig\Error\SyntaxError
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function preview(Request $request, Response $response, $id)
|
||||
{
|
||||
$sanitizedQuery = $this->getSanitizer($request->getParams());
|
||||
|
||||
$widgetId = $sanitizedQuery->getInt('widgetId', ['default' => null]);
|
||||
$seq = $sanitizedQuery->getInt('seq', ['default' => 1]);
|
||||
|
||||
// Load our region
|
||||
try {
|
||||
$region = $this->regionFactory->getById($id);
|
||||
$region->load();
|
||||
|
||||
// What type of region are we?
|
||||
$additionalContexts = [];
|
||||
if ($region->type === 'canvas' || $region->type === 'playlist') {
|
||||
$this->getLog()->debug('preview: canvas or playlist region');
|
||||
|
||||
// Get the first playlist we can find
|
||||
$playlist = $region->getPlaylist()->setModuleFactory($this->moduleFactory);
|
||||
|
||||
// Expand this Playlist out to its individual Widgets
|
||||
$widgets = $playlist->expandWidgets();
|
||||
|
||||
$countWidgets = count($widgets);
|
||||
|
||||
// Select the widget at the required sequence
|
||||
$widget = $playlist->getWidgetAt($seq, $widgets);
|
||||
$widget->load();
|
||||
} else {
|
||||
$this->getLog()->debug('preview: single widget');
|
||||
|
||||
// Assume we're a frame, single Widget Requested
|
||||
$widget = $this->widgetFactory->getById($widgetId);
|
||||
$widget->load();
|
||||
|
||||
if ($widget->type === 'subplaylist') {
|
||||
// Get the sub-playlist widgets
|
||||
$event = new SubPlaylistWidgetsEvent($widget, $widget->tempId);
|
||||
$this->getDispatcher()->dispatch($event, SubPlaylistWidgetsEvent::$NAME);
|
||||
$additionalContexts['countSubPlaylistWidgets'] = count($event->getWidgets());
|
||||
}
|
||||
|
||||
$countWidgets = 1;
|
||||
}
|
||||
|
||||
$this->getLog()->debug('There are ' . $countWidgets . ' widgets.');
|
||||
|
||||
// Output a preview
|
||||
$module = $this->moduleFactory->getByType($widget->type);
|
||||
$this->getState()->html = $this->moduleFactory
|
||||
->createWidgetHtmlRenderer()
|
||||
->preview(
|
||||
$module,
|
||||
$region,
|
||||
$widget,
|
||||
$sanitizedQuery,
|
||||
$this->urlFor(
|
||||
$request,
|
||||
'library.download',
|
||||
[
|
||||
'regionId' => $region->regionId,
|
||||
'id' => $widget->getPrimaryMedia()[0] ?? null
|
||||
]
|
||||
) . '?preview=1',
|
||||
$additionalContexts
|
||||
);
|
||||
$this->getState()->extra['countOfWidgets'] = $countWidgets;
|
||||
$this->getState()->extra['empty'] = false;
|
||||
} catch (NotFoundException) {
|
||||
$this->getState()->extra['empty'] = true;
|
||||
$this->getState()->extra['text'] = __('Empty Playlist');
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$this->getState()->extra['empty'] = true;
|
||||
$this->getState()->extra['text'] = __('Please correct the error with this Widget');
|
||||
}
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function transitionData()
|
||||
{
|
||||
return [
|
||||
'in' => $this->transitionFactory->getEnabledByType('in'),
|
||||
'out' => $this->transitionFactory->getEnabledByType('out'),
|
||||
'compassPoints' => array(
|
||||
array('id' => 'N', 'name' => __('North')),
|
||||
array('id' => 'NE', 'name' => __('North East')),
|
||||
array('id' => 'E', 'name' => __('East')),
|
||||
array('id' => 'SE', 'name' => __('South East')),
|
||||
array('id' => 'S', 'name' => __('South')),
|
||||
array('id' => 'SW', 'name' => __('South West')),
|
||||
array('id' => 'W', 'name' => __('West')),
|
||||
array('id' => 'NW', 'name' => __('North West'))
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a drawer
|
||||
|
||||
* @SWG\Post(
|
||||
* path="/region/drawer/{id}",
|
||||
* operationId="regionDrawerAdd",
|
||||
* tags={"layout"},
|
||||
* summary="Add drawer Region",
|
||||
* description="Add a drawer Region to a Layout",
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Layout ID to add the Region to",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/Region"),
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new record",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws ControllerNotImplemented
|
||||
*/
|
||||
public function addDrawer(Request $request, Response $response, $id) :Response
|
||||
{
|
||||
$layout = $this->layoutFactory->getById($id);
|
||||
if (!$this->getUser()->checkEditable($layout)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
if (!$layout->isChild()) {
|
||||
throw new InvalidArgumentException(__('This Layout is not a Draft, please checkout.'), 'layoutId');
|
||||
}
|
||||
|
||||
$layout->load([
|
||||
'loadPlaylists' => true,
|
||||
'loadTags' => false,
|
||||
'loadPermissions' => true,
|
||||
'loadCampaigns' => false
|
||||
]);
|
||||
|
||||
// Add a new region
|
||||
// we default to layout width/height/0/0
|
||||
$drawer = $this->regionFactory->create(
|
||||
'drawer',
|
||||
$this->getUser()->userId,
|
||||
$layout->layout . '-' . (count($layout->regions) + 1 . ' - drawer'),
|
||||
$layout->width,
|
||||
$layout->height,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
);
|
||||
|
||||
$layout->drawers[] = $drawer;
|
||||
$layout->save([
|
||||
'saveTags' => false
|
||||
]);
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => sprintf(__('Added drawer %s'), $drawer->name),
|
||||
'id' => $drawer->regionId,
|
||||
'data' => $drawer
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws ControllerNotImplemented
|
||||
*
|
||||
* @SWG\Put(
|
||||
* path="/region/drawer/{id}",
|
||||
* operationId="regionDrawerSave",
|
||||
* tags={"layout"},
|
||||
* summary="Save Drawer",
|
||||
* description="Save Drawer",
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Drawer ID to Save",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="width",
|
||||
* in="formData",
|
||||
* description="The Width, default 250",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="height",
|
||||
* in="formData",
|
||||
* description="The Height",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/Region")
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function saveDrawer(Request $request, Response $response, $id)
|
||||
{
|
||||
$region = $this->regionFactory->getById($id);
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
if (!$this->getUser()->checkEditable($region)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Check that this Regions Layout is in an editable state
|
||||
$layout = $this->layoutFactory->getById($region->layoutId);
|
||||
|
||||
if (!$layout->isChild()) {
|
||||
throw new InvalidArgumentException(__('This Layout is not a Draft, please checkout.'), 'layoutId');
|
||||
}
|
||||
|
||||
// Save
|
||||
$region->load();
|
||||
$region->width = $sanitizedParams->getDouble('width', ['default' => $layout->width]);
|
||||
$region->height = $sanitizedParams->getDouble('height', ['default' => $layout->height]);
|
||||
$region->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => sprintf(__('Edited Drawer %s'), $region->name),
|
||||
'id' => $region->regionId,
|
||||
'data' => $region
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
122
lib/Controller/Report.php
Normal file
122
lib/Controller/Report.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2021 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Entity\ReportResult;
|
||||
use Xibo\Service\ReportServiceInterface;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
|
||||
/**
|
||||
* Class Report
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class Report extends Base
|
||||
{
|
||||
/**
|
||||
* @var ReportServiceInterface
|
||||
*/
|
||||
private $reportService;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param ReportServiceInterface $reportService
|
||||
*/
|
||||
public function __construct($reportService)
|
||||
{
|
||||
$this->reportService = $reportService;
|
||||
}
|
||||
|
||||
/// //<editor-fold desc="Ad hoc reports">
|
||||
|
||||
/**
|
||||
* Displays an Ad Hoc Report form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $name
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function getReportForm(Request $request, Response $response, $name)
|
||||
{
|
||||
$this->getLog()->debug('Get report name: '. $name);
|
||||
|
||||
// Get the report Class from the Json File
|
||||
$className = $this->reportService->getReportClass($name);
|
||||
|
||||
// Create the report object
|
||||
$object = $this->reportService->createReportObject($className);
|
||||
|
||||
// We assert the user so that we can use getUser in the report class
|
||||
$object->setUser($this->getUser());
|
||||
|
||||
// Get the twig file template and required data of the report form
|
||||
$form = $object->getReportForm();
|
||||
|
||||
// Show the twig
|
||||
$this->getState()->template = $form->template;
|
||||
$this->getState()->setData([
|
||||
'reportName' => $form->reportName,
|
||||
'reportCategory' => $form->reportCategory,
|
||||
'reportAddBtnTitle' => $form->reportAddBtnTitle,
|
||||
'availableReports' => $this->reportService->listReports(),
|
||||
'defaults' => $form->defaults
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays Ad Hoc/ On demand Report data in charts
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $name
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function getReportData(Request $request, Response $response, $name)
|
||||
{
|
||||
$this->getLog()->debug('Get report name: '. $name);
|
||||
|
||||
// Get the report Class from the Json File
|
||||
$className = $this->reportService->getReportClass($name);
|
||||
|
||||
// Create the report object
|
||||
$object = $this->reportService->createReportObject($className)->setUser($this->getUser());
|
||||
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
// Return data to build chart/table
|
||||
$result = $object->getResults($sanitizedParams);
|
||||
|
||||
//
|
||||
// Output Results
|
||||
// --------------
|
||||
return $response->withJson($result);
|
||||
}
|
||||
|
||||
//</editor-fold>
|
||||
}
|
||||
448
lib/Controller/Resolution.php
Normal file
448
lib/Controller/Resolution.php
Normal file
@@ -0,0 +1,448 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2023 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\ResolutionFactory;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
|
||||
/**
|
||||
* Class Resolution
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class Resolution extends Base
|
||||
{
|
||||
/**
|
||||
* @var ResolutionFactory
|
||||
*/
|
||||
private $resolutionFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param ResolutionFactory $resolutionFactory
|
||||
*/
|
||||
public function __construct($resolutionFactory)
|
||||
{
|
||||
$this->resolutionFactory = $resolutionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the Resolution Page
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
function displayPage(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'resolution-page';
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolution Grid
|
||||
*
|
||||
* @SWG\Get(
|
||||
* path="/resolution",
|
||||
* operationId="resolutionSearch",
|
||||
* tags={"resolution"},
|
||||
* summary="Resolution Search",
|
||||
* description="Search Resolutions this user has access to",
|
||||
* @SWG\Parameter(
|
||||
* name="resolutionId",
|
||||
* in="query",
|
||||
* description="Filter by Resolution Id",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="resolution",
|
||||
* in="query",
|
||||
* description="Filter by Resolution Name",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="partialResolution",
|
||||
* in="query",
|
||||
* description="Filter by Partial Resolution Name",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="enabled",
|
||||
* in="query",
|
||||
* description="Filter by Enabled",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="width",
|
||||
* in="query",
|
||||
* description="Filter by Resolution width",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="height",
|
||||
* in="query",
|
||||
* description="Filter by Resolution height",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/Resolution")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
function grid(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedQueryParams = $this->getSanitizer($request->getQueryParams());
|
||||
// Show enabled
|
||||
$filter = [
|
||||
'enabled' => $sanitizedQueryParams->getInt('enabled', ['default' => -1]),
|
||||
'resolutionId' => $sanitizedQueryParams->getInt('resolutionId'),
|
||||
'resolution' => $sanitizedQueryParams->getString('resolution'),
|
||||
'partialResolution' => $sanitizedQueryParams->getString('partialResolution'),
|
||||
'width' => $sanitizedQueryParams->getInt('width'),
|
||||
'height' => $sanitizedQueryParams->getInt('height'),
|
||||
'orientation' => $sanitizedQueryParams->getString('orientation')
|
||||
];
|
||||
|
||||
$resolutions = $this->resolutionFactory->query($this->gridRenderSort($sanitizedQueryParams), $this->gridRenderFilter($filter, $sanitizedQueryParams));
|
||||
|
||||
foreach ($resolutions as $resolution) {
|
||||
/* @var \Xibo\Entity\Resolution $resolution */
|
||||
|
||||
if ($this->isApi($request))
|
||||
break;
|
||||
|
||||
$resolution->includeProperty('buttons');
|
||||
|
||||
if ($this->getUser()->featureEnabled('resolution.modify')
|
||||
&& $this->getUser()->checkEditable($resolution)
|
||||
) {
|
||||
// Edit Button
|
||||
$resolution->buttons[] = array(
|
||||
'id' => 'resolution_button_edit',
|
||||
'url' => $this->urlFor($request,'resolution.edit.form', ['id' => $resolution->resolutionId]),
|
||||
'text' => __('Edit')
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->getUser()->featureEnabled('resolution.modify')
|
||||
&& $this->getUser()->checkDeleteable($resolution)
|
||||
) {
|
||||
// Delete Button
|
||||
$resolution->buttons[] = array(
|
||||
'id' => 'resolution_button_delete',
|
||||
'url' => $this->urlFor($request,'resolution.delete.form', ['id' => $resolution->resolutionId]),
|
||||
'text' => __('Delete')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->setData($resolutions);
|
||||
$this->getState()->recordsTotal = $this->resolutionFactory->countLast();
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolution Add
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
function addForm(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'resolution-form-add';
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolution Edit Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
function editForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$resolution = $this->resolutionFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($resolution)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'resolution-form-edit';
|
||||
$this->getState()->setData([
|
||||
'resolution' => $resolution,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolution Delete Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
function deleteForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$resolution = $this->resolutionFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($resolution)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'resolution-form-delete';
|
||||
$this->getState()->setData([
|
||||
'resolution' => $resolution,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Resolution
|
||||
*
|
||||
* @SWG\Post(
|
||||
* path="/resolution",
|
||||
* operationId="resolutionAdd",
|
||||
* tags={"resolution"},
|
||||
* summary="Add Resolution",
|
||||
* description="Add new Resolution",
|
||||
* @SWG\Parameter(
|
||||
* name="resolution",
|
||||
* in="formData",
|
||||
* description="A name for the Resolution",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="width",
|
||||
* in="formData",
|
||||
* description="The Display Width of the Resolution",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="height",
|
||||
* in="formData",
|
||||
* description="The Display Height of the Resolution",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/Resolution"),
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new record",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
*/
|
||||
function add(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
/* @var \Xibo\Entity\Resolution $resolution */
|
||||
$resolution = $this->resolutionFactory->create($sanitizedParams->getString('resolution'),
|
||||
$sanitizedParams->getInt('width'),
|
||||
$sanitizedParams->getInt('height'));
|
||||
|
||||
$resolution->userId = $this->getUser()->userId;
|
||||
$resolution->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => sprintf(__('Added %s'), $resolution->resolution),
|
||||
'id' => $resolution->resolutionId,
|
||||
'data' => $resolution
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Resolution
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
* @SWG\Put(
|
||||
* path="/resolution/{resolutionId}",
|
||||
* operationId="resolutionEdit",
|
||||
* tags={"resolution"},
|
||||
* summary="Edit Resolution",
|
||||
* description="Edit new Resolution",
|
||||
* @SWG\Parameter(
|
||||
* name="resolutionId",
|
||||
* in="path",
|
||||
* description="The Resolution ID to Edit",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="resolution",
|
||||
* in="formData",
|
||||
* description="A name for the Resolution",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="width",
|
||||
* in="formData",
|
||||
* description="The Display Width of the Resolution",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="height",
|
||||
* in="formData",
|
||||
* description="The Display Height of the Resolution",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/Resolution")
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
function edit(Request $request, Response $response, $id)
|
||||
{
|
||||
$resolution = $this->resolutionFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($resolution)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$resolution->resolution = $sanitizedParams->getString('resolution');
|
||||
$resolution->width = $sanitizedParams->getInt('width');
|
||||
$resolution->height = $sanitizedParams->getInt('height');
|
||||
$resolution->enabled = $sanitizedParams->getCheckbox('enabled');
|
||||
$resolution->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => sprintf(__('Edited %s'), $resolution->resolution),
|
||||
'id' => $resolution->resolutionId,
|
||||
'data' => $resolution
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Resolution
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
* @SWG\Delete(
|
||||
* path="/resolution/{resolutionId}",
|
||||
* operationId="resolutionDelete",
|
||||
* tags={"resolution"},
|
||||
* summary="Delete Resolution",
|
||||
* description="Delete Resolution",
|
||||
* @SWG\Parameter(
|
||||
* name="resolutionId",
|
||||
* in="path",
|
||||
* description="The Resolution ID to Delete",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
function delete(Request $request, Response $response, $id)
|
||||
{
|
||||
$resolution = $this->resolutionFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($resolution)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$resolution->delete();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => sprintf(__('Deleted %s'), $resolution->resolution),
|
||||
'httpStatus' => 204,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
476
lib/Controller/SavedReport.php
Normal file
476
lib/Controller/SavedReport.php
Normal file
@@ -0,0 +1,476 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2023 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Entity\ReportResult;
|
||||
use Xibo\Factory\MediaFactory;
|
||||
use Xibo\Factory\ReportScheduleFactory;
|
||||
use Xibo\Factory\SavedReportFactory;
|
||||
use Xibo\Factory\UserFactory;
|
||||
use Xibo\Helper\SendFile;
|
||||
use Xibo\Service\ReportServiceInterface;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class SavedReport
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class SavedReport extends Base
|
||||
{
|
||||
/**
|
||||
* @var ReportServiceInterface
|
||||
*/
|
||||
private $reportService;
|
||||
|
||||
/**
|
||||
* @var ReportScheduleFactory
|
||||
*/
|
||||
private $reportScheduleFactory;
|
||||
|
||||
/**
|
||||
* @var SavedReportFactory
|
||||
*/
|
||||
private $savedReportFactory;
|
||||
|
||||
/**
|
||||
* @var MediaFactory
|
||||
*/
|
||||
private $mediaFactory;
|
||||
|
||||
/**
|
||||
* @var UserFactory
|
||||
*/
|
||||
private $userFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param ReportServiceInterface $reportService
|
||||
* @param ReportScheduleFactory $reportScheduleFactory
|
||||
* @param SavedReportFactory $savedReportFactory
|
||||
* @param MediaFactory $mediaFactory
|
||||
* @param UserFactory $userFactory
|
||||
*/
|
||||
public function __construct($reportService, $reportScheduleFactory, $savedReportFactory, $mediaFactory, $userFactory)
|
||||
{
|
||||
$this->reportService = $reportService;
|
||||
$this->reportScheduleFactory = $reportScheduleFactory;
|
||||
$this->savedReportFactory = $savedReportFactory;
|
||||
$this->mediaFactory = $mediaFactory;
|
||||
$this->userFactory = $userFactory;
|
||||
}
|
||||
|
||||
//<editor-fold desc="Saved report">
|
||||
|
||||
/**
|
||||
* Saved report Grid
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function savedReportGrid(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedQueryParams = $this->getSanitizer($request->getQueryParams());
|
||||
|
||||
$savedReports = $this->savedReportFactory->query($this->gridRenderSort($sanitizedQueryParams), $this->gridRenderFilter([
|
||||
'saveAs' => $sanitizedQueryParams->getString('saveAs'),
|
||||
'useRegexForName' => $sanitizedQueryParams->getCheckbox('useRegexForName'),
|
||||
'userId' => $sanitizedQueryParams->getInt('userId'),
|
||||
'reportName' => $sanitizedQueryParams->getString('reportName'),
|
||||
'onlyMyReport' => $sanitizedQueryParams->getCheckbox('onlyMyReport'),
|
||||
'logicalOperatorName' => $sanitizedQueryParams->getString('logicalOperatorName'),
|
||||
], $sanitizedQueryParams));
|
||||
|
||||
foreach ($savedReports as $savedReport) {
|
||||
if ($this->isApi($request)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$savedReport->includeProperty('buttons');
|
||||
|
||||
// If a report class does not comply (i.e., no category or route) we get an error when trying to get the email template
|
||||
// Dont show any button if the report is not compatible
|
||||
// This will also check whether the report feature is enabled or not.
|
||||
$compatible = true;
|
||||
try {
|
||||
// Get report email template
|
||||
$emailTemplate = $this->reportService->getReportEmailTemplate($savedReport->reportName);
|
||||
} catch (NotFoundException $exception) {
|
||||
$compatible = false;
|
||||
}
|
||||
|
||||
if ($compatible) {
|
||||
// Show only convert button for schema version 1
|
||||
if ($savedReport->schemaVersion == 1) {
|
||||
$savedReport->buttons[] = [
|
||||
'id' => 'button_convert_report',
|
||||
'url' => $this->urlFor($request, 'savedreport.convert.form', ['id' => $savedReport->savedReportId]),
|
||||
'text' => __('Convert')
|
||||
];
|
||||
} else {
|
||||
$savedReport->buttons[] = [
|
||||
'id' => 'button_show_report.now',
|
||||
'class' => 'XiboRedirectButton',
|
||||
'url' => $this->urlFor($request, 'savedreport.open', ['id' => $savedReport->savedReportId, 'name' => $savedReport->reportName]),
|
||||
'text' => __('Open')
|
||||
];
|
||||
$savedReport->buttons[] = ['divider' => true];
|
||||
|
||||
$savedReport->buttons[] = [
|
||||
'id' => 'button_goto_report',
|
||||
'class' => 'XiboRedirectButton',
|
||||
'url' => $this->urlFor($request, 'report.form', ['name' => $savedReport->reportName]),
|
||||
'text' => __('Back to Reports')
|
||||
];
|
||||
|
||||
$savedReport->buttons[] = [
|
||||
'id' => 'button_goto_schedule',
|
||||
'class' => 'XiboRedirectButton',
|
||||
'url' => $this->urlFor($request, 'reportschedule.view') . '?reportScheduleId=' . $savedReport->reportScheduleId. '&reportName='.$savedReport->reportName,
|
||||
'text' => __('Go to schedule')
|
||||
];
|
||||
|
||||
$savedReport->buttons[] = ['divider' => true];
|
||||
|
||||
if (!empty($emailTemplate)) {
|
||||
// Export Button
|
||||
$savedReport->buttons[] = [
|
||||
'id' => 'button_export_report',
|
||||
'linkType' => '_self', 'external' => true,
|
||||
'url' => $this->urlFor($request, 'savedreport.export', ['id' => $savedReport->savedReportId, 'name' => $savedReport->reportName]),
|
||||
'text' => __('Export as PDF')
|
||||
];
|
||||
}
|
||||
|
||||
// Delete
|
||||
if ($this->getUser()->checkDeleteable($savedReport)) {
|
||||
// Show the delete button
|
||||
$savedReport->buttons[] = array(
|
||||
'id' => 'savedreport_button_delete',
|
||||
'url' => $this->urlFor($request, 'savedreport.delete.form', ['id' => $savedReport->savedReportId]),
|
||||
'text' => __('Delete'),
|
||||
'multi-select' => true,
|
||||
'dataAttributes' => array(
|
||||
array('name' => 'commit-url', 'value' => $this->urlFor($request, 'savedreport.delete', ['id' => $savedReport->savedReportId])),
|
||||
array('name' => 'commit-method', 'value' => 'delete'),
|
||||
array('name' => 'id', 'value' => 'savedreport_button_delete'),
|
||||
array('name' => 'text', 'value' => __('Delete')),
|
||||
array('name' => 'sort-group', 'value' => 1),
|
||||
array('name' => 'rowtitle', 'value' => $savedReport->saveAs),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->savedReportFactory->countLast();
|
||||
$this->getState()->setData($savedReports);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the Saved Report Page
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function displaySavedReportPage(Request $request, Response $response)
|
||||
{
|
||||
$reportsList = $this->reportService->listReports();
|
||||
$availableReports = [];
|
||||
foreach ($reportsList as $reports) {
|
||||
foreach ($reports as $report) {
|
||||
$availableReports[] = $report;
|
||||
}
|
||||
}
|
||||
|
||||
// Call to render the template
|
||||
$this->getState()->template = 'saved-report-page';
|
||||
$this->getState()->setData([
|
||||
'availableReports' => $availableReports
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report Schedule Delete Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function deleteSavedReportForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$savedReport = $this->savedReportFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($savedReport)) {
|
||||
throw new AccessDeniedException(__('You do not have permissions to delete this report schedule'));
|
||||
}
|
||||
|
||||
$data = [
|
||||
'savedReport' => $savedReport
|
||||
];
|
||||
|
||||
$this->getState()->template = 'savedreport-form-delete';
|
||||
$this->getState()->setData($data);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saved Report Delete
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function savedReportDelete(Request $request, Response $response, $id)
|
||||
{
|
||||
|
||||
$savedReport = $this->savedReportFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($savedReport)) {
|
||||
throw new AccessDeniedException(__('You do not have permissions to delete this report schedule'));
|
||||
}
|
||||
|
||||
$savedReport->load();
|
||||
|
||||
// Delete
|
||||
$savedReport->delete();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Deleted %s'), $savedReport->saveAs)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Saved Report's preview
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @param $name
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function savedReportOpen(Request $request, Response $response, $id, $name)
|
||||
{
|
||||
// Retrieve the saved report result in array
|
||||
/* @var ReportResult $results */
|
||||
$results = $this->reportService->getSavedReportResults($id, $name);
|
||||
|
||||
// Set Template
|
||||
$this->getState()->template = $this->reportService->getSavedReportTemplate($name);
|
||||
|
||||
$this->getState()->setData($results->jsonSerialize());
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports saved report as a PDF file
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @param $name
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Twig\Error\LoaderError
|
||||
* @throws \Twig\Error\RuntimeError
|
||||
* @throws \Twig\Error\SyntaxError
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function savedReportExport(Request $request, Response $response, $id, $name)
|
||||
{
|
||||
$savedReport = $this->savedReportFactory->getById($id);
|
||||
|
||||
// Retrieve the saved report result in array
|
||||
/* @var ReportResult $results */
|
||||
$results = $this->reportService->getSavedReportResults($id, $name);
|
||||
|
||||
// Get the report config
|
||||
$report = $this->reportService->getReportByName($name);
|
||||
if ($report->output_type == 'both' || $report->output_type == 'chart') {
|
||||
$quickChartUrl = $this->getConfig()->getSetting('QUICK_CHART_URL');
|
||||
if (!empty($quickChartUrl)) {
|
||||
$quickChartUrl .= '/chart?width=1000&height=300&c=';
|
||||
|
||||
$script = $this->reportService->getReportChartScript($id, $name);
|
||||
|
||||
// Replace " with ' for the quick chart URL
|
||||
$src = $quickChartUrl . str_replace('"', '\'', $script);
|
||||
|
||||
// If multiple charts needs to be displayed
|
||||
$multipleCharts = [];
|
||||
$chartScriptArray = json_decode($script, true);
|
||||
foreach ($chartScriptArray as $key => $chartData) {
|
||||
$multipleCharts[$key] = $quickChartUrl . str_replace('"', '\'', json_encode($chartData));
|
||||
}
|
||||
} else {
|
||||
$placeholder = __('Chart could not be drawn because the CMS has not been configured with a Quick Chart URL.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($report->output_type == 'both' || $report->output_type == 'table') { // only for tablebased report
|
||||
$tableData = $results->table;
|
||||
}
|
||||
|
||||
// Get report email template to export
|
||||
$emailTemplate = $this->reportService->getReportEmailTemplate($name);
|
||||
|
||||
if (!empty($emailTemplate)) {
|
||||
// Save PDF attachment
|
||||
$showLogo = $this->getConfig()->getSetting('REPORTS_EXPORT_SHOW_LOGO', 1) == 1;
|
||||
$body = $this->getView()->fetch(
|
||||
$emailTemplate,
|
||||
[
|
||||
'header' => $report->description,
|
||||
'logo' => ($showLogo) ? $this->getConfig()->uri('img/xibologo.png', true) : null,
|
||||
'title' => $savedReport->saveAs,
|
||||
'metadata' => $results->metadata,
|
||||
'tableData' => $tableData ?? null,
|
||||
'src' => $src ?? null,
|
||||
'multipleCharts' => $multipleCharts ?? null,
|
||||
'placeholder' => $placeholder ?? null
|
||||
]
|
||||
);
|
||||
|
||||
$fileName = $this->getConfig()->getSetting('LIBRARY_LOCATION') . 'temp/saved_report_' . $id . '.pdf';
|
||||
|
||||
try {
|
||||
$mpdf = new \Mpdf\Mpdf([
|
||||
'tempDir' => $this->getConfig()->getSetting('LIBRARY_LOCATION') . '/temp',
|
||||
'orientation' => 'L',
|
||||
'mode' => 'c',
|
||||
'margin_left' => 20,
|
||||
'margin_right' => 20,
|
||||
'margin_top' => 20,
|
||||
'margin_bottom' => 20,
|
||||
'margin_header' => 5,
|
||||
'margin_footer' => 15
|
||||
]);
|
||||
$mpdf->setFooter('Page {PAGENO}') ;
|
||||
$mpdf->SetDisplayMode('fullpage');
|
||||
$stylesheet = file_get_contents($this->getConfig()->uri('css/email-report.css', true));
|
||||
$mpdf->WriteHTML($stylesheet, 1);
|
||||
$mpdf->WriteHTML($body);
|
||||
$mpdf->Output($fileName, \Mpdf\Output\Destination::FILE);
|
||||
} catch (\Exception $error) {
|
||||
$this->getLog()->error($error->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Return the file with PHP
|
||||
$this->setNoOutput(true);
|
||||
|
||||
return $this->render($request, SendFile::decorateResponse(
|
||||
$response,
|
||||
$this->getConfig()->getSetting('SENDFILE_MODE'),
|
||||
$fileName
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Saved Report Convert Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function convertSavedReportForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$savedReport = $this->savedReportFactory->getById($id);
|
||||
|
||||
$data = [
|
||||
'savedReport' => $savedReport
|
||||
];
|
||||
|
||||
$this->getState()->template = 'savedreport-form-convert';
|
||||
$this->getState()->setData($data);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Saved Report from Schema Version 1 to 2
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @param $name
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function savedReportConvert(Request $request, Response $response, $id, $name)
|
||||
{
|
||||
$savedReport = $this->savedReportFactory->getById($id);
|
||||
|
||||
if ($savedReport->schemaVersion == 2) {
|
||||
throw new GeneralException(__('This report has already been converted to the latest version.'));
|
||||
}
|
||||
|
||||
// Convert Result to schemaVersion 2
|
||||
$this->reportService->convertSavedReportResults($id, $name);
|
||||
|
||||
$savedReport->schemaVersion = 2;
|
||||
$savedReport->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Saved Report Converted to Schema Version 2'))
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
//</editor-fold>
|
||||
}
|
||||
2787
lib/Controller/Schedule.php
Normal file
2787
lib/Controller/Schedule.php
Normal file
File diff suppressed because it is too large
Load Diff
763
lib/Controller/ScheduleReport.php
Normal file
763
lib/Controller/ScheduleReport.php
Normal file
@@ -0,0 +1,763 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2023 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Slim\Views\Twig;
|
||||
use Xibo\Entity\ReportSchedule;
|
||||
use Xibo\Factory\MediaFactory;
|
||||
use Xibo\Factory\ReportScheduleFactory;
|
||||
use Xibo\Factory\SavedReportFactory;
|
||||
use Xibo\Factory\UserFactory;
|
||||
use Xibo\Helper\DateFormatHelper;
|
||||
use Xibo\Helper\SanitizerService;
|
||||
use Xibo\Service\ConfigServiceInterface;
|
||||
use Xibo\Service\LogServiceInterface;
|
||||
use Xibo\Service\ReportServiceInterface;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class Report
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class ScheduleReport extends Base
|
||||
{
|
||||
/**
|
||||
* @var ReportServiceInterface
|
||||
*/
|
||||
private $reportService;
|
||||
|
||||
/**
|
||||
* @var ReportScheduleFactory
|
||||
*/
|
||||
private $reportScheduleFactory;
|
||||
|
||||
/**
|
||||
* @var SavedReportFactory
|
||||
*/
|
||||
private $savedReportFactory;
|
||||
|
||||
/**
|
||||
* @var MediaFactory
|
||||
*/
|
||||
private $mediaFactory;
|
||||
|
||||
/**
|
||||
* @var UserFactory
|
||||
*/
|
||||
private $userFactory;
|
||||
|
||||
/**
|
||||
* @var Twig
|
||||
*/
|
||||
private $view;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param LogServiceInterface $log
|
||||
* @param SanitizerService $sanitizerService
|
||||
* @param \Xibo\Helper\ApplicationState $state
|
||||
* @param \Xibo\Entity\User $user
|
||||
* @param \Xibo\Service\HelpServiceInterface $help
|
||||
* @param ConfigServiceInterface $config
|
||||
* @param Twig $view
|
||||
* @param ReportServiceInterface $reportService
|
||||
* @param ReportScheduleFactory $reportScheduleFactory
|
||||
* @param SavedReportFactory $savedReportFactory
|
||||
* @param MediaFactory $mediaFactory
|
||||
* @param UserFactory $userFactory
|
||||
*/
|
||||
public function __construct($reportService, $reportScheduleFactory, $savedReportFactory, $mediaFactory, $userFactory)
|
||||
{
|
||||
$this->reportService = $reportService;
|
||||
$this->reportScheduleFactory = $reportScheduleFactory;
|
||||
$this->savedReportFactory = $savedReportFactory;
|
||||
$this->mediaFactory = $mediaFactory;
|
||||
$this->userFactory = $userFactory;
|
||||
}
|
||||
|
||||
/// //<editor-fold desc="Report Schedules">
|
||||
|
||||
/**
|
||||
* Report Schedule Grid
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function reportScheduleGrid(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedQueryParams = $this->getSanitizer($request->getQueryParams());
|
||||
|
||||
$reportSchedules = $this->reportScheduleFactory->query($this->gridRenderSort($sanitizedQueryParams), $this->gridRenderFilter([
|
||||
'name' => $sanitizedQueryParams->getString('name'),
|
||||
'useRegexForName' => $sanitizedQueryParams->getCheckbox('useRegexForName'),
|
||||
'userId' => $sanitizedQueryParams->getInt('userId'),
|
||||
'reportScheduleId' => $sanitizedQueryParams->getInt('reportScheduleId'),
|
||||
'reportName' => $sanitizedQueryParams->getString('reportName'),
|
||||
'onlyMySchedules' => $sanitizedQueryParams->getCheckbox('onlyMySchedules'),
|
||||
'logicalOperatorName' => $sanitizedQueryParams->getString('logicalOperatorName'),
|
||||
], $sanitizedQueryParams));
|
||||
|
||||
/** @var \Xibo\Entity\ReportSchedule $reportSchedule */
|
||||
foreach ($reportSchedules as $reportSchedule) {
|
||||
if ($this->isApi($request)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$reportSchedule->includeProperty('buttons');
|
||||
|
||||
$cron = \Cron\CronExpression::factory($reportSchedule->schedule);
|
||||
|
||||
if ($reportSchedule->lastRunDt == 0) {
|
||||
$nextRunDt = Carbon::now()->format('U');
|
||||
} else {
|
||||
$nextRunDt = $cron->getNextRunDate(Carbon::createFromTimestamp($reportSchedule->lastRunDt))->format('U');
|
||||
}
|
||||
|
||||
$reportSchedule->setUnmatchedProperty('nextRunDt', $nextRunDt);
|
||||
|
||||
// Ad hoc report name
|
||||
$adhocReportName = $reportSchedule->reportName;
|
||||
|
||||
// We get the report description
|
||||
try {
|
||||
$reportSchedule->reportName = $this->reportService->getReportByName($reportSchedule->reportName)->description;
|
||||
} catch (NotFoundException $notFoundException) {
|
||||
$reportSchedule->reportName = __('Unknown or removed report.');
|
||||
}
|
||||
|
||||
switch ($reportSchedule->schedule) {
|
||||
case ReportSchedule::$SCHEDULE_DAILY:
|
||||
$reportSchedule->schedule = __('Run once a day, midnight');
|
||||
break;
|
||||
|
||||
case ReportSchedule::$SCHEDULE_WEEKLY:
|
||||
$reportSchedule->schedule = __('Run once a week, midnight on Monday');
|
||||
|
||||
break;
|
||||
|
||||
case ReportSchedule::$SCHEDULE_MONTHLY:
|
||||
$reportSchedule->schedule = __('Run once a month, midnight, first of month');
|
||||
|
||||
break;
|
||||
|
||||
case ReportSchedule::$SCHEDULE_YEARLY:
|
||||
$reportSchedule->schedule = __('Run once a year, midnight, Jan. 1');
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
switch ($reportSchedule->isActive) {
|
||||
case 1:
|
||||
$reportSchedule->setUnmatchedProperty('isActiveDescription', __('This report schedule is active'));
|
||||
break;
|
||||
|
||||
default:
|
||||
$reportSchedule->setUnmatchedProperty('isActiveDescription', __('This report schedule is paused'));
|
||||
}
|
||||
|
||||
if ($reportSchedule->getLastSavedReportId() > 0) {
|
||||
$lastSavedReport = $this->savedReportFactory->getById($reportSchedule->getLastSavedReportId());
|
||||
|
||||
// Hide this for schema version 1
|
||||
if ($lastSavedReport->schemaVersion != 1) {
|
||||
// Open Last Saved Report
|
||||
$reportSchedule->buttons[] = [
|
||||
'id' => 'reportSchedule_lastsaved_report_button',
|
||||
'class' => 'XiboRedirectButton',
|
||||
'url' => $this->urlFor($request, 'savedreport.open', ['id' => $lastSavedReport->savedReportId, 'name' => $lastSavedReport->reportName]),
|
||||
'text' => __('Open last saved report')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Back to Reports
|
||||
$reportSchedule->buttons[] = [
|
||||
'id' => 'reportSchedule_goto_report_button',
|
||||
'class' => 'XiboRedirectButton',
|
||||
'url' => $this->urlFor($request, 'report.form', ['name' => $adhocReportName]),
|
||||
'text' => __('Back to Reports')
|
||||
];
|
||||
$reportSchedule->buttons[] = ['divider' => true];
|
||||
|
||||
// Edit
|
||||
if ($this->getUser()->featureEnabled('report.scheduling')) {
|
||||
$reportSchedule->buttons[] = [
|
||||
'id' => 'reportSchedule_edit_button',
|
||||
'url' => $this->urlFor($request, 'reportschedule.edit.form', ['id' => $reportSchedule->reportScheduleId]),
|
||||
'text' => __('Edit')
|
||||
];
|
||||
}
|
||||
|
||||
// Reset to previous run
|
||||
if ($this->getUser()->isSuperAdmin()) {
|
||||
$reportSchedule->buttons[] = [
|
||||
'id' => 'reportSchedule_reset_button',
|
||||
'url' => $this->urlFor($request, 'reportschedule.reset.form', ['id' => $reportSchedule->reportScheduleId]),
|
||||
'text' => __('Reset to previous run')
|
||||
];
|
||||
}
|
||||
|
||||
// Delete
|
||||
if ($this->getUser()->featureEnabled('report.scheduling')
|
||||
&& $this->getUser()->checkDeleteable($reportSchedule)) {
|
||||
// Show the delete button
|
||||
$reportSchedule->buttons[] = [
|
||||
'id' => 'reportschedule_button_delete',
|
||||
'url' => $this->urlFor($request, 'reportschedule.delete.form', ['id' => $reportSchedule->reportScheduleId]),
|
||||
'text' => __('Delete'),
|
||||
'multi-select' => true,
|
||||
'dataAttributes' => [
|
||||
['name' => 'commit-url', 'value' => $this->urlFor($request, 'reportschedule.delete', ['id' => $reportSchedule->reportScheduleId])],
|
||||
['name' => 'commit-method', 'value' => 'delete'],
|
||||
['name' => 'id', 'value' => 'reportschedule_button_delete'],
|
||||
['name' => 'text', 'value' => __('Delete')],
|
||||
['name' => 'sort-group', 'value' => 1],
|
||||
['name' => 'rowtitle', 'value' => $reportSchedule->name]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
// Toggle active
|
||||
if ($this->getUser()->featureEnabled('report.scheduling')) {
|
||||
$reportSchedule->buttons[] = [
|
||||
'id' => 'reportSchedule_toggleactive_button',
|
||||
'url' => $this->urlFor($request, 'reportschedule.toggleactive.form', ['id' => $reportSchedule->reportScheduleId]),
|
||||
'text' => ($reportSchedule->isActive == 1) ? __('Pause') : __('Resume')
|
||||
];
|
||||
}
|
||||
|
||||
// Delete all saved report
|
||||
$savedreports = $this->savedReportFactory->query(null, ['reportScheduleId'=> $reportSchedule->reportScheduleId]);
|
||||
if ((count($savedreports) > 0)
|
||||
&& $this->getUser()->checkDeleteable($reportSchedule)
|
||||
&& $this->getUser()->featureEnabled('report.saving')
|
||||
) {
|
||||
$reportSchedule->buttons[] = ['divider' => true];
|
||||
|
||||
$reportSchedule->buttons[] = array(
|
||||
'id' => 'reportschedule_button_delete_all',
|
||||
'url' => $this->urlFor($request, 'reportschedule.deleteall.form', ['id' => $reportSchedule->reportScheduleId]),
|
||||
'text' => __('Delete all saved reports'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->reportScheduleFactory->countLast();
|
||||
$this->getState()->setData($reportSchedules);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report Schedule Reset
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function reportScheduleReset(Request $request, Response $response, $id)
|
||||
{
|
||||
$reportSchedule = $this->reportScheduleFactory->getById($id);
|
||||
|
||||
$this->getLog()->debug('Reset Report Schedule: '.$reportSchedule->name);
|
||||
|
||||
// Go back to previous run date
|
||||
$reportSchedule->lastSavedReportId = 0;
|
||||
$reportSchedule->lastRunDt = $reportSchedule->previousRunDt;
|
||||
$reportSchedule->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => 'Success'
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report Schedule Add
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function reportScheduleAdd(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$name = $sanitizedParams->getString('name');
|
||||
$reportName = $request->getParam('reportName', null);
|
||||
$fromDt = $sanitizedParams->getDate('fromDt', ['default' => 0]);
|
||||
$toDt = $sanitizedParams->getDate('toDt', ['default' => 0]);
|
||||
$today = Carbon::now()->startOfDay()->format('U');
|
||||
|
||||
// from and todt should be greater than today
|
||||
if (!empty($fromDt)) {
|
||||
$fromDt = $fromDt->format('U');
|
||||
if ($fromDt < $today) {
|
||||
throw new InvalidArgumentException(__('Start time cannot be earlier than today'), 'fromDt');
|
||||
}
|
||||
}
|
||||
if (!empty($toDt)) {
|
||||
$toDt = $toDt->format('U');
|
||||
if ($toDt < $today) {
|
||||
throw new InvalidArgumentException(__('End time cannot be earlier than today'), 'toDt');
|
||||
}
|
||||
}
|
||||
|
||||
$this->getLog()->debug('Add Report Schedule: '. $name);
|
||||
|
||||
// Set Report Schedule form data
|
||||
$result = $this->reportService->setReportScheduleFormData($reportName, $request);
|
||||
|
||||
$reportSchedule = $this->reportScheduleFactory->createEmpty();
|
||||
$reportSchedule->name = $name;
|
||||
$reportSchedule->lastSavedReportId = 0;
|
||||
$reportSchedule->reportName = $reportName;
|
||||
$reportSchedule->filterCriteria = $result['filterCriteria'];
|
||||
$reportSchedule->schedule = $result['schedule'];
|
||||
$reportSchedule->lastRunDt = 0;
|
||||
$reportSchedule->previousRunDt = 0;
|
||||
$reportSchedule->fromDt = $fromDt;
|
||||
$reportSchedule->toDt = $toDt;
|
||||
$reportSchedule->userId = $this->getUser()->userId;
|
||||
$reportSchedule->createdDt = Carbon::now()->format('U');
|
||||
|
||||
$reportSchedule->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => __('Added Report Schedule'),
|
||||
'id' => $reportSchedule->reportScheduleId,
|
||||
'data' => $reportSchedule
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report Schedule Edit
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function reportScheduleEdit(Request $request, Response $response, $id)
|
||||
{
|
||||
$reportSchedule = $this->reportScheduleFactory->getById($id, 0);
|
||||
|
||||
if ($reportSchedule->getOwnerId() != $this->getUser()->userId && $this->getUser()->userTypeId != 1) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$name = $sanitizedParams->getString('name');
|
||||
$reportName = $request->getParam('reportName', null);
|
||||
$fromDt = $sanitizedParams->getDate('fromDt', ['default' => 0]);
|
||||
$toDt = $sanitizedParams->getDate('toDt', ['default' => 0]);
|
||||
$today = Carbon::now()->startOfDay()->format('U');
|
||||
|
||||
// from and todt should be greater than today
|
||||
if (!empty($fromDt)) {
|
||||
$fromDt = $fromDt->format('U');
|
||||
if ($fromDt < $today) {
|
||||
throw new InvalidArgumentException(__('Start time cannot be earlier than today'), 'fromDt');
|
||||
}
|
||||
}
|
||||
if (!empty($toDt)) {
|
||||
$toDt = $toDt->format('U');
|
||||
if ($toDt < $today) {
|
||||
throw new InvalidArgumentException(__('End time cannot be earlier than today'), 'toDt');
|
||||
}
|
||||
}
|
||||
|
||||
$reportSchedule->name = $name;
|
||||
$reportSchedule->fromDt = $fromDt;
|
||||
$reportSchedule->toDt = $toDt;
|
||||
$reportSchedule->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 200,
|
||||
'message' => sprintf(__('Edited %s'), $reportSchedule->name),
|
||||
'id' => $reportSchedule->reportScheduleId,
|
||||
'data' => $reportSchedule
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report Schedule Delete
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function reportScheduleDelete(Request $request, Response $response, $id)
|
||||
{
|
||||
|
||||
$reportSchedule = $this->reportScheduleFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($reportSchedule)) {
|
||||
throw new AccessDeniedException(__('You do not have permissions to delete this report schedule'));
|
||||
}
|
||||
|
||||
try {
|
||||
$reportSchedule->delete();
|
||||
} catch (\RuntimeException $e) {
|
||||
throw new InvalidArgumentException(__('Report schedule cannot be deleted. Please ensure there are no saved reports against the schedule.'), 'reportScheduleId');
|
||||
}
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Deleted %s'), $reportSchedule->name)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report Schedule Delete All Saved Report
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function reportScheduleDeleteAllSavedReport(Request $request, Response $response, $id)
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$reportSchedule = $this->reportScheduleFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($reportSchedule)) {
|
||||
throw new AccessDeniedException(__('You do not have permissions to delete the saved report of this report schedule'));
|
||||
}
|
||||
|
||||
// Get all saved reports of the report schedule
|
||||
$savedReports = $this->savedReportFactory->query(
|
||||
null,
|
||||
[
|
||||
'reportScheduleId' => $reportSchedule->reportScheduleId
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
foreach ($savedReports as $savedreport) {
|
||||
try {
|
||||
$savedreport->load();
|
||||
|
||||
// Delete
|
||||
$savedreport->delete();
|
||||
} catch (\RuntimeException $e) {
|
||||
throw new InvalidArgumentException(__('Saved report cannot be deleted'), 'savedReportId');
|
||||
}
|
||||
}
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Deleted all saved reports of %s'), $reportSchedule->name)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report Schedule Toggle Active
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function reportScheduleToggleActive(Request $request, Response $response, $id)
|
||||
{
|
||||
|
||||
$reportSchedule = $this->reportScheduleFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($reportSchedule)) {
|
||||
throw new AccessDeniedException(__('You do not have permissions to pause/resume this report schedule'));
|
||||
}
|
||||
|
||||
if ($reportSchedule->isActive == 1) {
|
||||
$reportSchedule->isActive = 0;
|
||||
$msg = sprintf(__('Paused %s'), $reportSchedule->name);
|
||||
} else {
|
||||
$reportSchedule->isActive = 1;
|
||||
$msg = sprintf(__('Resumed %s'), $reportSchedule->name);
|
||||
}
|
||||
|
||||
$reportSchedule->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => $msg
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the Report Schedule Page
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function displayReportSchedulePage(Request $request, Response $response)
|
||||
{
|
||||
$reportsList = $this->reportService->listReports();
|
||||
$availableReports = [];
|
||||
foreach ($reportsList as $reports) {
|
||||
foreach ($reports as $report) {
|
||||
$availableReports[] = $report;
|
||||
}
|
||||
}
|
||||
|
||||
// Call to render the template
|
||||
$this->getState()->template = 'report-schedule-page';
|
||||
$this->getState()->setData([
|
||||
'availableReports' => $availableReports
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an Add form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function addReportScheduleForm(Request $request, Response $response)
|
||||
{
|
||||
|
||||
$reportName = $request->getParam('reportName', null);
|
||||
|
||||
// Populate form title and hidden fields
|
||||
$formData = $this->reportService->getReportScheduleFormData($reportName, $request);
|
||||
|
||||
$template = $formData['template'];
|
||||
$this->getState()->template = $template;
|
||||
$this->getState()->setData($formData['data']);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report Schedule Edit Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function editReportScheduleForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$reportSchedule = $this->reportScheduleFactory->getById($id, 0);
|
||||
|
||||
if ($reportSchedule->fromDt > 0) {
|
||||
$reportSchedule->fromDt = Carbon::createFromTimestamp($reportSchedule->fromDt)->format(DateFormatHelper::getSystemFormat());
|
||||
} else {
|
||||
$reportSchedule->fromDt = '';
|
||||
}
|
||||
|
||||
if ($reportSchedule->toDt > 0) {
|
||||
$reportSchedule->toDt = Carbon::createFromTimestamp($reportSchedule->toDt)->format(DateFormatHelper::getSystemFormat());
|
||||
} else {
|
||||
$reportSchedule->toDt = '';
|
||||
}
|
||||
|
||||
$this->getState()->template = 'reportschedule-form-edit';
|
||||
$this->getState()->setData([
|
||||
'reportSchedule' => $reportSchedule
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report Schedule Reset Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function resetReportScheduleForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$reportSchedule = $this->reportScheduleFactory->getById($id, 0);
|
||||
|
||||
// Only admin can reset it
|
||||
if ($this->getUser()->userTypeId != 1) {
|
||||
throw new AccessDeniedException(__('You do not have permissions to reset this report schedule'));
|
||||
}
|
||||
|
||||
$data = [
|
||||
'reportSchedule' => $reportSchedule
|
||||
];
|
||||
|
||||
$this->getState()->template = 'reportschedule-form-reset';
|
||||
$this->getState()->setData($data);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report Schedule Delete Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function deleteReportScheduleForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$reportSchedule = $this->reportScheduleFactory->getById($id, 0);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($reportSchedule)) {
|
||||
throw new AccessDeniedException(__('You do not have permissions to delete this report schedule'));
|
||||
}
|
||||
|
||||
$data = [
|
||||
'reportSchedule' => $reportSchedule
|
||||
];
|
||||
|
||||
$this->getState()->template = 'reportschedule-form-delete';
|
||||
$this->getState()->setData($data);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report Schedule Delete All Saved Report Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function deleteAllSavedReportReportScheduleForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$reportSchedule = $this->reportScheduleFactory->getById($id, 0);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($reportSchedule)) {
|
||||
throw new AccessDeniedException(__('You do not have permissions to delete saved reports of this report schedule'));
|
||||
}
|
||||
|
||||
$data = [
|
||||
'reportSchedule' => $reportSchedule
|
||||
];
|
||||
|
||||
$this->getState()->template = 'reportschedule-form-deleteall';
|
||||
$this->getState()->setData($data);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report Schedule Toggle Active Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function toggleActiveReportScheduleForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$reportSchedule = $this->reportScheduleFactory->getById($id, 0);
|
||||
|
||||
if (!$this->getUser()->checkEditable($reportSchedule)) {
|
||||
throw new AccessDeniedException(__('You do not have permissions to pause/resume this report schedule'));
|
||||
}
|
||||
|
||||
$data = [
|
||||
'reportSchedule' => $reportSchedule
|
||||
];
|
||||
|
||||
$this->getState()->template = 'reportschedule-form-toggleactive';
|
||||
$this->getState()->setData($data);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
//</editor-fold>
|
||||
}
|
||||
182
lib/Controller/Sessions.php
Normal file
182
lib/Controller/Sessions.php
Normal file
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\SessionFactory;
|
||||
use Xibo\Helper\DateFormatHelper;
|
||||
use Xibo\Storage\StorageServiceInterface;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
|
||||
/**
|
||||
* Class Sessions
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class Sessions extends Base
|
||||
{
|
||||
/**
|
||||
* @var StorageServiceInterface
|
||||
*/
|
||||
private $store;
|
||||
|
||||
/**
|
||||
* @var SessionFactory
|
||||
*/
|
||||
private $sessionFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param StorageServiceInterface $store
|
||||
* @param SessionFactory $sessionFactory
|
||||
*/
|
||||
public function __construct($store, $sessionFactory)
|
||||
{
|
||||
$this->store = $store;
|
||||
$this->sessionFactory = $sessionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
function displayPage(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'sessions-page';
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function grid(Request $request, Response $response): Response|\Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
$sanitizedQueryParams = $this->getSanitizer($request->getQueryParams());
|
||||
|
||||
$sessions = $this->sessionFactory->query($this->gridRenderSort($sanitizedQueryParams), $this->gridRenderFilter([
|
||||
'type' => $sanitizedQueryParams->getString('type'),
|
||||
'fromDt' => $sanitizedQueryParams->getString('fromDt')
|
||||
], $sanitizedQueryParams));
|
||||
|
||||
foreach ($sessions as $row) {
|
||||
/* @var \Xibo\Entity\Session $row */
|
||||
|
||||
// Normalise the date
|
||||
$row->lastAccessed =
|
||||
Carbon::createFromTimeString($row->lastAccessed)?->format(DateFormatHelper::getSystemFormat());
|
||||
|
||||
if (!$this->isApi($request) && $this->getUser()->isSuperAdmin()) {
|
||||
$row->includeProperty('buttons');
|
||||
|
||||
// No buttons on expired sessions
|
||||
if ($row->isExpired == 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// logout, current user/session
|
||||
if ($row->userId === $this->getUser()->userId && session_id() === $row->sessionId) {
|
||||
$url = $this->urlFor($request, 'logout');
|
||||
} else {
|
||||
// logout, different user/session
|
||||
$url = $this->urlFor(
|
||||
$request,
|
||||
'sessions.confirm.logout.form',
|
||||
['id' => $row->userId]
|
||||
);
|
||||
}
|
||||
|
||||
$row->buttons[] = [
|
||||
'id' => 'sessions_button_logout',
|
||||
'url' => $url,
|
||||
'text' => __('Logout')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->sessionFactory->countLast();
|
||||
$this->getState()->setData($sessions);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm Logout Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param int $id The UserID
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function confirmLogoutForm(Request $request, Response $response, $id)
|
||||
{
|
||||
if ($this->getUser()->userTypeId != 1) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'sessions-form-confirm-logout';
|
||||
$this->getState()->setData([
|
||||
'userId' => $id,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function logout(Request $request, Response $response, $id)
|
||||
{
|
||||
if ($this->getUser()->userTypeId != 1) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// We log out all of this user's sessions.
|
||||
$this->sessionFactory->expireByUserId($id);
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => __('User Logged Out.')
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
793
lib/Controller/Settings.php
Normal file
793
lib/Controller/Settings.php
Normal file
@@ -0,0 +1,793 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2025 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Respect\Validation\Validator as v;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Event\PlaylistMaxNumberChangedEvent;
|
||||
use Xibo\Event\SystemUserChangedEvent;
|
||||
use Xibo\Factory\LayoutFactory;
|
||||
use Xibo\Factory\TransitionFactory;
|
||||
use Xibo\Factory\UserFactory;
|
||||
use Xibo\Factory\UserGroupFactory;
|
||||
use Xibo\Helper\DateFormatHelper;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class Settings
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class Settings extends Base
|
||||
{
|
||||
/** @var LayoutFactory */
|
||||
private $layoutFactory;
|
||||
|
||||
/** @var UserGroupFactory */
|
||||
private $userGroupFactory;
|
||||
|
||||
/** @var TransitionFactory */
|
||||
private $transitionfactory;
|
||||
|
||||
/** @var UserFactory */
|
||||
private $userFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param LayoutFactory $layoutFactory
|
||||
* @param UserGroupFactory $userGroupFactory
|
||||
* @param TransitionFactory $transitionfactory
|
||||
* @param UserFactory $userFactory
|
||||
*/
|
||||
public function __construct($layoutFactory, $userGroupFactory, $transitionfactory, $userFactory)
|
||||
{
|
||||
$this->layoutFactory = $layoutFactory;
|
||||
$this->userGroupFactory = $userGroupFactory;
|
||||
$this->transitionfactory = $transitionfactory;
|
||||
$this->userFactory = $userFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display Page
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response)
|
||||
{
|
||||
// Should we hide other themes?
|
||||
$themes = [];
|
||||
$hideThemes = $this->getConfig()->getThemeConfig('hide_others');
|
||||
|
||||
if (!$hideThemes) {
|
||||
// Get all theme options
|
||||
$directory = new \RecursiveDirectoryIterator(PROJECT_ROOT . '/web/theme', \FilesystemIterator::SKIP_DOTS);
|
||||
$filter = new \RecursiveCallbackFilterIterator($directory, function($current, $key, $iterator) {
|
||||
|
||||
if ($current->isDir()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return strpos($current->getFilename(), 'config.php') === 0;
|
||||
});
|
||||
|
||||
$iterator = new \RecursiveIteratorIterator($filter);
|
||||
|
||||
// Add options for all themes installed
|
||||
foreach($iterator as $file) {
|
||||
/* @var \SplFileInfo $file */
|
||||
$this->getLog()->debug('Found ' . $file->getPath());
|
||||
|
||||
// Include the config file
|
||||
include $file->getPath() . '/' . $file->getFilename();
|
||||
|
||||
$themes[] = ['id' => basename($file->getPath()), 'value' => $config['theme_name']];
|
||||
}
|
||||
}
|
||||
|
||||
// A list of timezones
|
||||
$timeZones = [];
|
||||
foreach (DateFormatHelper::timezoneList() as $key => $value) {
|
||||
$timeZones[] = ['id' => $key, 'value' => $value];
|
||||
}
|
||||
|
||||
// A list of languages
|
||||
// Build an array of supported languages
|
||||
$languages = [];
|
||||
$localeDir = PROJECT_ROOT . '/locale';
|
||||
foreach (array_map('basename', glob($localeDir . '/*.mo')) as $lang) {
|
||||
// Trim the .mo off the end
|
||||
$lang = str_replace('.mo', '', $lang);
|
||||
$languages[] = ['id' => $lang, 'value' => $lang];
|
||||
}
|
||||
|
||||
// The default layout
|
||||
try {
|
||||
$defaultLayout = $this->layoutFactory->getById($this->getConfig()->getSetting('DEFAULT_LAYOUT'));
|
||||
} catch (NotFoundException $notFoundException) {
|
||||
$defaultLayout = null;
|
||||
}
|
||||
|
||||
// The system User
|
||||
try {
|
||||
$systemUser = $this->userFactory->getById($this->getConfig()->getSetting('SYSTEM_USER'));
|
||||
} catch (NotFoundException $notFoundException) {
|
||||
$systemUser = null;
|
||||
}
|
||||
|
||||
// The default user group
|
||||
try {
|
||||
$defaultUserGroup = $this->userGroupFactory->getById($this->getConfig()->getSetting('DEFAULT_USERGROUP'));
|
||||
} catch (NotFoundException $notFoundException) {
|
||||
$defaultUserGroup = null;
|
||||
}
|
||||
|
||||
// The default Transition In
|
||||
try {
|
||||
$defaultTransitionIn = $this->transitionfactory->getByCode($this->getConfig()->getSetting('DEFAULT_TRANSITION_IN'));
|
||||
} catch (NotFoundException $notFoundException) {
|
||||
$defaultTransitionIn = null;
|
||||
}
|
||||
|
||||
// The default Transition Out
|
||||
try {
|
||||
$defaultTransitionOut = $this->transitionfactory->getByCode($this->getConfig()->getSetting('DEFAULT_TRANSITION_OUT'));
|
||||
} catch (NotFoundException $notFoundException) {
|
||||
$defaultTransitionOut = null;
|
||||
}
|
||||
|
||||
// Work out whether we're in a valid elevate log period
|
||||
$elevateLogUntil = $this->getConfig()->getSetting('ELEVATE_LOG_UNTIL');
|
||||
|
||||
if ($elevateLogUntil != null) {
|
||||
$elevateLogUntil = intval($elevateLogUntil);
|
||||
|
||||
if ($elevateLogUntil <= Carbon::now()->format('U')) {
|
||||
$elevateLogUntil = null;
|
||||
} else {
|
||||
$elevateLogUntil = Carbon::createFromTimestamp($elevateLogUntil)->format(DateFormatHelper::getSystemFormat());
|
||||
}
|
||||
}
|
||||
|
||||
// Render the Theme and output
|
||||
$this->getState()->template = 'settings-page';
|
||||
$this->getState()->setData([
|
||||
'hideThemes' => $hideThemes,
|
||||
'themes' => $themes,
|
||||
'languages' => $languages,
|
||||
'timeZones' => $timeZones,
|
||||
'defaultLayout' => $defaultLayout,
|
||||
'defaultUserGroup' => $defaultUserGroup,
|
||||
'elevateLogUntil' => $elevateLogUntil,
|
||||
'defaultTransitionIn' => $defaultTransitionIn,
|
||||
'defaultTransitionOut' => $defaultTransitionOut,
|
||||
'systemUser' => $systemUser
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update settings
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @phpcs:disable Generic.Files.LineLength.TooLong
|
||||
*/
|
||||
public function update(Request $request, Response $response)
|
||||
{
|
||||
if (!$this->getUser()->isSuperAdmin()) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$changedSettings = [];
|
||||
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
// Pull in all of the settings we're expecting to be submitted with this form.
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('LIBRARY_LOCATION')) {
|
||||
$libraryLocation = $sanitizedParams->getString('LIBRARY_LOCATION');
|
||||
|
||||
// Validate library location
|
||||
// Check for a trailing slash and add it if its not there
|
||||
$libraryLocation = rtrim($libraryLocation, '/');
|
||||
$libraryLocation = rtrim($libraryLocation, '\\') . DIRECTORY_SEPARATOR;
|
||||
|
||||
// Attempt to add the directory specified
|
||||
if (!file_exists($libraryLocation . 'temp')) {
|
||||
// Make the directory with broad permissions recursively (so will add the whole path)
|
||||
mkdir($libraryLocation . 'temp', 0777, true);
|
||||
}
|
||||
|
||||
if (!is_writable($libraryLocation . 'temp')) {
|
||||
throw new InvalidArgumentException(__('The Library Location you have picked is not writeable'), 'LIBRARY_LOCATION');
|
||||
}
|
||||
|
||||
$this->handleChangedSettings('LIBRARY_LOCATION', $this->getConfig()->getSetting('LIBRARY_LOCATION'), $libraryLocation, $changedSettings);
|
||||
$this->getConfig()->changeSetting('LIBRARY_LOCATION', $libraryLocation);
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('SERVER_KEY')) {
|
||||
$this->handleChangedSettings('SERVER_KEY', $this->getConfig()->getSetting('SERVER_KEY'), $sanitizedParams->getString('SERVER_KEY'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('SERVER_KEY', $sanitizedParams->getString('SERVER_KEY'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('GLOBAL_THEME_NAME')) {
|
||||
$this->handleChangedSettings('GLOBAL_THEME_NAME', $this->getConfig()->getSetting('GLOBAL_THEME_NAME'), $sanitizedParams->getString('GLOBAL_THEME_NAME'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('GLOBAL_THEME_NAME', $sanitizedParams->getString('GLOBAL_THEME_NAME'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('NAVIGATION_MENU_POSITION')) {
|
||||
$this->handleChangedSettings('NAVIGATION_MENU_POSITION', $this->getConfig()->getSetting('NAVIGATION_MENU_POSITION'), $sanitizedParams->getString('NAVIGATION_MENU_POSITION'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('NAVIGATION_MENU_POSITION', $sanitizedParams->getString('NAVIGATION_MENU_POSITION'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('LIBRARY_MEDIA_UPDATEINALL_CHECKB')) {
|
||||
$this->handleChangedSettings('LIBRARY_MEDIA_UPDATEINALL_CHECKB', $this->getConfig()->getSetting('LIBRARY_MEDIA_UPDATEINALL_CHECKB'), $sanitizedParams->getCheckbox('LIBRARY_MEDIA_UPDATEINALL_CHECKB'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('LIBRARY_MEDIA_UPDATEINALL_CHECKB', $sanitizedParams->getCheckbox('LIBRARY_MEDIA_UPDATEINALL_CHECKB'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('LAYOUT_COPY_MEDIA_CHECKB')) {
|
||||
$this->handleChangedSettings('LAYOUT_COPY_MEDIA_CHECKB', $this->getConfig()->getSetting('LAYOUT_COPY_MEDIA_CHECKB'), $sanitizedParams->getCheckbox('LAYOUT_COPY_MEDIA_CHECKB'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('LAYOUT_COPY_MEDIA_CHECKB', $sanitizedParams->getCheckbox('LAYOUT_COPY_MEDIA_CHECKB'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('LIBRARY_MEDIA_DELETEOLDVER_CHECKB')) {
|
||||
$this->handleChangedSettings('LIBRARY_MEDIA_DELETEOLDVER_CHECKB', $this->getConfig()->getSetting('LIBRARY_MEDIA_DELETEOLDVER_CHECKB'), $sanitizedParams->getCheckbox('LIBRARY_MEDIA_DELETEOLDVER_CHECKB'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('LIBRARY_MEDIA_DELETEOLDVER_CHECKB', $sanitizedParams->getCheckbox('LIBRARY_MEDIA_DELETEOLDVER_CHECKB'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DEFAULT_LAYOUT_AUTO_PUBLISH_CHECKB')) {
|
||||
$this->handleChangedSettings('DEFAULT_LAYOUT_AUTO_PUBLISH_CHECKB', $this->getConfig()->getSetting('DEFAULT_LAYOUT_AUTO_PUBLISH_CHECKB'), $sanitizedParams->getCheckbox('DEFAULT_LAYOUT_AUTO_PUBLISH_CHECKB'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DEFAULT_LAYOUT_AUTO_PUBLISH_CHECKB', $sanitizedParams->getCheckbox('DEFAULT_LAYOUT_AUTO_PUBLISH_CHECKB'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DEFAULT_TRANSITION_IN')) {
|
||||
$this->handleChangedSettings('DEFAULT_TRANSITION_IN', $this->getConfig()->getSetting('DEFAULT_TRANSITION_IN'), $sanitizedParams->getString('DEFAULT_TRANSITION_IN'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DEFAULT_TRANSITION_IN', $sanitizedParams->getString('DEFAULT_TRANSITION_IN'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DEFAULT_TRANSITION_OUT')) {
|
||||
$this->handleChangedSettings('DEFAULT_TRANSITION_OUT', $this->getConfig()->getSetting('DEFAULT_TRANSITION_OUT'), $sanitizedParams->getString('DEFAULT_TRANSITION_OUT'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DEFAULT_TRANSITION_OUT', $sanitizedParams->getString('DEFAULT_TRANSITION_OUT'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DEFAULT_TRANSITION_DURATION')) {
|
||||
$this->handleChangedSettings('DEFAULT_TRANSITION_DURATION', $this->getConfig()->getSetting('DEFAULT_TRANSITION_DURATION'), $sanitizedParams->getInt('DEFAULT_TRANSITION_DURATION'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DEFAULT_TRANSITION_DURATION', $sanitizedParams->getInt('DEFAULT_TRANSITION_DURATION'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DEFAULT_TRANSITION_AUTO_APPLY')) {
|
||||
$this->handleChangedSettings('DEFAULT_TRANSITION_AUTO_APPLY', $this->getConfig()->getSetting('DEFAULT_TRANSITION_AUTO_APPLY'), $sanitizedParams->getCheckbox('DEFAULT_TRANSITION_AUTO_APPLY'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DEFAULT_TRANSITION_AUTO_APPLY', $sanitizedParams->getCheckbox('DEFAULT_TRANSITION_AUTO_APPLY'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DEFAULT_RESIZE_THRESHOLD')) {
|
||||
$this->handleChangedSettings('DEFAULT_RESIZE_THRESHOLD', $this->getConfig()->getSetting('DEFAULT_RESIZE_THRESHOLD'), $sanitizedParams->getInt('DEFAULT_RESIZE_THRESHOLD'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DEFAULT_RESIZE_THRESHOLD', $sanitizedParams->getInt('DEFAULT_RESIZE_THRESHOLD'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DEFAULT_RESIZE_LIMIT')) {
|
||||
$this->handleChangedSettings('DEFAULT_RESIZE_LIMIT', $this->getConfig()->getSetting('DEFAULT_RESIZE_LIMIT'), $sanitizedParams->getInt('DEFAULT_RESIZE_LIMIT'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DEFAULT_RESIZE_LIMIT', $sanitizedParams->getInt('DEFAULT_RESIZE_LIMIT'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DATASET_HARD_ROW_LIMIT')) {
|
||||
$this->handleChangedSettings('DATASET_HARD_ROW_LIMIT', $this->getConfig()->getSetting('DATASET_HARD_ROW_LIMIT'), $sanitizedParams->getInt('DATASET_HARD_ROW_LIMIT'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DATASET_HARD_ROW_LIMIT', $sanitizedParams->getInt('DATASET_HARD_ROW_LIMIT'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DEFAULT_PURGE_LIST_TTL')) {
|
||||
$this->handleChangedSettings('DEFAULT_PURGE_LIST_TTL', $this->getConfig()->getSetting('DEFAULT_PURGE_LIST_TTL'), $sanitizedParams->getInt('DEFAULT_PURGE_LIST_TTL'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DEFAULT_PURGE_LIST_TTL', $sanitizedParams->getInt('DEFAULT_PURGE_LIST_TTL'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DEFAULT_LAYOUT')) {
|
||||
$this->handleChangedSettings('DEFAULT_LAYOUT', $this->getConfig()->getSetting('DEFAULT_LAYOUT'), $sanitizedParams->getInt('DEFAULT_LAYOUT'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DEFAULT_LAYOUT', $sanitizedParams->getInt('DEFAULT_LAYOUT'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('XMR_ADDRESS')) {
|
||||
$this->handleChangedSettings('XMR_ADDRESS', $this->getConfig()->getSetting('XMR_ADDRESS'), $sanitizedParams->getString('XMR_ADDRESS'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('XMR_ADDRESS', $sanitizedParams->getString('XMR_ADDRESS'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('XMR_PUB_ADDRESS')) {
|
||||
$this->handleChangedSettings('XMR_PUB_ADDRESS', $this->getConfig()->getSetting('XMR_PUB_ADDRESS'), $sanitizedParams->getString('XMR_PUB_ADDRESS'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('XMR_PUB_ADDRESS', $sanitizedParams->getString('XMR_PUB_ADDRESS'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('XMR_WS_ADDRESS')) {
|
||||
$this->handleChangedSettings('XMR_WS_ADDRESS', $this->getConfig()->getSetting('XMR_WS_ADDRESS'), $sanitizedParams->getString('XMR_WS_ADDRESS'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('XMR_WS_ADDRESS', $sanitizedParams->getString('XMR_WS_ADDRESS'), 1);
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DEFAULT_LAT')) {
|
||||
$value = $sanitizedParams->getString('DEFAULT_LAT');
|
||||
$this->handleChangedSettings('DEFAULT_LAT', $this->getConfig()->getSetting('DEFAULT_LAT'), $value, $changedSettings);
|
||||
$this->getConfig()->changeSetting('DEFAULT_LAT', $value);
|
||||
|
||||
if (!v::latitude()->validate($value)) {
|
||||
throw new InvalidArgumentException(__('The latitude entered is not valid.'), 'DEFAULT_LAT');
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DEFAULT_LONG')) {
|
||||
$value = $sanitizedParams->getString('DEFAULT_LONG');
|
||||
$this->handleChangedSettings('DEFAULT_LONG', $this->getConfig()->getSetting('DEFAULT_LONG'), $value, $changedSettings);
|
||||
$this->getConfig()->changeSetting('DEFAULT_LONG', $value);
|
||||
|
||||
if (!v::longitude()->validate($value)) {
|
||||
throw new InvalidArgumentException(__('The longitude entered is not valid.'), 'DEFAULT_LONG');
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DEFAULT_DYNAMIC_PLAYLIST_MAXNUMBER')) {
|
||||
$this->handleChangedSettings('DEFAULT_DYNAMIC_PLAYLIST_MAXNUMBER', $this->getConfig()->getSetting('DEFAULT_DYNAMIC_PLAYLIST_MAXNUMBER'), $sanitizedParams->getInt('DEFAULT_DYNAMIC_PLAYLIST_MAXNUMBER'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DEFAULT_DYNAMIC_PLAYLIST_MAXNUMBER', $sanitizedParams->getInt('DEFAULT_DYNAMIC_PLAYLIST_MAXNUMBER'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DEFAULT_DYNAMIC_PLAYLIST_MAXNUMBER_LIMIT')) {
|
||||
$this->handleChangedSettings('DEFAULT_DYNAMIC_PLAYLIST_MAXNUMBER_LIMIT', $this->getConfig()->getSetting('DEFAULT_DYNAMIC_PLAYLIST_MAXNUMBER_LIMIT'), $sanitizedParams->getInt('DEFAULT_DYNAMIC_PLAYLIST_MAXNUMBER_LIMIT'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DEFAULT_DYNAMIC_PLAYLIST_MAXNUMBER_LIMIT', $sanitizedParams->getInt('DEFAULT_DYNAMIC_PLAYLIST_MAXNUMBER_LIMIT'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('SHOW_DISPLAY_AS_VNCLINK')) {
|
||||
$this->handleChangedSettings('SHOW_DISPLAY_AS_VNCLINK', $this->getConfig()->getSetting('SHOW_DISPLAY_AS_VNCLINK'), $sanitizedParams->getString('SHOW_DISPLAY_AS_VNCLINK'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('SHOW_DISPLAY_AS_VNCLINK', $sanitizedParams->getString('SHOW_DISPLAY_AS_VNCLINK'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('SHOW_DISPLAY_AS_VNC_TGT')) {
|
||||
$this->handleChangedSettings('SHOW_DISPLAY_AS_VNC_TGT', $this->getConfig()->getSetting('SHOW_DISPLAY_AS_VNC_TGT'), $sanitizedParams->getString('SHOW_DISPLAY_AS_VNC_TGT'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('SHOW_DISPLAY_AS_VNC_TGT', $sanitizedParams->getString('SHOW_DISPLAY_AS_VNC_TGT'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('MAX_LICENSED_DISPLAYS')) {
|
||||
$this->handleChangedSettings('MAX_LICENSED_DISPLAYS', $this->getConfig()->getSetting('MAX_LICENSED_DISPLAYS'), $sanitizedParams->getInt('MAX_LICENSED_DISPLAYS'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('MAX_LICENSED_DISPLAYS', $sanitizedParams->getInt('MAX_LICENSED_DISPLAYS'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DISPLAY_PROFILE_AGGREGATION_LEVEL_DEFAULT')) {
|
||||
$this->handleChangedSettings('DISPLAY_PROFILE_AGGREGATION_LEVEL_DEFAULT', $this->getConfig()->getSetting('DISPLAY_PROFILE_AGGREGATION_LEVEL_DEFAULT'), $sanitizedParams->getString('DISPLAY_PROFILE_AGGREGATION_LEVEL_DEFAULT'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DISPLAY_PROFILE_AGGREGATION_LEVEL_DEFAULT', $sanitizedParams->getString('DISPLAY_PROFILE_AGGREGATION_LEVEL_DEFAULT'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DISPLAY_PROFILE_STATS_DEFAULT')) {
|
||||
$this->handleChangedSettings('DISPLAY_PROFILE_STATS_DEFAULT', $this->getConfig()->getSetting('DISPLAY_PROFILE_STATS_DEFAULT'), $sanitizedParams->getCheckbox('DISPLAY_PROFILE_STATS_DEFAULT'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DISPLAY_PROFILE_STATS_DEFAULT', $sanitizedParams->getCheckbox('DISPLAY_PROFILE_STATS_DEFAULT'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('LAYOUT_STATS_ENABLED_DEFAULT')) {
|
||||
$this->handleChangedSettings('LAYOUT_STATS_ENABLED_DEFAULT', $this->getConfig()->getSetting('LAYOUT_STATS_ENABLED_DEFAULT'), $sanitizedParams->getCheckbox('LAYOUT_STATS_ENABLED_DEFAULT'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('LAYOUT_STATS_ENABLED_DEFAULT', $sanitizedParams->getCheckbox('LAYOUT_STATS_ENABLED_DEFAULT'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('PLAYLIST_STATS_ENABLED_DEFAULT')) {
|
||||
$this->handleChangedSettings('PLAYLIST_STATS_ENABLED_DEFAULT', $this->getConfig()->getSetting('PLAYLIST_STATS_ENABLED_DEFAULT'), $sanitizedParams->getString('PLAYLIST_STATS_ENABLED_DEFAULT'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('PLAYLIST_STATS_ENABLED_DEFAULT', $sanitizedParams->getString('PLAYLIST_STATS_ENABLED_DEFAULT'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('MEDIA_STATS_ENABLED_DEFAULT')) {
|
||||
$this->handleChangedSettings('MEDIA_STATS_ENABLED_DEFAULT', $this->getConfig()->getSetting('MEDIA_STATS_ENABLED_DEFAULT'), $sanitizedParams->getString('MEDIA_STATS_ENABLED_DEFAULT'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('MEDIA_STATS_ENABLED_DEFAULT', $sanitizedParams->getString('MEDIA_STATS_ENABLED_DEFAULT'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('WIDGET_STATS_ENABLED_DEFAULT')) {
|
||||
$this->handleChangedSettings('WIDGET_STATS_ENABLED_DEFAULT', $this->getConfig()->getSetting('WIDGET_STATS_ENABLED_DEFAULT'), $sanitizedParams->getString('WIDGET_STATS_ENABLED_DEFAULT'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('WIDGET_STATS_ENABLED_DEFAULT', $sanitizedParams->getString('WIDGET_STATS_ENABLED_DEFAULT'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DISPLAY_PROFILE_CURRENT_LAYOUT_STATUS_ENABLED')) {
|
||||
$this->handleChangedSettings('DISPLAY_PROFILE_CURRENT_LAYOUT_STATUS_ENABLED', $this->getConfig()->getSetting('DISPLAY_PROFILE_CURRENT_LAYOUT_STATUS_ENABLED'), $sanitizedParams->getCheckbox('DISPLAY_PROFILE_CURRENT_LAYOUT_STATUS_ENABLED'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DISPLAY_PROFILE_CURRENT_LAYOUT_STATUS_ENABLED', $sanitizedParams->getCheckbox('DISPLAY_PROFILE_CURRENT_LAYOUT_STATUS_ENABLED'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DISPLAY_LOCK_NAME_TO_DEVICENAME')) {
|
||||
$this->handleChangedSettings('DISPLAY_LOCK_NAME_TO_DEVICENAME', $this->getConfig()->getSetting('DISPLAY_LOCK_NAME_TO_DEVICENAME'), $sanitizedParams->getCheckbox('DISPLAY_LOCK_NAME_TO_DEVICENAME'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DISPLAY_LOCK_NAME_TO_DEVICENAME', $sanitizedParams->getCheckbox('DISPLAY_LOCK_NAME_TO_DEVICENAME'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DISPLAY_PROFILE_SCREENSHOT_INTERVAL_ENABLED')) {
|
||||
$this->handleChangedSettings('DISPLAY_PROFILE_SCREENSHOT_INTERVAL_ENABLED', $this->getConfig()->getSetting('DISPLAY_PROFILE_SCREENSHOT_INTERVAL_ENABLED'), $sanitizedParams->getCheckbox('DISPLAY_PROFILE_SCREENSHOT_INTERVAL_ENABLED'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DISPLAY_PROFILE_SCREENSHOT_INTERVAL_ENABLED', $sanitizedParams->getCheckbox('DISPLAY_PROFILE_SCREENSHOT_INTERVAL_ENABLED'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DISPLAY_PROFILE_SCREENSHOT_SIZE_DEFAULT')) {
|
||||
$this->handleChangedSettings('DISPLAY_PROFILE_SCREENSHOT_SIZE_DEFAULT', $this->getConfig()->getSetting('DISPLAY_PROFILE_SCREENSHOT_SIZE_DEFAULT'), $sanitizedParams->getInt('DISPLAY_PROFILE_SCREENSHOT_SIZE_DEFAULT'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DISPLAY_PROFILE_SCREENSHOT_SIZE_DEFAULT', $sanitizedParams->getInt('DISPLAY_PROFILE_SCREENSHOT_SIZE_DEFAULT'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DISPLAY_SCREENSHOT_TTL')) {
|
||||
$this->handleChangedSettings('DISPLAY_SCREENSHOT_TTL', $this->getConfig()->getSetting('DISPLAY_SCREENSHOT_TTL'), $sanitizedParams->getInt('DISPLAY_SCREENSHOT_TTL'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DISPLAY_SCREENSHOT_TTL', $sanitizedParams->getInt('DISPLAY_SCREENSHOT_TTL'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DISPLAY_AUTO_AUTH')) {
|
||||
$this->handleChangedSettings('DISPLAY_AUTO_AUTH', $this->getConfig()->getSetting('DISPLAY_AUTO_AUTH'), $sanitizedParams->getCheckbox('DISPLAY_AUTO_AUTH'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DISPLAY_AUTO_AUTH', $sanitizedParams->getCheckbox('DISPLAY_AUTO_AUTH'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DISPLAY_DEFAULT_FOLDER')) {
|
||||
$this->handleChangedSettings(
|
||||
'DISPLAY_DEFAULT_FOLDER',
|
||||
$this->getConfig()->getSetting('DISPLAY_DEFAULT_FOLDER'),
|
||||
$sanitizedParams->getInt('DISPLAY_DEFAULT_FOLDER'),
|
||||
$changedSettings
|
||||
);
|
||||
$this->getConfig()->changeSetting(
|
||||
'DISPLAY_DEFAULT_FOLDER',
|
||||
$sanitizedParams->getInt('DISPLAY_DEFAULT_FOLDER'),
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('HELP_BASE')) {
|
||||
$this->handleChangedSettings('HELP_BASE', $this->getConfig()->getSetting('HELP_BASE'), $sanitizedParams->getString('HELP_BASE'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('HELP_BASE', $sanitizedParams->getString('HELP_BASE'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('QUICK_CHART_URL')) {
|
||||
$this->handleChangedSettings('QUICK_CHART_URL', $this->getConfig()->getSetting('QUICK_CHART_URL'), $sanitizedParams->getString('QUICK_CHART_URL'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('QUICK_CHART_URL', $sanitizedParams->getString('QUICK_CHART_URL'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('PHONE_HOME')) {
|
||||
$this->handleChangedSettings('PHONE_HOME', $this->getConfig()->getSetting('PHONE_HOME'), $sanitizedParams->getCheckbox('PHONE_HOME'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('PHONE_HOME', $sanitizedParams->getCheckbox('PHONE_HOME'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('PHONE_HOME_KEY')) {
|
||||
$this->handleChangedSettings('PHONE_HOME_KEY', $this->getConfig()->getSetting('PHONE_HOME_KEY'), $sanitizedParams->getString('PHONE_HOME_KEY'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('PHONE_HOME_KEY', $sanitizedParams->getString('PHONE_HOME_KEY'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('PHONE_HOME_DATE')) {
|
||||
$this->handleChangedSettings('PHONE_HOME_DATE', $this->getConfig()->getSetting('PHONE_HOME_DATE'), $sanitizedParams->getInt('PHONE_HOME_DATE'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('PHONE_HOME_DATE', $sanitizedParams->getInt('PHONE_HOME_DATE'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('SCHEDULE_LOOKAHEAD')) {
|
||||
$this->handleChangedSettings('SCHEDULE_LOOKAHEAD', $this->getConfig()->getSetting('SCHEDULE_LOOKAHEAD'), $sanitizedParams->getCheckbox('SCHEDULE_LOOKAHEAD'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('SCHEDULE_LOOKAHEAD', $sanitizedParams->getCheckbox('SCHEDULE_LOOKAHEAD'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('REQUIRED_FILES_LOOKAHEAD')) {
|
||||
$this->handleChangedSettings('REQUIRED_FILES_LOOKAHEAD', $this->getConfig()->getSetting('REQUIRED_FILES_LOOKAHEAD'), $sanitizedParams->getInt('REQUIRED_FILES_LOOKAHEAD'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('REQUIRED_FILES_LOOKAHEAD', $sanitizedParams->getInt('REQUIRED_FILES_LOOKAHEAD'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('SETTING_IMPORT_ENABLED')) {
|
||||
$this->handleChangedSettings('SETTING_IMPORT_ENABLED', $this->getConfig()->getSetting('SETTING_IMPORT_ENABLED'), $sanitizedParams->getCheckbox('SETTING_IMPORT_ENABLED'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('SETTING_IMPORT_ENABLED', $sanitizedParams->getCheckbox('SETTING_IMPORT_ENABLED'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('SETTING_LIBRARY_TIDY_ENABLED')) {
|
||||
$this->handleChangedSettings('SETTING_LIBRARY_TIDY_ENABLED', $this->getConfig()->getSetting('SETTING_LIBRARY_TIDY_ENABLED'), $sanitizedParams->getCheckbox('SETTING_LIBRARY_TIDY_ENABLED'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('SETTING_LIBRARY_TIDY_ENABLED', $sanitizedParams->getCheckbox('SETTING_LIBRARY_TIDY_ENABLED'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('EMBEDDED_STATUS_WIDGET')) {
|
||||
$this->handleChangedSettings('EMBEDDED_STATUS_WIDGET', $this->getConfig()->getSetting('EMBEDDED_STATUS_WIDGET'), $sanitizedParams->getString('EMBEDDED_STATUS_WIDGET'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('EMBEDDED_STATUS_WIDGET', $sanitizedParams->getString('EMBEDDED_STATUS_WIDGET'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DEFAULTS_IMPORTED')) {
|
||||
$this->handleChangedSettings('DEFAULTS_IMPORTED', $this->getConfig()->getSetting('DEFAULTS_IMPORTED'), $sanitizedParams->getCheckbox('DEFAULTS_IMPORTED'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DEFAULTS_IMPORTED', $sanitizedParams->getCheckbox('DEFAULTS_IMPORTED'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DASHBOARD_LATEST_NEWS_ENABLED')) {
|
||||
$this->handleChangedSettings('DASHBOARD_LATEST_NEWS_ENABLED', $this->getConfig()->getSetting('DASHBOARD_LATEST_NEWS_ENABLED'), $sanitizedParams->getCheckbox('DASHBOARD_LATEST_NEWS_ENABLED'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DASHBOARD_LATEST_NEWS_ENABLED', $sanitizedParams->getCheckbox('DASHBOARD_LATEST_NEWS_ENABLED'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('INSTANCE_SUSPENDED')) {
|
||||
$this->handleChangedSettings('INSTANCE_SUSPENDED', $this->getConfig()->getSetting('INSTANCE_SUSPENDED'), $sanitizedParams->getString('INSTANCE_SUSPENDED'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('INSTANCE_SUSPENDED', $sanitizedParams->getString('INSTANCE_SUSPENDED'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('LATEST_NEWS_URL')) {
|
||||
$this->handleChangedSettings('LATEST_NEWS_URL', $this->getConfig()->getSetting('LATEST_NEWS_URL'), $sanitizedParams->getString('LATEST_NEWS_URL'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('LATEST_NEWS_URL', $sanitizedParams->getString('LATEST_NEWS_URL'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('REPORTS_EXPORT_SHOW_LOGO')) {
|
||||
$this->handleChangedSettings(
|
||||
'REPORTS_EXPORT_SHOW_LOGO',
|
||||
$this->getConfig()->getSetting('REPORTS_EXPORT_SHOW_LOGO'),
|
||||
$sanitizedParams->getCheckbox('REPORTS_EXPORT_SHOW_LOGO'),
|
||||
$changedSettings
|
||||
);
|
||||
$this->getConfig()->changeSetting(
|
||||
'REPORTS_EXPORT_SHOW_LOGO',
|
||||
$sanitizedParams->getCheckbox('REPORTS_EXPORT_SHOW_LOGO')
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('MAINTENANCE_ENABLED')) {
|
||||
$this->handleChangedSettings('MAINTENANCE_ENABLED', $this->getConfig()->getSetting('MAINTENANCE_ENABLED'), $sanitizedParams->getString('MAINTENANCE_ENABLED'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('MAINTENANCE_ENABLED', $sanitizedParams->getString('MAINTENANCE_ENABLED'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('MAINTENANCE_EMAIL_ALERTS')) {
|
||||
$this->handleChangedSettings('MAINTENANCE_EMAIL_ALERTS', $this->getConfig()->getSetting('MAINTENANCE_EMAIL_ALERTS'), $sanitizedParams->getCheckbox('MAINTENANCE_EMAIL_ALERTS'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('MAINTENANCE_EMAIL_ALERTS', $sanitizedParams->getCheckbox('MAINTENANCE_EMAIL_ALERTS'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('MAINTENANCE_LOG_MAXAGE')) {
|
||||
$this->handleChangedSettings('MAINTENANCE_LOG_MAXAGE', $this->getConfig()->getSetting('MAINTENANCE_LOG_MAXAGE'), $sanitizedParams->getInt('MAINTENANCE_LOG_MAXAGE'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('MAINTENANCE_LOG_MAXAGE', $sanitizedParams->getInt('MAINTENANCE_LOG_MAXAGE'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('MAINTENANCE_STAT_MAXAGE')) {
|
||||
$this->handleChangedSettings('MAINTENANCE_STAT_MAXAGE', $this->getConfig()->getSetting('MAINTENANCE_STAT_MAXAGE'), $sanitizedParams->getInt('MAINTENANCE_STAT_MAXAGE'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('MAINTENANCE_STAT_MAXAGE', $sanitizedParams->getInt('MAINTENANCE_STAT_MAXAGE'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('MAINTENANCE_ALERT_TOUT')) {
|
||||
$this->handleChangedSettings('MAINTENANCE_ALERT_TOUT', $this->getConfig()->getSetting('MAINTENANCE_ALERT_TOUT'), $sanitizedParams->getInt('MAINTENANCE_ALERT_TOUT'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('MAINTENANCE_ALERT_TOUT', $sanitizedParams->getInt('MAINTENANCE_ALERT_TOUT'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('MAINTENANCE_ALWAYS_ALERT')) {
|
||||
$this->handleChangedSettings('MAINTENANCE_ALWAYS_ALERT', $this->getConfig()->getSetting('MAINTENANCE_ALWAYS_ALERT'), $sanitizedParams->getCheckbox('MAINTENANCE_ALWAYS_ALERT'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('MAINTENANCE_ALWAYS_ALERT', $sanitizedParams->getCheckbox('MAINTENANCE_ALWAYS_ALERT'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('mail_to')) {
|
||||
$this->handleChangedSettings('mail_to', $this->getConfig()->getSetting('mail_to'), $sanitizedParams->getString('mail_to'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('mail_to', $sanitizedParams->getString('mail_to'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('mail_from')) {
|
||||
$this->handleChangedSettings('mail_from', $this->getConfig()->getSetting('mail_from'), $sanitizedParams->getString('mail_from'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('mail_from', $sanitizedParams->getString('mail_from'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('mail_from_name')) {
|
||||
$this->handleChangedSettings('mail_from_name', $this->getConfig()->getSetting('mail_from_name'), $sanitizedParams->getString('mail_from_name'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('mail_from_name', $sanitizedParams->getString('mail_from_name'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('SENDFILE_MODE')) {
|
||||
$this->handleChangedSettings('SENDFILE_MODE', $this->getConfig()->getSetting('SENDFILE_MODE'), $sanitizedParams->getString('SENDFILE_MODE'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('SENDFILE_MODE', $sanitizedParams->getString('SENDFILE_MODE'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('PROXY_HOST')) {
|
||||
$this->handleChangedSettings('PROXY_HOST', $this->getConfig()->getSetting('PROXY_HOST'), $sanitizedParams->getString('PROXY_HOST'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('PROXY_HOST', $sanitizedParams->getString('PROXY_HOST'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('PROXY_PORT')) {
|
||||
$this->handleChangedSettings('PROXY_PORT', $this->getConfig()->getSetting('PROXY_PORT'), $sanitizedParams->getString('PROXY_PORT'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('PROXY_PORT', $sanitizedParams->getString('PROXY_PORT'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('PROXY_AUTH')) {
|
||||
$this->handleChangedSettings('PROXY_AUTH', $this->getConfig()->getSetting('PROXY_AUTH'), $sanitizedParams->getString('PROXY_AUTH'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('PROXY_AUTH', $sanitizedParams->getString('PROXY_AUTH'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('PROXY_EXCEPTIONS')) {
|
||||
$this->handleChangedSettings('PROXY_EXCEPTIONS', $this->getConfig()->getSetting('PROXY_EXCEPTIONS'), $sanitizedParams->getString('PROXY_EXCEPTIONS'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('PROXY_EXCEPTIONS', $sanitizedParams->getString('PROXY_EXCEPTIONS'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('CDN_URL')) {
|
||||
$this->handleChangedSettings('CDN_URL', $this->getConfig()->getSetting('CDN_URL'), $sanitizedParams->getString('CDN_URL'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('CDN_URL', $sanitizedParams->getString('CDN_URL'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('MONTHLY_XMDS_TRANSFER_LIMIT_KB')) {
|
||||
$this->handleChangedSettings('MONTHLY_XMDS_TRANSFER_LIMIT_KB', $this->getConfig()->getSetting('MONTHLY_XMDS_TRANSFER_LIMIT_KB'), $sanitizedParams->getInt('MONTHLY_XMDS_TRANSFER_LIMIT_KB'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('MONTHLY_XMDS_TRANSFER_LIMIT_KB', $sanitizedParams->getInt('MONTHLY_XMDS_TRANSFER_LIMIT_KB'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('LIBRARY_SIZE_LIMIT_KB')) {
|
||||
$this->handleChangedSettings('LIBRARY_SIZE_LIMIT_KB', $this->getConfig()->getSetting('LIBRARY_SIZE_LIMIT_KB'), $sanitizedParams->getInt('LIBRARY_SIZE_LIMIT_KB'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('LIBRARY_SIZE_LIMIT_KB', $sanitizedParams->getInt('LIBRARY_SIZE_LIMIT_KB'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('FORCE_HTTPS')) {
|
||||
$this->handleChangedSettings('FORCE_HTTPS', $this->getConfig()->getSetting('FORCE_HTTPS'), $sanitizedParams->getCheckbox('FORCE_HTTPS'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('FORCE_HTTPS', $sanitizedParams->getCheckbox('FORCE_HTTPS'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('ISSUE_STS')) {
|
||||
$this->handleChangedSettings('ISSUE_STS', $this->getConfig()->getSetting('ISSUE_STS'), $sanitizedParams->getCheckbox('ISSUE_STS'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('ISSUE_STS', $sanitizedParams->getCheckbox('ISSUE_STS'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('STS_TTL')) {
|
||||
$this->handleChangedSettings('STS_TTL', $this->getConfig()->getSetting('STS_TTL'), $sanitizedParams->getInt('STS_TTL'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('STS_TTL', $sanitizedParams->getInt('STS_TTL'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('WHITELIST_LOAD_BALANCERS')) {
|
||||
$this->handleChangedSettings('WHITELIST_LOAD_BALANCERS', $this->getConfig()->getSetting('WHITELIST_LOAD_BALANCERS'), $sanitizedParams->getString('WHITELIST_LOAD_BALANCERS'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('WHITELIST_LOAD_BALANCERS', $sanitizedParams->getString('WHITELIST_LOAD_BALANCERS'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('REGION_OPTIONS_COLOURING')) {
|
||||
$this->handleChangedSettings('REGION_OPTIONS_COLOURING', $this->getConfig()->getSetting('REGION_OPTIONS_COLOURING'), $sanitizedParams->getString('REGION_OPTIONS_COLOURING'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('REGION_OPTIONS_COLOURING', $sanitizedParams->getString('REGION_OPTIONS_COLOURING'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('SCHEDULE_WITH_VIEW_PERMISSION')) {
|
||||
$this->handleChangedSettings('SCHEDULE_WITH_VIEW_PERMISSION', $this->getConfig()->getSetting('SCHEDULE_WITH_VIEW_PERMISSION'), $sanitizedParams->getCheckbox('SCHEDULE_WITH_VIEW_PERMISSION'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('SCHEDULE_WITH_VIEW_PERMISSION', $sanitizedParams->getCheckbox('SCHEDULE_WITH_VIEW_PERMISSION'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('SCHEDULE_SHOW_LAYOUT_NAME')) {
|
||||
$this->handleChangedSettings('SCHEDULE_SHOW_LAYOUT_NAME', $this->getConfig()->getSetting('SCHEDULE_SHOW_LAYOUT_NAME'), $sanitizedParams->getCheckbox('SCHEDULE_SHOW_LAYOUT_NAME'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('SCHEDULE_SHOW_LAYOUT_NAME', $sanitizedParams->getCheckbox('SCHEDULE_SHOW_LAYOUT_NAME'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('TASK_CONFIG_LOCKED_CHECKB')) {
|
||||
$this->handleChangedSettings('TASK_CONFIG_LOCKED_CHECKB', $this->getConfig()->getSetting('TASK_CONFIG_LOCKED_CHECKB'), $sanitizedParams->getCheckbox('TASK_CONFIG_LOCKED_CHECKB'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('TASK_CONFIG_LOCKED_CHECKB', $sanitizedParams->getCheckbox('TASK_CONFIG_LOCKED_CHECKB'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('TRANSITION_CONFIG_LOCKED_CHECKB')) {
|
||||
$this->handleChangedSettings('TRANSITION_CONFIG_LOCKED_CHECKB', $this->getConfig()->getSetting('TRANSITION_CONFIG_LOCKED_CHECKB'), $sanitizedParams->getCheckbox('TRANSITION_CONFIG_LOCKED_CHECKB'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('TRANSITION_CONFIG_LOCKED_CHECKB', $sanitizedParams->getCheckbox('TRANSITION_CONFIG_LOCKED_CHECKB'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('FOLDERS_ALLOW_SAVE_IN_ROOT')) {
|
||||
$this->handleChangedSettings('FOLDERS_ALLOW_SAVE_IN_ROOT', $this->getConfig()->getSetting('FOLDERS_ALLOW_SAVE_IN_ROOT'), $sanitizedParams->getCheckbox('FOLDERS_ALLOW_SAVE_IN_ROOT'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('FOLDERS_ALLOW_SAVE_IN_ROOT', $sanitizedParams->getCheckbox('FOLDERS_ALLOW_SAVE_IN_ROOT'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DEFAULT_LANGUAGE')) {
|
||||
$this->handleChangedSettings('DEFAULT_LANGUAGE', $this->getConfig()->getSetting('DEFAULT_LANGUAGE'), $sanitizedParams->getString('DEFAULT_LANGUAGE'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DEFAULT_LANGUAGE', $sanitizedParams->getString('DEFAULT_LANGUAGE'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('defaultTimezone')) {
|
||||
$this->handleChangedSettings('defaultTimezone', $this->getConfig()->getSetting('defaultTimezone'), $sanitizedParams->getString('defaultTimezone'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('defaultTimezone', $sanitizedParams->getString('defaultTimezone'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DATE_FORMAT')) {
|
||||
$this->handleChangedSettings('DATE_FORMAT', $this->getConfig()->getSetting('DATE_FORMAT'), $sanitizedParams->getString('DATE_FORMAT'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DATE_FORMAT', $sanitizedParams->getString('DATE_FORMAT'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DETECT_LANGUAGE')) {
|
||||
$this->handleChangedSettings('DETECT_LANGUAGE', $this->getConfig()->getSetting('DETECT_LANGUAGE'), $sanitizedParams->getCheckbox('DETECT_LANGUAGE'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DETECT_LANGUAGE', $sanitizedParams->getCheckbox('DETECT_LANGUAGE'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('CALENDAR_TYPE')) {
|
||||
$this->handleChangedSettings('CALENDAR_TYPE', $this->getConfig()->getSetting('CALENDAR_TYPE'), $sanitizedParams->getString('CALENDAR_TYPE'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('CALENDAR_TYPE', $sanitizedParams->getString('CALENDAR_TYPE'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('RESTING_LOG_LEVEL')) {
|
||||
$this->handleChangedSettings('RESTING_LOG_LEVEL', $this->getConfig()->getSetting('RESTING_LOG_LEVEL'), $sanitizedParams->getString('RESTING_LOG_LEVEL'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('RESTING_LOG_LEVEL', $sanitizedParams->getString('RESTING_LOG_LEVEL'));
|
||||
}
|
||||
|
||||
// Handle changes to log level
|
||||
$newLogLevel = null;
|
||||
$newElevateUntil = null;
|
||||
$currentLogLevel = $this->getConfig()->getSetting('audit');
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('audit')) {
|
||||
$newLogLevel = $sanitizedParams->getString('audit');
|
||||
$this->handleChangedSettings('audit', $this->getConfig()->getSetting('audit'), $newLogLevel, $changedSettings);
|
||||
$this->getConfig()->changeSetting('audit', $newLogLevel);
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('ELEVATE_LOG_UNTIL') && $sanitizedParams->getDate('ELEVATE_LOG_UNTIL') != null) {
|
||||
$newElevateUntil = $sanitizedParams->getDate('ELEVATE_LOG_UNTIL')->format('U');
|
||||
$this->handleChangedSettings('ELEVATE_LOG_UNTIL', $this->getConfig()->getSetting('ELEVATE_LOG_UNTIL'), $newElevateUntil, $changedSettings);
|
||||
$this->getConfig()->changeSetting('ELEVATE_LOG_UNTIL', $newElevateUntil);
|
||||
}
|
||||
|
||||
// Have we changed log level? If so, were we also provided the elevate until setting?
|
||||
if ($newElevateUntil === null && $currentLogLevel != $newLogLevel) {
|
||||
// We haven't provided an elevate until (meaning it is not visible)
|
||||
$this->getConfig()->changeSetting('ELEVATE_LOG_UNTIL', Carbon::now()->addHour()->format('U'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('SERVER_MODE')) {
|
||||
$this->handleChangedSettings('SERVER_MODE', $this->getConfig()->getSetting('SERVER_MODE'), $sanitizedParams->getString('SERVER_MODE'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('SERVER_MODE', $sanitizedParams->getString('SERVER_MODE'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('SYSTEM_USER')) {
|
||||
$this->handleChangedSettings('SYSTEM_USER', $this->getConfig()->getSetting('SYSTEM_USER'), $sanitizedParams->getInt('SYSTEM_USER'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('SYSTEM_USER', $sanitizedParams->getInt('SYSTEM_USER'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('DEFAULT_USERGROUP')) {
|
||||
$this->handleChangedSettings('DEFAULT_USERGROUP', $this->getConfig()->getSetting('DEFAULT_USERGROUP'), $sanitizedParams->getInt('DEFAULT_USERGROUP'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('DEFAULT_USERGROUP', $sanitizedParams->getInt('DEFAULT_USERGROUP'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('defaultUsertype')) {
|
||||
$this->handleChangedSettings('defaultUsertype', $this->getConfig()->getSetting('defaultUsertype'), $sanitizedParams->getString('defaultUsertype'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('defaultUsertype', $sanitizedParams->getString('defaultUsertype'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('USER_PASSWORD_POLICY')) {
|
||||
$this->handleChangedSettings('USER_PASSWORD_POLICY', $this->getConfig()->getSetting('USER_PASSWORD_POLICY'), $sanitizedParams->getString('USER_PASSWORD_POLICY'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('USER_PASSWORD_POLICY', $sanitizedParams->getString('USER_PASSWORD_POLICY'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('USER_PASSWORD_ERROR')) {
|
||||
$this->handleChangedSettings('USER_PASSWORD_ERROR', $this->getConfig()->getSetting('USER_PASSWORD_ERROR'), $sanitizedParams->getString('USER_PASSWORD_ERROR'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('USER_PASSWORD_ERROR', $sanitizedParams->getString('USER_PASSWORD_ERROR'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('PASSWORD_REMINDER_ENABLED')) {
|
||||
$this->handleChangedSettings('PASSWORD_REMINDER_ENABLED', $this->getConfig()->getSetting('PASSWORD_REMINDER_ENABLED'), $sanitizedParams->getString('PASSWORD_REMINDER_ENABLED'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('PASSWORD_REMINDER_ENABLED', $sanitizedParams->getString('PASSWORD_REMINDER_ENABLED'));
|
||||
}
|
||||
|
||||
if ($this->getConfig()->isSettingEditable('TWOFACTOR_ISSUER')) {
|
||||
$this->handleChangedSettings('TWOFACTOR_ISSUER', $this->getConfig()->getSetting('TWOFACTOR_ISSUER'), $sanitizedParams->getString('TWOFACTOR_ISSUER'), $changedSettings);
|
||||
$this->getConfig()->changeSetting('TWOFACTOR_ISSUER', $sanitizedParams->getString('TWOFACTOR_ISSUER'));
|
||||
}
|
||||
|
||||
if ($changedSettings != []) {
|
||||
$this->getLog()->audit('Settings', 0, 'Updated', $changedSettings);
|
||||
}
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => __('Settings Updated')
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
private function handleChangedSettings($setting, $oldValue, $newValue, &$changedSettings)
|
||||
{
|
||||
if ($oldValue != $newValue) {
|
||||
if ($setting === 'SYSTEM_USER') {
|
||||
$newSystemUser = $this->userFactory->getById($newValue);
|
||||
$oldSystemUser = $this->userFactory->getById($oldValue);
|
||||
$this->getDispatcher()->dispatch(SystemUserChangedEvent::$NAME, new SystemUserChangedEvent($oldSystemUser, $newSystemUser));
|
||||
} elseif ($setting === 'DEFAULT_DYNAMIC_PLAYLIST_MAXNUMBER_LIMIT') {
|
||||
$this->getDispatcher()->dispatch(PlaylistMaxNumberChangedEvent::$NAME, new PlaylistMaxNumberChangedEvent($newValue));
|
||||
}
|
||||
if ($setting === 'ELEVATE_LOG_UNTIL') {
|
||||
$changedSettings[$setting] = Carbon::createFromTimestamp($oldValue)->format(DateFormatHelper::getSystemFormat()) . ' > ' . Carbon::createFromTimestamp($newValue)->format(DateFormatHelper::getSystemFormat());
|
||||
} else {
|
||||
$changedSettings[$setting] = $oldValue . ' > ' . $newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1085
lib/Controller/Stats.php
Normal file
1085
lib/Controller/Stats.php
Normal file
File diff suppressed because it is too large
Load Diff
550
lib/Controller/StatusDashboard.php
Normal file
550
lib/Controller/StatusDashboard.php
Normal file
@@ -0,0 +1,550 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2023 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
use PicoFeed\PicoFeedException;
|
||||
use PicoFeed\Reader\Reader;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Stash\Interfaces\PoolInterface;
|
||||
use Xibo\Factory\DisplayFactory;
|
||||
use Xibo\Factory\DisplayGroupFactory;
|
||||
use Xibo\Factory\MediaFactory;
|
||||
use Xibo\Factory\UserFactory;
|
||||
use Xibo\Helper\ByteFormatter;
|
||||
use Xibo\Helper\DateFormatHelper;
|
||||
use Xibo\Service\MediaService;
|
||||
use Xibo\Storage\StorageServiceInterface;
|
||||
|
||||
/**
|
||||
* Class StatusDashboard
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class StatusDashboard extends Base
|
||||
{
|
||||
/**
|
||||
* @var StorageServiceInterface
|
||||
*/
|
||||
private $store;
|
||||
|
||||
/**
|
||||
* @var PoolInterface
|
||||
*/
|
||||
private $pool;
|
||||
|
||||
/**
|
||||
* @var UserFactory
|
||||
*/
|
||||
private $userFactory;
|
||||
|
||||
/**
|
||||
* @var DisplayFactory
|
||||
*/
|
||||
private $displayFactory;
|
||||
|
||||
/**
|
||||
* @var DisplayGroupFactory
|
||||
*/
|
||||
private $displayGroupFactory;
|
||||
|
||||
/**
|
||||
* @var MediaFactory
|
||||
*/
|
||||
private $mediaFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param StorageServiceInterface $store
|
||||
* @param PoolInterface $pool
|
||||
* @param UserFactory $userFactory
|
||||
* @param DisplayFactory $displayFactory
|
||||
* @param DisplayGroupFactory $displayGroupFactory
|
||||
* @param MediaFactory $mediaFactory
|
||||
*/
|
||||
public function __construct($store, $pool, $userFactory, $displayFactory, $displayGroupFactory, $mediaFactory)
|
||||
{
|
||||
$this->store = $store;
|
||||
$this->pool = $pool;
|
||||
$this->userFactory = $userFactory;
|
||||
$this->displayFactory = $displayFactory;
|
||||
$this->displayGroupFactory = $displayGroupFactory;
|
||||
$this->mediaFactory = $mediaFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function displays(Request $request, Response $response)
|
||||
{
|
||||
$parsedRequestParams = $this->getSanitizer($request->getParams());
|
||||
// Get a list of displays
|
||||
$displays = $this->displayFactory->query($this->gridRenderSort($parsedRequestParams), $this->gridRenderFilter([], $parsedRequestParams));
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->displayFactory->countLast();
|
||||
$this->getState()->setData($displays);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* View
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response)
|
||||
{
|
||||
$data = [];
|
||||
// Set up some suffixes
|
||||
$suffixes = array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB');
|
||||
|
||||
try {
|
||||
// Get some data for a bandwidth chart
|
||||
$dbh = $this->store->getConnection();
|
||||
$params = ['month' => Carbon::now()->subSeconds(86400 * 365)->format('U')];
|
||||
|
||||
$sql = '
|
||||
SELECT month,
|
||||
SUM(size) AS size
|
||||
FROM (
|
||||
SELECT MAX(FROM_UNIXTIME(month)) AS month,
|
||||
IFNULL(SUM(Size), 0) AS size,
|
||||
MIN(month) AS month_order
|
||||
FROM `bandwidth`
|
||||
INNER JOIN `lkdisplaydg`
|
||||
ON lkdisplaydg.displayID = bandwidth.displayId
|
||||
INNER JOIN `displaygroup`
|
||||
ON displaygroup.DisplayGroupID = lkdisplaydg.DisplayGroupID
|
||||
AND displaygroup.isDisplaySpecific = 1
|
||||
WHERE month > :month
|
||||
';
|
||||
|
||||
// Permissions
|
||||
$this->displayFactory->viewPermissionSql('Xibo\Entity\DisplayGroup', $sql, $params, '`lkdisplaydg`.displayGroupId');
|
||||
|
||||
$sql .= ' GROUP BY MONTH(FROM_UNIXTIME(month)) ';
|
||||
|
||||
// Include deleted displays?
|
||||
if ($this->getUser()->isSuperAdmin()) {
|
||||
$sql .= '
|
||||
UNION ALL
|
||||
SELECT MAX(FROM_UNIXTIME(month)) AS month,
|
||||
IFNULL(SUM(Size), 0) AS size,
|
||||
MIN(month) AS month_order
|
||||
FROM `bandwidth`
|
||||
WHERE bandwidth.displayId NOT IN (SELECT displayId FROM `display`)
|
||||
AND month > :month
|
||||
GROUP BY MONTH(FROM_UNIXTIME(month))
|
||||
';
|
||||
}
|
||||
|
||||
$sql .= '
|
||||
) grp
|
||||
GROUP BY month
|
||||
ORDER BY MIN(month_order)
|
||||
';
|
||||
|
||||
// Run the SQL
|
||||
$results = $this->store->select($sql, $params);
|
||||
|
||||
// Monthly bandwidth - optionally tested against limits
|
||||
$xmdsLimit = $this->getConfig()->getSetting('MONTHLY_XMDS_TRANSFER_LIMIT_KB');
|
||||
|
||||
$maxSize = 0;
|
||||
foreach ($results as $row) {
|
||||
$maxSize = ($row['size'] > $maxSize) ? $row['size'] : $maxSize;
|
||||
}
|
||||
|
||||
// Decide what our units are going to be, based on the size
|
||||
$base = ($maxSize == 0) ? 0 : floor(log($maxSize) / log(1024));
|
||||
|
||||
if ($xmdsLimit > 0) {
|
||||
// Convert to appropriate size (xmds limit is in KB)
|
||||
$xmdsLimit = ($xmdsLimit * 1024) / (pow(1024, $base));
|
||||
$data['xmdsLimit'] = round($xmdsLimit, 2) . ' ' . $suffixes[$base];
|
||||
}
|
||||
|
||||
$labels = [];
|
||||
$usage = [];
|
||||
$limit = [];
|
||||
|
||||
foreach ($results as $row) {
|
||||
$sanitizedRow = $this->getSanitizer($row);
|
||||
$labels[] = Carbon::createFromTimeString($sanitizedRow->getString('month'))->format('F');
|
||||
|
||||
$size = ((double)$row['size']) / (pow(1024, $base));
|
||||
$usage[] = round($size, 2);
|
||||
|
||||
$limit[] = round($xmdsLimit - $size, 2);
|
||||
}
|
||||
|
||||
// What if we are empty?
|
||||
if (count($results) == 0) {
|
||||
$labels[] = Carbon::now()->format('F');
|
||||
$usage[] = 0;
|
||||
$limit[] = 0;
|
||||
}
|
||||
|
||||
// Organise our datasets
|
||||
$dataSets = [
|
||||
[
|
||||
'label' => __('Used'),
|
||||
'backgroundColor' => ($xmdsLimit > 0) ? 'rgb(255, 0, 0)' : 'rgb(11, 98, 164)',
|
||||
'data' => $usage
|
||||
]
|
||||
];
|
||||
|
||||
if ($xmdsLimit > 0) {
|
||||
$dataSets[] = [
|
||||
'label' => __('Available'),
|
||||
'backgroundColor' => 'rgb(0, 204, 0)',
|
||||
'data' => $limit
|
||||
];
|
||||
}
|
||||
|
||||
// Set the data
|
||||
$data['xmdsLimitSet'] = ($xmdsLimit > 0);
|
||||
$data['bandwidthSuffix'] = $suffixes[$base];
|
||||
$data['bandwidthWidget'] = json_encode([
|
||||
'labels' => $labels,
|
||||
'datasets' => $dataSets
|
||||
]);
|
||||
|
||||
// We would also like a library usage pie chart!
|
||||
if ($this->getUser()->libraryQuota != 0) {
|
||||
$libraryLimit = $this->getUser()->libraryQuota * 1024;
|
||||
} else {
|
||||
$libraryLimit = $this->getConfig()->getSetting('LIBRARY_SIZE_LIMIT_KB') * 1024;
|
||||
}
|
||||
|
||||
// Library Size in Bytes
|
||||
$params = [];
|
||||
$sql = 'SELECT IFNULL(SUM(FileSize), 0) AS SumSize, type FROM `media` WHERE 1 = 1 ';
|
||||
$this->mediaFactory->viewPermissionSql('Xibo\Entity\Media', $sql, $params, '`media`.mediaId', '`media`.userId', [], 'media.permissionsFolderId');
|
||||
$sql .= ' GROUP BY type ';
|
||||
|
||||
$sth = $dbh->prepare($sql);
|
||||
$sth->execute($params);
|
||||
|
||||
$results = $sth->fetchAll();
|
||||
// add any dependencies fonts, player software etc to the results
|
||||
$event = new \Xibo\Event\DependencyFileSizeEvent($results);
|
||||
$this->getDispatcher()->dispatch($event, $event::$NAME);
|
||||
$results = $event->getResults();
|
||||
|
||||
// Do we base the units on the maximum size or the library limit
|
||||
$maxSize = 0;
|
||||
if ($libraryLimit > 0) {
|
||||
$maxSize = $libraryLimit;
|
||||
} else {
|
||||
// Find the maximum sized chunk of the items in the library
|
||||
foreach ($results as $library) {
|
||||
$maxSize = ($library['SumSize'] > $maxSize) ? $library['SumSize'] : $maxSize;
|
||||
}
|
||||
}
|
||||
|
||||
// Decide what our units are going to be, based on the size
|
||||
$base = ($maxSize == 0) ? 0 : floor(log($maxSize) / log(1024));
|
||||
|
||||
$libraryUsage = [];
|
||||
$libraryLabels = [];
|
||||
$totalSize = 0;
|
||||
foreach ($results as $library) {
|
||||
$libraryUsage[] = round((double)$library['SumSize'] / (pow(1024, $base)), 2);
|
||||
$libraryLabels[] = ucfirst($library['type']) . ' ' . $suffixes[$base];
|
||||
|
||||
$totalSize = $totalSize + $library['SumSize'];
|
||||
}
|
||||
|
||||
// Do we need to add the library remaining?
|
||||
if ($libraryLimit > 0) {
|
||||
$remaining = round(($libraryLimit - $totalSize) / (pow(1024, $base)), 2);
|
||||
|
||||
$libraryUsage[] = $remaining;
|
||||
$libraryLabels[] = __('Free') . ' ' . $suffixes[$base];
|
||||
}
|
||||
|
||||
// What if we are empty?
|
||||
if (count($results) == 0 && $libraryLimit <= 0) {
|
||||
$libraryUsage[] = 0;
|
||||
$libraryLabels[] = __('Empty');
|
||||
}
|
||||
|
||||
$data['libraryLimitSet'] = ($libraryLimit > 0);
|
||||
$data['libraryLimit'] = (round((double)$libraryLimit / (pow(1024, $base)), 2)) . ' ' . $suffixes[$base];
|
||||
$data['librarySize'] = ByteFormatter::format($totalSize, 1);
|
||||
$data['librarySuffix'] = $suffixes[$base];
|
||||
$data['libraryWidgetLabels'] = json_encode($libraryLabels);
|
||||
$data['libraryWidgetData'] = json_encode($libraryUsage);
|
||||
|
||||
// Get a count of users
|
||||
$data['countUsers'] = $this->userFactory->count();
|
||||
|
||||
// Get a count of active layouts, only for display groups we have permission for
|
||||
$params = ['now' => Carbon::now()->format('U')];
|
||||
|
||||
$sql = '
|
||||
SELECT IFNULL(COUNT(*), 0) AS count_scheduled
|
||||
FROM `schedule`
|
||||
WHERE (
|
||||
:now BETWEEN FromDT AND ToDT
|
||||
OR `schedule`.recurrence_range >= :now
|
||||
OR (
|
||||
IFNULL(`schedule`.recurrence_range, 0) = 0 AND IFNULL(`schedule`.recurrence_type, \'\') <> \'\'
|
||||
)
|
||||
)
|
||||
AND eventId IN (
|
||||
SELECT eventId
|
||||
FROM `lkscheduledisplaygroup`
|
||||
WHERE 1 = 1
|
||||
';
|
||||
|
||||
$this->displayFactory->viewPermissionSql('Xibo\Entity\DisplayGroup', $sql, $params, '`lkscheduledisplaygroup`.displayGroupId');
|
||||
|
||||
$sql .= ' ) ';
|
||||
|
||||
$sth = $dbh->prepare($sql);
|
||||
$sth->execute($params);
|
||||
|
||||
$data['nowShowing'] = $sth->fetchColumn(0);
|
||||
|
||||
// Latest news
|
||||
if ($this->getConfig()->getSetting('DASHBOARD_LATEST_NEWS_ENABLED') == 1
|
||||
&& !empty($this->getConfig()->getSetting('LATEST_NEWS_URL'))
|
||||
) {
|
||||
// Make sure we have the cache location configured
|
||||
MediaService::ensureLibraryExists($this->getConfig()->getSetting('LIBRARY_LOCATION'));
|
||||
|
||||
try {
|
||||
$feedUrl = $this->getConfig()->getSetting('LATEST_NEWS_URL');
|
||||
$cache = $this->pool->getItem('rss/' . md5($feedUrl));
|
||||
|
||||
$latestNews = $cache->get();
|
||||
|
||||
// Check the cache
|
||||
if ($cache->isMiss()) {
|
||||
// Create a Guzzle Client to get the Feed XML
|
||||
$client = new Client();
|
||||
$responseGuzzle = $client->get($feedUrl, $this->getConfig()->getGuzzleProxy());
|
||||
|
||||
// Pull out the content type and body
|
||||
$result = explode('charset=', $responseGuzzle->getHeaderLine('Content-Type'));
|
||||
$document['encoding'] = $result[1] ?? '';
|
||||
$document['xml'] = $responseGuzzle->getBody();
|
||||
|
||||
$this->getLog()->debug($document['xml']);
|
||||
|
||||
// Get the feed parser
|
||||
$reader = new Reader();
|
||||
$parser = $reader->getParser($feedUrl, $document['xml'], $document['encoding']);
|
||||
|
||||
// Get a feed object
|
||||
$feed = $parser->execute();
|
||||
|
||||
// Parse the items in the feed
|
||||
$latestNews = [];
|
||||
|
||||
foreach ($feed->getItems() as $item) {
|
||||
// Try to get the description tag
|
||||
$desc = $item->getTag('description');
|
||||
if (!$desc) {
|
||||
// use content with tags stripped
|
||||
$content = strip_tags($item->getContent());
|
||||
} else {
|
||||
// use description
|
||||
$content = ($desc[0] ?? strip_tags($item->getContent()));
|
||||
}
|
||||
|
||||
$latestNews[] = [
|
||||
'title' => $item->getTitle(),
|
||||
'description' => $content,
|
||||
'link' => $item->getUrl(),
|
||||
'date' => Carbon::instance($item->getDate())->format(DateFormatHelper::getSystemFormat()),
|
||||
];
|
||||
}
|
||||
|
||||
// Store in the cache for 1 day
|
||||
$cache->set($latestNews);
|
||||
$cache->expiresAfter(86400);
|
||||
|
||||
$this->pool->saveDeferred($cache);
|
||||
}
|
||||
|
||||
$data['latestNews'] = $latestNews;
|
||||
} catch (PicoFeedException $e) {
|
||||
$this->getLog()->error('Unable to get feed: %s', $e->getMessage());
|
||||
$this->getLog()->debug($e->getTraceAsString());
|
||||
|
||||
$data['latestNews'] = array(array('title' => __('Latest news not available.'), 'description' => '', 'link' => ''));
|
||||
}
|
||||
} else {
|
||||
$data['latestNews'] = array(array('title' => __('Latest news not enabled.'), 'description' => '', 'link' => ''));
|
||||
}
|
||||
|
||||
// Display Status and Media Inventory data - Level one
|
||||
$displays = $this->displayFactory->query();
|
||||
$displayLoggedIn = [];
|
||||
$displayMediaStatus = [];
|
||||
$displaysOnline = 0;
|
||||
$displaysOffline = 0;
|
||||
$displaysMediaUpToDate = 0;
|
||||
$displaysMediaNotUpToDate = 0;
|
||||
|
||||
foreach ($displays as $display) {
|
||||
$displayLoggedIn[] = $display->loggedIn;
|
||||
$displayMediaStatus[] = $display->mediaInventoryStatus;
|
||||
}
|
||||
|
||||
foreach ($displayLoggedIn as $status) {
|
||||
if ($status == 1) {
|
||||
$displaysOnline++;
|
||||
} else {
|
||||
$displaysOffline++;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($displayMediaStatus as $statusMedia) {
|
||||
if ($statusMedia == 1) {
|
||||
$displaysMediaUpToDate++;
|
||||
} else {
|
||||
$displaysMediaNotUpToDate++;
|
||||
}
|
||||
}
|
||||
|
||||
$data['displayStatus'] = json_encode([$displaysOnline, $displaysOffline]);
|
||||
$data['displayMediaStatus'] = json_encode([$displaysMediaUpToDate, $displaysMediaNotUpToDate]);
|
||||
} catch (Exception $e) {
|
||||
$this->getLog()->error($e->getMessage());
|
||||
$this->getLog()->debug($e->getTraceAsString());
|
||||
|
||||
// Show the error in place of the bandwidth chart
|
||||
$data['widget-error'] = 'Unable to get widget details';
|
||||
}
|
||||
|
||||
// Do we have an embedded widget?
|
||||
$data['embeddedWidget'] = html_entity_decode($this->getConfig()->getSetting('EMBEDDED_STATUS_WIDGET'));
|
||||
|
||||
// Render the Theme and output
|
||||
$this->getState()->template = 'dashboard-status-page';
|
||||
$this->getState()->setData($data);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function displayGroups(Request $request, Response $response)
|
||||
{
|
||||
$parsedQueryParams = $this->getSanitizer($request->getQueryParams());
|
||||
$status = null;
|
||||
$inventoryStatus = null;
|
||||
$params = [];
|
||||
$label = $parsedQueryParams->getString('status');
|
||||
$labelContent = $parsedQueryParams->getString('inventoryStatus');
|
||||
|
||||
$displayGroupIds = [];
|
||||
$displayGroupNames = [];
|
||||
$displaysAssigned = [];
|
||||
$data = [];
|
||||
|
||||
if (isset($label)) {
|
||||
if ($label == 'Online') {
|
||||
$status = 1;
|
||||
} else {
|
||||
$status = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($labelContent)) {
|
||||
if ($labelContent == 'Up to Date') {
|
||||
$inventoryStatus = 1;
|
||||
} else {
|
||||
$inventoryStatus = -1;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$sql = 'SELECT DISTINCT displaygroup.DisplayGroupID, displaygroup.displayGroup
|
||||
FROM displaygroup
|
||||
INNER JOIN `lkdisplaydg`
|
||||
ON lkdisplaydg.DisplayGroupID = displaygroup.DisplayGroupID
|
||||
INNER JOIN `display`
|
||||
ON display.displayid = lkdisplaydg.DisplayID
|
||||
WHERE
|
||||
displaygroup.IsDisplaySpecific = 0 ';
|
||||
|
||||
if ($status !== null) {
|
||||
$sql .= ' AND display.loggedIn = :status ';
|
||||
$params = ['status' => $status];
|
||||
}
|
||||
|
||||
if ($inventoryStatus != null) {
|
||||
if ($inventoryStatus === -1) {
|
||||
$sql .= ' AND display.MediaInventoryStatus <> 1';
|
||||
} else {
|
||||
$sql .= ' AND display.MediaInventoryStatus = :inventoryStatus';
|
||||
$params = ['inventoryStatus' => $inventoryStatus];
|
||||
}
|
||||
}
|
||||
|
||||
$this->displayFactory->viewPermissionSql('Xibo\Entity\DisplayGroup', $sql, $params, '`lkdisplaydg`.displayGroupId', null, [], 'permissionsFolderId');
|
||||
|
||||
$sql .= ' ORDER BY displaygroup.DisplayGroup ';
|
||||
|
||||
$results = $this->store->select($sql, $params);
|
||||
|
||||
foreach ($results as $row) {
|
||||
$displayGroupNames[] = $row['displayGroup'];
|
||||
$displayGroupIds[] = $row['DisplayGroupID'];
|
||||
$displaysAssigned[] = count($this->displayFactory->query(['displayGroup'], ['displayGroupId' => $row['DisplayGroupID'], 'mediaInventoryStatus' => $inventoryStatus, 'loggedIn' => $status]));
|
||||
}
|
||||
|
||||
$data['displayGroupNames'] = json_encode($displayGroupNames);
|
||||
$data['displayGroupIds'] = json_encode($displayGroupIds);
|
||||
$data['displayGroupMembers'] = json_encode($displaysAssigned);
|
||||
|
||||
$this->getState()->setData($data);
|
||||
} catch (Exception $e) {
|
||||
$this->getLog()->error($e->getMessage());
|
||||
$this->getLog()->debug($e->getTraceAsString());
|
||||
}
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
722
lib/Controller/SyncGroup.php
Executable file
722
lib/Controller/SyncGroup.php
Executable file
@@ -0,0 +1,722 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\FolderFactory;
|
||||
use Xibo\Factory\SyncGroupFactory;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\ControllerNotImplemented;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class SyncGroup
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class SyncGroup extends Base
|
||||
{
|
||||
private SyncGroupFactory $syncGroupFactory;
|
||||
private FolderFactory $folderFactory;
|
||||
|
||||
public function __construct(
|
||||
SyncGroupFactory $syncGroupFactory,
|
||||
FolderFactory $folderFactory
|
||||
) {
|
||||
$this->syncGroupFactory = $syncGroupFactory;
|
||||
$this->folderFactory = $folderFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync Group Page Render
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'syncgroup-page';
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/syncgroups",
|
||||
* summary="Get Sync Groups",
|
||||
* tags={"syncGroup"},
|
||||
* operationId="syncGroupSearch",
|
||||
* @SWG\Parameter(
|
||||
* name="syncGroupId",
|
||||
* in="query",
|
||||
* description="Filter by syncGroup Id",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="query",
|
||||
* description="Filter by syncGroup Name",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="ownerId",
|
||||
* in="query",
|
||||
* description="Filter by Owner ID",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="folderId",
|
||||
* in="query",
|
||||
* description="Filter by Folder ID",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="a successful response",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/SyncGroup")
|
||||
* ),
|
||||
* @SWG\Header(
|
||||
* header="X-Total-Count",
|
||||
* description="The total number of records",
|
||||
* type="integer"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return Response|ResponseInterface
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function grid(Request $request, Response $response): Response|\Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
$parsedQueryParams = $this->getSanitizer($request->getQueryParams());
|
||||
|
||||
$filter = [
|
||||
'syncGroupId' => $parsedQueryParams->getInt('syncGroupId'),
|
||||
'name' => $parsedQueryParams->getString('name'),
|
||||
'folderId' => $parsedQueryParams->getInt('folderId'),
|
||||
'ownerId' => $parsedQueryParams->getInt('ownerId'),
|
||||
'leadDisplayId' => $parsedQueryParams->getInt('leadDisplayId')
|
||||
];
|
||||
|
||||
$syncGroups = $this->syncGroupFactory->query(
|
||||
$this->gridRenderSort($parsedQueryParams),
|
||||
$this->gridRenderFilter($filter, $parsedQueryParams)
|
||||
);
|
||||
|
||||
foreach ($syncGroups as $syncGroup) {
|
||||
if (!empty($syncGroup->leadDisplayId)) {
|
||||
try {
|
||||
$display = $this->syncGroupFactory->getLeadDisplay($syncGroup->leadDisplayId);
|
||||
$syncGroup->leadDisplay = $display->display;
|
||||
} catch (NotFoundException $exception) {
|
||||
$this->getLog()->error(
|
||||
sprintf(
|
||||
'Lead Display %d not found for %s',
|
||||
$syncGroup->leadDisplayId,
|
||||
$syncGroup->name
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isApi($request)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$syncGroup->includeProperty('buttons');
|
||||
|
||||
if ($this->getUser()->featureEnabled('display.syncModify')
|
||||
&& $this->getUser()->checkEditable($syncGroup)
|
||||
) {
|
||||
// Edit
|
||||
$syncGroup->buttons[] = [
|
||||
'id' => 'syncgroup_button_group_edit',
|
||||
'url' => $this->urlFor($request, 'syncgroup.form.edit', ['id' => $syncGroup->syncGroupId]),
|
||||
'text' => __('Edit')
|
||||
];
|
||||
// Group Members
|
||||
$syncGroup->buttons[] = [
|
||||
'id' => 'syncgroup_button_group_members',
|
||||
'url' => $this->urlFor($request, 'syncgroup.form.members', ['id' => $syncGroup->syncGroupId]),
|
||||
'text' => __('Members')
|
||||
];
|
||||
$syncGroup->buttons[] = ['divider' => true];
|
||||
|
||||
// Delete
|
||||
$syncGroup->buttons[] = [
|
||||
'id' => 'syncgroup_button_group_delete',
|
||||
'url' => $this->urlFor($request, 'syncgroup.form.delete', ['id' => $syncGroup->syncGroupId]),
|
||||
'text' => __('Delete')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->syncGroupFactory->countLast();
|
||||
$this->getState()->setData($syncGroups);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return Response|ResponseInterface
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
*/
|
||||
public function addForm(Request $request, Response $response): Response|ResponseInterface
|
||||
{
|
||||
$this->getState()->template = 'syncgroup-form-add';
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Sync Group
|
||||
* @SWG\Post(
|
||||
* path="/syncgroup/add",
|
||||
* operationId="syncGroupAdd",
|
||||
* tags={"syncGroup"},
|
||||
* summary="Add a Sync Group",
|
||||
* description="Add a new Sync Group to the CMS",
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="formData",
|
||||
* description="The Sync Group Name",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="syncPublisherPort",
|
||||
* in="formData",
|
||||
* description="The publisher port number on which sync group members will communicate - default 9590",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="folderId",
|
||||
* in="formData",
|
||||
* description="Folder ID to which this object should be assigned to",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/DisplayGroup"),
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new DisplayGroup",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return Response|ResponseInterface
|
||||
* @throws AccessDeniedException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function add(Request $request, Response $response): Response|ResponseInterface
|
||||
{
|
||||
if (!$this->getUser()->featureEnabled('display.syncAdd')) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
|
||||
// Folders
|
||||
$folderId = $params->getInt('folderId');
|
||||
if ($folderId === 1) {
|
||||
$this->checkRootFolderAllowSave();
|
||||
}
|
||||
|
||||
if (empty($folderId) || !$this->getUser()->featureEnabled('folder.view')) {
|
||||
$folderId = $this->getUser()->homeFolderId;
|
||||
}
|
||||
|
||||
$folder = $this->folderFactory->getById($folderId, 0);
|
||||
|
||||
$syncGroup = $this->syncGroupFactory->createEmpty();
|
||||
$syncGroup->name = $params->getString('name');
|
||||
$syncGroup->ownerId = $this->getUser()->userId;
|
||||
$syncGroup->syncPublisherPort = $params->getInt('syncPublisherPort');
|
||||
$syncGroup->syncSwitchDelay = $params->getInt('syncSwitchDelay');
|
||||
$syncGroup->syncVideoPauseDelay = $params->getInt('syncVideoPauseDelay');
|
||||
$syncGroup->folderId = $folder->getId();
|
||||
$syncGroup->permissionsFolderId = $folder->getPermissionFolderIdOrThis();
|
||||
|
||||
$syncGroup->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => sprintf(__('Added %s'), $syncGroup->name),
|
||||
'id' => $syncGroup->syncGroupId,
|
||||
'data' => $syncGroup
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return Response|ResponseInterface
|
||||
* @throws AccessDeniedException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function membersForm(Request $request, Response $response, $id): Response|ResponseInterface
|
||||
{
|
||||
$syncGroup = $this->syncGroupFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($syncGroup)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Displays in Group
|
||||
$displaysAssigned = $syncGroup->getSyncGroupMembers();
|
||||
|
||||
$this->getState()->template = 'syncgroup-form-members';
|
||||
$this->getState()->setData([
|
||||
'syncGroup' => $syncGroup,
|
||||
'extra' => [
|
||||
'displaysAssigned' => $displaysAssigned,
|
||||
],
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Post(
|
||||
* path="/syncgroup/{syncGroupId}/members",
|
||||
* operationId="syncGroupMembers",
|
||||
* tags={"syncGroup"},
|
||||
* summary="Assign one or more Displays to a Sync Group",
|
||||
* description="Adds the provided Displays to the Sync Group",
|
||||
* @SWG\Parameter(
|
||||
* name="syncGroupId",
|
||||
* type="integer",
|
||||
* in="path",
|
||||
* description="The Sync Group to assign to",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="displayId",
|
||||
* type="array",
|
||||
* in="formData",
|
||||
* description="The Display Ids to assign",
|
||||
* required=true,
|
||||
* @SWG\Items(
|
||||
* type="integer"
|
||||
* )
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="unassignDisplayId",
|
||||
* in="formData",
|
||||
* description="An optional array of Display IDs to unassign",
|
||||
* type="array",
|
||||
* required=false,
|
||||
* @SWG\Items(type="integer")
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return Response|ResponseInterface
|
||||
* @throws AccessDeniedException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function members(Request $request, Response $response, $id): Response|ResponseInterface
|
||||
{
|
||||
$syncGroup = $this->syncGroupFactory->getById($id);
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
if (!$this->getUser()->checkEditable($syncGroup)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Support both an array and a single int.
|
||||
$displays = $sanitizedParams->getParam('displayId');
|
||||
if (is_numeric($displays)) {
|
||||
$displays = [$sanitizedParams->getInt('displayId')];
|
||||
} else {
|
||||
$displays = $sanitizedParams->getIntArray('displayId', ['default' => []]);
|
||||
}
|
||||
|
||||
$syncGroup->setMembers($displays);
|
||||
|
||||
// Have we been provided with unassign id's as well?
|
||||
$unSetDisplays = $sanitizedParams->getParam('unassignDisplayId');
|
||||
if (is_numeric($unSetDisplays)) {
|
||||
$unSetDisplays = [$sanitizedParams->getInt('unassignDisplayId')];
|
||||
} else {
|
||||
$unSetDisplays = $sanitizedParams->getIntArray('unassignDisplayId', ['default' => []]);
|
||||
}
|
||||
|
||||
$syncGroup->unSetMembers($unSetDisplays);
|
||||
$syncGroup->modifiedBy = $this->getUser()->userId;
|
||||
|
||||
if (empty($syncGroup->getSyncGroupMembers()) ||
|
||||
in_array($syncGroup->leadDisplayId, $unSetDisplays)
|
||||
) {
|
||||
$syncGroup->leadDisplayId = null;
|
||||
}
|
||||
|
||||
$syncGroup->save(['validate' => false]);
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Displays assigned to %s'), $syncGroup->name),
|
||||
'id' => $syncGroup->syncGroupId
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return Response|ResponseInterface
|
||||
* @throws AccessDeniedException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function editForm(Request $request, Response $response, $id): Response|ResponseInterface
|
||||
{
|
||||
$syncGroup = $this->syncGroupFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkEditable($syncGroup)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$leadDisplay = null;
|
||||
|
||||
if (!empty($syncGroup->leadDisplayId)) {
|
||||
$leadDisplay = $this->syncGroupFactory->getLeadDisplay($syncGroup->leadDisplayId);
|
||||
}
|
||||
|
||||
$this->getState()->template = 'syncgroup-form-edit';
|
||||
$this->getState()->setData([
|
||||
'syncGroup' => $syncGroup,
|
||||
'leadDisplay' => $leadDisplay,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits a Sync Group
|
||||
* @SWG\Post(
|
||||
* path="/syncgroup/{syncGroupId}/edit",
|
||||
* operationId="syncGroupEdit",
|
||||
* tags={"syncGroup"},
|
||||
* summary="Edit a Sync Group",
|
||||
* description="Edit an existing Sync Group",
|
||||
* @SWG\Parameter(
|
||||
* name="syncGroupId",
|
||||
* type="integer",
|
||||
* in="path",
|
||||
* description="The Sync Group to assign to",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="formData",
|
||||
* description="The Sync Group Name",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="syncPublisherPort",
|
||||
* in="formData",
|
||||
* description="The publisher port number on which sync group members will communicate - default 9590",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="syncSwitchDelay",
|
||||
* in="formData",
|
||||
* description="The delay (in ms) when displaying the changes in content - default 750",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="syncVideoPauseDelay",
|
||||
* in="formData",
|
||||
* description="The delay (in ms) before unpausing the video on start - default 100",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="leadDisplayId",
|
||||
* in="formData",
|
||||
* description="The ID of the Display that belongs to this Sync Group and should act as a Lead Display",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="folderId",
|
||||
* in="formData",
|
||||
* description="Folder ID to which this object should be assigned to",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/DisplayGroup"),
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new DisplayGroup",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return Response|ResponseInterface
|
||||
* @throws AccessDeniedException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function edit(Request $request, Response $response, $id): Response|ResponseInterface
|
||||
{
|
||||
$syncGroup = $this->syncGroupFactory->getById($id);
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
|
||||
if (!$this->getUser()->checkEditable($syncGroup)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Folders
|
||||
$folderId = $params->getInt('folderId');
|
||||
if ($folderId === 1) {
|
||||
$this->checkRootFolderAllowSave();
|
||||
}
|
||||
|
||||
if (empty($folderId) || !$this->getUser()->featureEnabled('folder.view')) {
|
||||
$folderId = $this->getUser()->homeFolderId;
|
||||
}
|
||||
|
||||
$folder = $this->folderFactory->getById($folderId, 0);
|
||||
|
||||
$syncGroup->name = $params->getString('name');
|
||||
$syncGroup->syncPublisherPort = $params->getInt('syncPublisherPort');
|
||||
$syncGroup->syncSwitchDelay = $params->getInt('syncSwitchDelay');
|
||||
$syncGroup->syncVideoPauseDelay = $params->getInt('syncVideoPauseDelay');
|
||||
$syncGroup->leadDisplayId = $params->getInt('leadDisplayId');
|
||||
$syncGroup->modifiedBy = $this->getUser()->userId;
|
||||
$syncGroup->folderId = $folder->getId();
|
||||
$syncGroup->permissionsFolderId = $folder->getPermissionFolderIdOrThis();
|
||||
|
||||
$syncGroup->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'message' => sprintf(__('Edited %s'), $syncGroup->name),
|
||||
'id' => $syncGroup->syncGroupId,
|
||||
'data' => $syncGroup
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return Response|ResponseInterface
|
||||
* @throws AccessDeniedException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function deleteForm(Request $request, Response $response, $id): Response|ResponseInterface
|
||||
{
|
||||
$syncGroup = $this->syncGroupFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($syncGroup)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
// Set the form
|
||||
$this->getState()->template = 'syncgroup-form-delete';
|
||||
$this->getState()->setData([
|
||||
'syncGroup' => $syncGroup,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Delete(
|
||||
* path="/syncgroup/{syncGroupId}/delete",
|
||||
* operationId="syncGroupDelete",
|
||||
* tags={"syncGroup"},
|
||||
* summary="Delete a Sync Group",
|
||||
* description="Delete an existing Sync Group identified by its Id",
|
||||
* @SWG\Parameter(
|
||||
* name="syncGroupId",
|
||||
* type="integer",
|
||||
* in="path",
|
||||
* description="The syncGroupId to delete",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return Response|ResponseInterface
|
||||
* @throws AccessDeniedException
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function delete(Request $request, Response $response, $id): Response|ResponseInterface
|
||||
{
|
||||
$syncGroup = $this->syncGroupFactory->getById($id);
|
||||
|
||||
if (!$this->getUser()->checkDeleteable($syncGroup)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$syncGroup->delete();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Deleted %s'), $syncGroup->name)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/syncgroup/{syncGroupId}/displays",
|
||||
* summary="Get members of this sync group",
|
||||
* tags={"syncGroup"},
|
||||
* operationId="syncGroupDisplays",
|
||||
* @SWG\Parameter(
|
||||
* name="syncGroupId",
|
||||
* type="integer",
|
||||
* in="path",
|
||||
* description="The syncGroupId to delete",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="eventId",
|
||||
* in="query",
|
||||
* description="Filter by event ID - return will include Layouts Ids scheduled against each group member",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="a successful response",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/SyncGroup")
|
||||
* ),
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return Response|ResponseInterface
|
||||
* @throws ControllerNotImplemented
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function fetchDisplays(Request $request, Response $response, $id): Response|ResponseInterface
|
||||
{
|
||||
$syncGroup = $this->syncGroupFactory->getById($id);
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
$displays = [];
|
||||
|
||||
if (!empty($params->getInt('eventId'))) {
|
||||
$syncGroupMembers = $syncGroup->getGroupMembersForForm();
|
||||
foreach ($syncGroupMembers as $display) {
|
||||
$layoutId = $syncGroup->getLayoutIdForDisplay(
|
||||
$params->getInt('eventId'),
|
||||
$display['displayId']
|
||||
);
|
||||
$display['layoutId'] = $layoutId;
|
||||
$displays[] = $display;
|
||||
}
|
||||
} else {
|
||||
$displays = $syncGroup->getGroupMembersForForm();
|
||||
}
|
||||
|
||||
$this->getState()->setData([
|
||||
'displays' => $displays
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
766
lib/Controller/Tag.php
Normal file
766
lib/Controller/Tag.php
Normal file
@@ -0,0 +1,766 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2023 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Event\DisplayGroupLoadEvent;
|
||||
use Xibo\Event\TagAddEvent;
|
||||
use Xibo\Event\TagDeleteEvent;
|
||||
use Xibo\Event\TagEditEvent;
|
||||
use Xibo\Event\TriggerTaskEvent;
|
||||
use Xibo\Factory\CampaignFactory;
|
||||
use Xibo\Factory\DisplayFactory;
|
||||
use Xibo\Factory\DisplayGroupFactory;
|
||||
use Xibo\Factory\LayoutFactory;
|
||||
use Xibo\Factory\MediaFactory;
|
||||
use Xibo\Factory\PlaylistFactory;
|
||||
use Xibo\Factory\ScheduleFactory;
|
||||
use Xibo\Factory\TagFactory;
|
||||
use Xibo\Factory\UserFactory;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class Tag
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class Tag extends Base
|
||||
{
|
||||
/** @var CampaignFactory */
|
||||
private $campaignFactory;
|
||||
|
||||
/**
|
||||
* @var DisplayFactory
|
||||
*/
|
||||
private $displayFactory;
|
||||
|
||||
/**
|
||||
* @var DisplayGroupFactory
|
||||
*/
|
||||
private $displayGroupFactory;
|
||||
|
||||
/**
|
||||
* @var LayoutFactory
|
||||
*/
|
||||
private $layoutFactory;
|
||||
|
||||
/**
|
||||
* @var MediaFactory
|
||||
*/
|
||||
private $mediaFactory;
|
||||
|
||||
/** @var PlaylistFactory */
|
||||
private $playlistFactory;
|
||||
|
||||
/**
|
||||
* @var ScheduleFactory
|
||||
*/
|
||||
private $scheduleFactory;
|
||||
|
||||
/**
|
||||
* @var TagFactory
|
||||
*/
|
||||
private $tagFactory;
|
||||
|
||||
/** @var UserFactory */
|
||||
private $userFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param DisplayGroupFactory $displayGroupFactory
|
||||
* @param LayoutFactory $layoutFactory
|
||||
* @param TagFactory $tagFactory
|
||||
* @param UserFactory $userFactory
|
||||
* @param DisplayFactory $displayFactory
|
||||
* @param MediaFactory $mediaFactory
|
||||
* @param ScheduleFactory $scheduleFactory
|
||||
* @param CampaignFactory $campaignFactory
|
||||
* @param PlaylistFactory $playlistFactory
|
||||
*/
|
||||
public function __construct($displayGroupFactory, $layoutFactory, $tagFactory, $userFactory, $displayFactory, $mediaFactory, $scheduleFactory, $campaignFactory, $playlistFactory)
|
||||
{
|
||||
$this->displayGroupFactory = $displayGroupFactory;
|
||||
$this->layoutFactory = $layoutFactory;
|
||||
$this->tagFactory = $tagFactory;
|
||||
$this->userFactory = $userFactory;
|
||||
$this->displayFactory = $displayFactory;
|
||||
$this->mediaFactory = $mediaFactory;
|
||||
$this->scheduleFactory = $scheduleFactory;
|
||||
$this->campaignFactory = $campaignFactory;
|
||||
$this->playlistFactory = $playlistFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'tag-page';
|
||||
$this->getState()->setData([
|
||||
'users' => $this->userFactory->query()
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag Search
|
||||
*
|
||||
* @SWG\Get(
|
||||
* path="/tag",
|
||||
* operationId="tagSearch",
|
||||
* tags={"tags"},
|
||||
* summary="Search Tags",
|
||||
* description="Search for Tags viewable by this user",
|
||||
* @SWG\Parameter(
|
||||
* name="tagId",
|
||||
* in="query",
|
||||
* description="Filter by Tag Id",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="tag",
|
||||
* in="query",
|
||||
* description="Filter by partial Tag",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="exactTag",
|
||||
* in="query",
|
||||
* description="Filter by exact Tag",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="isSystem",
|
||||
* in="query",
|
||||
* description="Filter by isSystem flag",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="isRequired",
|
||||
* in="query",
|
||||
* description="Filter by isRequired flag",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="haveOptions",
|
||||
* in="query",
|
||||
* description="Set to 1 to show only results that have options set",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/Tag")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
function grid(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedQueryParams = $this->getSanitizer($request->getQueryParams());
|
||||
|
||||
$filter = [
|
||||
'tagId' => $sanitizedQueryParams->getInt('tagId'),
|
||||
'tag' => $sanitizedQueryParams->getString('tag'),
|
||||
'useRegexForName' => $sanitizedQueryParams->getCheckbox('useRegexForName'),
|
||||
'isSystem' => $sanitizedQueryParams->getCheckbox('isSystem'),
|
||||
'isRequired' => $sanitizedQueryParams->getCheckbox('isRequired'),
|
||||
'haveOptions' => $sanitizedQueryParams->getCheckbox('haveOptions'),
|
||||
'allTags' => $sanitizedQueryParams->getInt('allTags'),
|
||||
'logicalOperatorName' => $sanitizedQueryParams->getString('logicalOperatorName'),
|
||||
];
|
||||
|
||||
$tags = $this->tagFactory->query($this->gridRenderSort($sanitizedQueryParams), $this->gridRenderFilter($filter, $sanitizedQueryParams));
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
/* @var \Xibo\Entity\Tag $tag */
|
||||
|
||||
if ($this->isApi($request)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tag->includeProperty('buttons');
|
||||
$tag->buttons = [];
|
||||
|
||||
|
||||
//Show buttons for non system tags
|
||||
if ($tag->isSystem === 0) {
|
||||
// Edit the Tag
|
||||
$tag->buttons[] = [
|
||||
'id' => 'tag_button_edit',
|
||||
'url' => $this->urlFor($request,'tag.edit.form', ['id' => $tag->tagId]),
|
||||
'text' => __('Edit')
|
||||
];
|
||||
|
||||
// Delete Tag
|
||||
$tag->buttons[] = [
|
||||
'id' => 'tag_button_delete',
|
||||
'url' => $this->urlFor($request,'tag.delete.form', ['id' => $tag->tagId]),
|
||||
'text' => __('Delete'),
|
||||
'multi-select' => true,
|
||||
'dataAttributes' => [
|
||||
['name' => 'commit-url', 'value' => $this->urlFor($request,'tag.delete', ['id' => $tag->tagId])],
|
||||
['name' => 'commit-method', 'value' => 'delete'],
|
||||
['name' => 'id', 'value' => 'tag_button_delete'],
|
||||
['name' => 'text', 'value' => __('Delete')],
|
||||
['name' => 'sort-group', 'value' => 1],
|
||||
['name' => 'rowtitle', 'value' => $tag->tag]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$tag->buttons[] = [
|
||||
'id' => 'tag_button_usage',
|
||||
'url' => $this->urlFor($request, 'tag.usage.form', ['id' => $tag->tagId]),
|
||||
'text' => __('Usage')
|
||||
];
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->tagFactory->countLast();
|
||||
$this->getState()->setData($tags);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag Add Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function addForm(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'tag-form-add';
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Tag
|
||||
*
|
||||
* @SWG\Post(
|
||||
* path="/tag",
|
||||
* operationId="tagAdd",
|
||||
* tags={"tags"},
|
||||
* summary="Add a new Tag",
|
||||
* description="Add a new Tag",
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="formData",
|
||||
* description="Tag name",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="isRequired",
|
||||
* in="formData",
|
||||
* description="A flag indicating whether value selection on assignment is required",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="options",
|
||||
* in="formData",
|
||||
* description="A comma separated string of Tag options",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/Tag")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\DuplicateEntityException
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function add(Request $request, Response $response)
|
||||
{
|
||||
if (!$this->getUser()->isSuperAdmin()) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$values = [];
|
||||
$tag = $this->tagFactory->create($sanitizedParams->getString('name'));
|
||||
$tag->options = [];
|
||||
$tag->isRequired = $sanitizedParams->getCheckbox('isRequired');
|
||||
$optionValues = $sanitizedParams->getString('options');
|
||||
|
||||
if ($optionValues != '') {
|
||||
$optionValuesArray = explode(',', $optionValues);
|
||||
foreach ($optionValuesArray as $options) {
|
||||
$values[] = $options;
|
||||
}
|
||||
$tag->options = json_encode($values);
|
||||
} else {
|
||||
$tag->options = null;
|
||||
}
|
||||
|
||||
$tag->save();
|
||||
|
||||
// dispatch Tag add event
|
||||
$event = new TagAddEvent($tag->tagId);
|
||||
$this->getDispatcher()->dispatch($event, $event::$NAME);
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => sprintf(__('Added %s'), $tag->tag),
|
||||
'id' => $tag->tagId,
|
||||
'data' => $tag
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a Tag
|
||||
*
|
||||
* @SWG\Put(
|
||||
* path="/tag/{tagId}",
|
||||
* operationId="tagEdit",
|
||||
* tags={"tags"},
|
||||
* summary="Edit existing Tag",
|
||||
* description="Edit existing Tag",
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="formData",
|
||||
* description="Tag name",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="isRequired",
|
||||
* in="formData",
|
||||
* description="A flag indicating whether value selection on assignment is required",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="options",
|
||||
* in="formData",
|
||||
* description="A comma separated string of Tag options",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/Tag")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function editForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$tag = $this->tagFactory->getById($id);
|
||||
$tagOptions = '';
|
||||
|
||||
if (isset($tag->options)) {
|
||||
$tagOptions = implode(',', json_decode($tag->options));
|
||||
}
|
||||
|
||||
$this->getState()->template = 'tag-form-edit';
|
||||
$this->getState()->setData([
|
||||
'tag' => $tag,
|
||||
'options' => $tagOptions,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
public function usageForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$tag = $this->tagFactory->getById($id);
|
||||
|
||||
$this->getState()->template = 'tag-usage-form';
|
||||
$this->getState()->setData([
|
||||
'tag' => $tag
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
public function usage(Request $request, Response $response, $id)
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
$sanitizedQueryParams = $this->getSanitizer($request->getQueryParams());
|
||||
|
||||
$filter = [
|
||||
'tagId' => $id,
|
||||
];
|
||||
|
||||
$entries = $this->tagFactory->getAllLinks(
|
||||
$this->gridRenderSort($sanitizedParams),
|
||||
$this->gridRenderFilter($filter, $sanitizedQueryParams)
|
||||
);
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->tagFactory->countLast();
|
||||
$this->getState()->setData($entries);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a Tag
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\DuplicateEntityException
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function edit(Request $request, Response $response, $id)
|
||||
{
|
||||
if (!$this->getUser()->isSuperAdmin()) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
$tag = $this->tagFactory->getById($id);
|
||||
|
||||
if ($tag->isSystem === 1) {
|
||||
throw new AccessDeniedException(__('Access denied System tags cannot be edited'));
|
||||
}
|
||||
|
||||
if(isset($tag->options)) {
|
||||
$tagOptionsCurrent = implode(',', json_decode($tag->options));
|
||||
$tagOptionsArrayCurrent = explode(',', $tagOptionsCurrent);
|
||||
}
|
||||
|
||||
$values = [];
|
||||
|
||||
$oldTag = $tag->tag;
|
||||
$tag->tag = $sanitizedParams->getString('name');
|
||||
$tag->isRequired = $sanitizedParams->getCheckbox('isRequired');
|
||||
$optionValues = $sanitizedParams->getString('options');
|
||||
|
||||
if ($optionValues != '') {
|
||||
$optionValuesArray = explode(',', $optionValues);
|
||||
foreach ($optionValuesArray as $option) {
|
||||
$values[] = trim($option);
|
||||
}
|
||||
$tag->options = json_encode($values);
|
||||
} else {
|
||||
$tag->options = null;
|
||||
}
|
||||
|
||||
// if option were changed, we need to compare the array of options before and after edit
|
||||
if($tag->hasPropertyChanged('options')) {
|
||||
|
||||
if (isset($tagOptionsArrayCurrent)) {
|
||||
|
||||
if(isset($tag->options)) {
|
||||
$tagOptions = implode(',', json_decode($tag->options));
|
||||
$tagOptionsArray = explode(',', $tagOptions);
|
||||
} else {
|
||||
$tagOptionsArray = [];
|
||||
}
|
||||
|
||||
// compare array of options before and after the Tag edit was made
|
||||
$tagValuesToRemove = array_diff($tagOptionsArrayCurrent, $tagOptionsArray);
|
||||
|
||||
// go through every element of the new array and set the value to null if removed value was assigned to one of the lktag tables
|
||||
$tag->updateTagValues($tagValuesToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
$tag->save();
|
||||
|
||||
// dispatch Tag edit event
|
||||
$event = new TagEditEvent($tag->tagId, $oldTag, $tag->tag);
|
||||
$this->getDispatcher()->dispatch($event, $event::$NAME);
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => sprintf(__('Edited %s'), $tag->tag),
|
||||
'id' => $tag->tagId,
|
||||
'data' => $tag
|
||||
]);
|
||||
|
||||
return $this->render($request,$response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the Delete Group Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
function deleteForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$tag = $this->tagFactory->getById($id);
|
||||
|
||||
$this->getState()->template = 'tag-form-delete';
|
||||
$this->getState()->setData([
|
||||
'tag' => $tag,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Tag
|
||||
*
|
||||
* @SWG\Delete(
|
||||
* path="/tag/{tagId}",
|
||||
* operationId="tagDelete",
|
||||
* tags={"tags"},
|
||||
* summary="Delete Tag",
|
||||
* description="Delete a Tag",
|
||||
* @SWG\Parameter(
|
||||
* name="tagId",
|
||||
* in="path",
|
||||
* description="The Tag ID to delete",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ConfigurationException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\DuplicateEntityException
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function delete(Request $request, Response $response, $id)
|
||||
{
|
||||
if (!$this->getUser()->isSuperAdmin()) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$tag = $this->tagFactory->getById($id);
|
||||
|
||||
if ($tag->isSystem === 1) {
|
||||
throw new AccessDeniedException(__('Access denied System tags cannot be deleted'));
|
||||
}
|
||||
|
||||
// Dispatch delete event, remove this tag links in all lktag tables.
|
||||
$event = new TagDeleteEvent($tag->tagId);
|
||||
$this->getDispatcher()->dispatch($event, $event::$NAME);
|
||||
// tag delete, remove the record from tag table
|
||||
$tag->delete();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Deleted %s'), $tag->tag)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function loadTagOptions(Request $request, Response $response)
|
||||
{
|
||||
$tagName = $this->getSanitizer($request->getParams())->getString('name');
|
||||
|
||||
try {
|
||||
$tag = $this->tagFactory->getByTag($tagName);
|
||||
} catch (NotFoundException $e) {
|
||||
// User provided new tag, which is fine
|
||||
$tag = null;
|
||||
}
|
||||
|
||||
$this->getState()->setData([
|
||||
'tag' => ($tag === null) ? null : $tag
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ConfigurationException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\DuplicateEntityException
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function editMultiple(Request $request, Response $response)
|
||||
{
|
||||
// Handle permissions
|
||||
if (!$this->getUser()->featureEnabled('tag.tagging')) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$targetType = $sanitizedParams->getString('targetType');
|
||||
$targetIds = $sanitizedParams->getString('targetIds');
|
||||
$tagsToAdd = $sanitizedParams->getString('addTags');
|
||||
$tagsToRemove = $sanitizedParams->getString('removeTags');
|
||||
|
||||
// check if we need to do anything first
|
||||
if ($tagsToAdd != '' || $tagsToRemove != '') {
|
||||
|
||||
// covert comma separated string of ids into array
|
||||
$targetIdsArray = explode(',', $targetIds);
|
||||
|
||||
// get tags to assign and unassign
|
||||
$tags = $this->tagFactory->tagsFromString($tagsToAdd);
|
||||
$untags = $this->tagFactory->tagsFromString($tagsToRemove);
|
||||
|
||||
// depending on the type we need different factory
|
||||
switch ($targetType){
|
||||
case 'layout':
|
||||
$entityFactory = $this->layoutFactory;
|
||||
break;
|
||||
case 'playlist':
|
||||
$entityFactory = $this->playlistFactory;
|
||||
break;
|
||||
case 'media':
|
||||
$entityFactory = $this->mediaFactory;
|
||||
break;
|
||||
case 'campaign':
|
||||
$entityFactory = $this->campaignFactory;
|
||||
break;
|
||||
case 'displayGroup':
|
||||
case 'display':
|
||||
$entityFactory = $this->displayGroupFactory;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException(__('Edit multiple tags is not supported on this item'), 'targetType');
|
||||
}
|
||||
|
||||
foreach ($targetIdsArray as $id) {
|
||||
// get the entity by provided id, for display we need different function
|
||||
$this->getLog()->debug('editMultiple: lookup using id: ' . $id . ' for type: ' . $targetType);
|
||||
if ($targetType === 'display') {
|
||||
$entity = $entityFactory->getDisplaySpecificByDisplayId($id);
|
||||
} else {
|
||||
$entity = $entityFactory->getById($id);
|
||||
}
|
||||
|
||||
if ($targetType === 'display' || $targetType === 'displaygroup') {
|
||||
$this->getDispatcher()->dispatch(new DisplayGroupLoadEvent($entity), DisplayGroupLoadEvent::$NAME);
|
||||
}
|
||||
|
||||
foreach ($untags as $untag) {
|
||||
$entity->unassignTag($untag);
|
||||
}
|
||||
|
||||
// go through tags and adjust assignments.
|
||||
foreach ($tags as $tag) {
|
||||
$entity->assignTag($tag);
|
||||
}
|
||||
|
||||
$entity->save(['isTagEdit' => true]);
|
||||
}
|
||||
|
||||
// Once we're done, and if we're a Display entity, we need to calculate the dynamic display groups
|
||||
if ($targetType === 'display') {
|
||||
// Background update.
|
||||
$this->getDispatcher()->dispatch(
|
||||
new TriggerTaskEvent('\Xibo\XTR\MaintenanceRegularTask', 'DYNAMIC_DISPLAY_GROUP_ASSESSED'),
|
||||
TriggerTaskEvent::$NAME
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$this->getLog()->debug('Tags were not changed');
|
||||
}
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => __('Tags Edited')
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
621
lib/Controller/Task.php
Normal file
621
lib/Controller/Task.php
Normal file
@@ -0,0 +1,621 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2025 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Cron\CronExpression;
|
||||
use Illuminate\Support\Str;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Stash\Interfaces\PoolInterface;
|
||||
use Xibo\Factory\TaskFactory;
|
||||
use Xibo\Helper\DateFormatHelper;
|
||||
use Xibo\Storage\StorageServiceInterface;
|
||||
use Xibo\Storage\TimeSeriesStoreInterface;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
use Xibo\XTR\TaskInterface;
|
||||
|
||||
/**
|
||||
* Class Task
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class Task extends Base
|
||||
{
|
||||
/** @var TaskFactory */
|
||||
private $taskFactory;
|
||||
|
||||
/** @var StorageServiceInterface */
|
||||
private $store;
|
||||
|
||||
/** @var TimeSeriesStoreInterface */
|
||||
private $timeSeriesStore;
|
||||
|
||||
/** @var PoolInterface */
|
||||
private $pool;
|
||||
|
||||
/** ContainerInterface */
|
||||
private $container;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param StorageServiceInterface $store
|
||||
* @param TimeSeriesStoreInterface $timeSeriesStore
|
||||
* @param PoolInterface $pool
|
||||
* @param TaskFactory $taskFactory
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct($store, $timeSeriesStore, $pool, $taskFactory, ContainerInterface $container)
|
||||
{
|
||||
$this->taskFactory = $taskFactory;
|
||||
$this->store = $store;
|
||||
$this->timeSeriesStore = $timeSeriesStore;
|
||||
$this->pool = $pool;
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display Page
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function displayPage(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'task-page';
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grid
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function grid(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
$tasks = $this->taskFactory->query(
|
||||
$this->gridRenderSort($sanitizedParams),
|
||||
$this->gridRenderFilter([], $sanitizedParams)
|
||||
);
|
||||
|
||||
foreach ($tasks as $task) {
|
||||
/** @var \Xibo\Entity\Task $task */
|
||||
|
||||
$task->setUnmatchedProperty('nextRunDt', $task->nextRunDate());
|
||||
|
||||
if ($this->isApi($request)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$task->includeProperty('buttons');
|
||||
|
||||
$task->buttons[] = array(
|
||||
'id' => 'task_button_run.now',
|
||||
'url' => $this->urlFor($request, 'task.runNow.form', ['id' => $task->taskId]),
|
||||
'text' => __('Run Now'),
|
||||
'dataAttributes' => [
|
||||
['name' => 'auto-submit', 'value' => true],
|
||||
[
|
||||
'name' => 'commit-url',
|
||||
'value' => $this->urlFor($request, 'task.runNow', ['id' => $task->taskId]),
|
||||
],
|
||||
['name' => 'commit-method', 'value' => 'POST']
|
||||
]
|
||||
);
|
||||
|
||||
// Don't show any edit buttons if the config is locked.
|
||||
if ($this->getConfig()->getSetting('TASK_CONFIG_LOCKED_CHECKB') == 1
|
||||
|| $this->getConfig()->getSetting('TASK_CONFIG_LOCKED_CHECKB') == 'Checked'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Edit Button
|
||||
$task->buttons[] = array(
|
||||
'id' => 'task_button_edit',
|
||||
'url' => $this->urlFor($request, 'task.edit.form', ['id' => $task->taskId]),
|
||||
'text' => __('Edit')
|
||||
);
|
||||
|
||||
// Delete Button
|
||||
$task->buttons[] = array(
|
||||
'id' => 'task_button_delete',
|
||||
'url' => $this->urlFor($request, 'task.delete.form', ['id' => $task->taskId]),
|
||||
'text' => __('Delete')
|
||||
);
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->taskFactory->countLast();
|
||||
$this->getState()->setData($tasks);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function addForm(Request $request, Response $response)
|
||||
{
|
||||
// Provide a list of possible task classes by searching for .task file in /tasks and /custom
|
||||
$data = ['tasksAvailable' => []];
|
||||
|
||||
// Do we have any modules to install?!
|
||||
if ($this->getConfig()->getSetting('TASK_CONFIG_LOCKED_CHECKB') != 1 && $this->getConfig()->getSetting('TASK_CONFIG_LOCKED_CHECKB') != 'Checked') {
|
||||
// Get a list of matching files in the modules folder
|
||||
$files = array_merge(glob(PROJECT_ROOT . '/tasks/*.task'), glob(PROJECT_ROOT . '/custom/*.task'));
|
||||
|
||||
// Add to the list of available tasks
|
||||
foreach ($files as $file) {
|
||||
$config = json_decode(file_get_contents($file));
|
||||
$config->file = Str::replaceFirst(PROJECT_ROOT, '', $file);
|
||||
|
||||
$data['tasksAvailable'][] = $config;
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'task-form-add';
|
||||
$this->getState()->setData($data);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function add(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$task = $this->taskFactory->create();
|
||||
$task->name = $sanitizedParams->getString('name');
|
||||
$task->configFile = $sanitizedParams->getString('file');
|
||||
$task->schedule = $sanitizedParams->getString('schedule');
|
||||
$task->status = \Xibo\Entity\Task::$STATUS_IDLE;
|
||||
$task->lastRunStatus = 0;
|
||||
$task->isActive = 0;
|
||||
$task->runNow = 0;
|
||||
$task->setClassAndOptions();
|
||||
$task->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => sprintf(__('Added %s'), $task->name),
|
||||
'id' => $task->taskId,
|
||||
'data' => $task
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function editForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$task = $this->taskFactory->getById($id);
|
||||
$task->setClassAndOptions();
|
||||
|
||||
$this->getState()->template = 'task-form-edit';
|
||||
$this->getState()->setData([
|
||||
'task' => $task
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function edit(Request $request, Response $response, $id)
|
||||
{
|
||||
$task = $this->taskFactory->getById($id);
|
||||
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$task->setClassAndOptions();
|
||||
$task->name = $sanitizedParams->getString('name');
|
||||
$task->schedule = $sanitizedParams->getString('schedule');
|
||||
$task->isActive = $sanitizedParams->getCheckbox('isActive');
|
||||
|
||||
// Loop through each option and see if a new value is provided
|
||||
foreach ($task->options as $option => $value) {
|
||||
$provided = $sanitizedParams->getString($option);
|
||||
|
||||
if ($provided !== null) {
|
||||
$this->getLog()->debug('Setting ' . $option . ' to ' . $provided);
|
||||
$task->options[$option] = $provided;
|
||||
}
|
||||
}
|
||||
|
||||
$this->getLog()->debug('New options = ' . var_export($task->options, true));
|
||||
|
||||
$task->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 200,
|
||||
'message' => sprintf(__('Edited %s'), $task->name),
|
||||
'id' => $task->taskId,
|
||||
'data' => $task
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function deleteForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$task = $this->taskFactory->getById($id);
|
||||
|
||||
$this->getState()->template = 'task-form-delete';
|
||||
$this->getState()->setData([
|
||||
'task' => $task
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function delete(Request $request, Response $response, $id)
|
||||
{
|
||||
$task = $this->taskFactory->getById($id);
|
||||
$task->delete();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Deleted %s'), $task->name)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function runNowForm(Request $request, Response $response, $id)
|
||||
{
|
||||
$task = $this->taskFactory->getById($id);
|
||||
|
||||
$this->getState()->template = 'task-form-run-now';
|
||||
$this->getState()->autoSubmit = $this->getAutoSubmit('taskRunNowForm');
|
||||
$this->getState()->setData([
|
||||
'task' => $task
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function runNow(Request $request, Response $response, $id)
|
||||
{
|
||||
$task = $this->taskFactory->getById($id);
|
||||
$task->runNow = 1;
|
||||
$task->save();
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 204,
|
||||
'message' => sprintf(__('Run Now set on %s'), $task->name)
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function run(Request $request, Response $response, $id)
|
||||
{
|
||||
// Get this task
|
||||
if (is_numeric($id)) {
|
||||
$task = $this->taskFactory->getById($id);
|
||||
} else {
|
||||
$task = $this->taskFactory->getByName($id);
|
||||
}
|
||||
|
||||
// Set to running
|
||||
$this->getLog()->debug('run: Running Task ' . $task->name
|
||||
. ' [' . $task->taskId . '], Class = ' . $task->class);
|
||||
|
||||
// Run
|
||||
$task->setStarted();
|
||||
|
||||
try {
|
||||
// Instantiate
|
||||
if (!class_exists($task->class)) {
|
||||
throw new NotFoundException(sprintf(__('Task with class name %s not found'), $task->class));
|
||||
}
|
||||
|
||||
/** @var TaskInterface $taskClass */
|
||||
$taskClass = new $task->class();
|
||||
|
||||
// Record the start time
|
||||
$start = Carbon::now()->format('U');
|
||||
|
||||
$taskClass
|
||||
->setSanitizer($this->getSanitizer($request->getParams()))
|
||||
->setUser($this->getUser())
|
||||
->setConfig($this->getConfig())
|
||||
->setLogger($this->getLog())
|
||||
->setPool($this->pool)
|
||||
->setStore($this->store)
|
||||
->setTimeSeriesStore($this->timeSeriesStore)
|
||||
->setDispatcher($this->getDispatcher())
|
||||
->setFactories($this->container)
|
||||
->setTask($task)
|
||||
->run();
|
||||
|
||||
// We should commit anything this task has done
|
||||
$this->store->commitIfNecessary();
|
||||
|
||||
// Collect results
|
||||
$task->lastRunDuration = Carbon::now()->format('U') - $start;
|
||||
$task->lastRunMessage = $taskClass->getRunMessage();
|
||||
$task->lastRunStatus = \Xibo\Entity\Task::$STATUS_SUCCESS;
|
||||
$task->lastRunExitCode = 0;
|
||||
} catch (\Exception $e) {
|
||||
$this->getLog()->error('run: ' . $e->getMessage() . ' Exception Type: ' . get_class($e));
|
||||
$this->getLog()->debug($e->getTraceAsString());
|
||||
|
||||
// We should roll back anything we've done so far
|
||||
if ($this->store->getConnection()->inTransaction()) {
|
||||
$this->store->getConnection()->rollBack();
|
||||
}
|
||||
|
||||
// Set the results to error
|
||||
$task->lastRunMessage = $e->getMessage();
|
||||
$task->lastRunStatus = \Xibo\Entity\Task::$STATUS_ERROR;
|
||||
$task->lastRunExitCode = 1;
|
||||
}
|
||||
|
||||
$task->lastRunDt = Carbon::now()->format('U');
|
||||
$task->runNow = 0;
|
||||
$task->status = \Xibo\Entity\Task::$STATUS_IDLE;
|
||||
|
||||
// lastRunMessage columns has a limit of 254 characters, if the message is longer, we need to truncate it.
|
||||
if (strlen($task->lastRunMessage) >= 255) {
|
||||
$task->lastRunMessage = substr($task->lastRunMessage, 0, 249) . '(...)';
|
||||
}
|
||||
|
||||
// Finished
|
||||
$task->setFinished();
|
||||
|
||||
$this->getLog()->debug('run: Finished Task ' . $task->name . ' [' . $task->taskId . '] Run Dt: '
|
||||
. Carbon::now()->format(DateFormatHelper::getSystemFormat()));
|
||||
|
||||
$this->setNoOutput();
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll for tasks to run
|
||||
* continue polling until there aren't anymore to run
|
||||
* allow for multiple polls to run at the same time
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function poll(Request $request, Response $response)
|
||||
{
|
||||
$this->getLog()->debug('poll: XTR poll started');
|
||||
|
||||
// Process timeouts
|
||||
$this->pollProcessTimeouts();
|
||||
|
||||
// Keep track of tasks we've run during this poll period
|
||||
// we will use this as a catch-all so that we do not run a task more than once.
|
||||
$tasksRun = [];
|
||||
|
||||
// We loop until we have gone through without running a task
|
||||
// each loop we are expecting to run ONE task only, to allow for multiple runs of XTR at the
|
||||
// same time.
|
||||
while (true) {
|
||||
// Get tasks that aren't running currently
|
||||
// we have to get them all here because we can't calculate the CRON schedule with SQL,
|
||||
// therefore we return them all and process one and a time.
|
||||
$tasks = $this->store->select('
|
||||
SELECT taskId, `schedule`, runNow, lastRunDt
|
||||
FROM `task`
|
||||
WHERE isActive = 1
|
||||
AND `status` <> :status
|
||||
ORDER BY lastRunDuration
|
||||
', ['status' => \Xibo\Entity\Task::$STATUS_RUNNING], 'xtr', true);
|
||||
|
||||
// Assume we won't run anything
|
||||
$taskRun = false;
|
||||
|
||||
foreach ($tasks as $task) {
|
||||
/** @var \Xibo\Entity\Task $task */
|
||||
$taskId = $task['taskId'];
|
||||
|
||||
// Skip tasks that have already been run
|
||||
if (in_array($taskId, $tasksRun)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$cron = new CronExpression($task['schedule']);
|
||||
} catch (\Exception $e) {
|
||||
$this->getLog()->info('run: CRON syntax error for taskId ' . $taskId
|
||||
. ', e: ' . $e->getMessage());
|
||||
|
||||
// Try and take the first X characters instead.
|
||||
try {
|
||||
$cron = new CronExpression(substr($task['schedule'], 0, strlen($task['schedule']) - 2));
|
||||
} catch (\Exception) {
|
||||
$this->getLog()->error('run: cannot fix CRON syntax error ' . $taskId);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Is the next run date of this event earlier than now, or is the task set to runNow
|
||||
$nextRunDt = $cron->getNextRunDate(\DateTime::createFromFormat('U', $task['lastRunDt']))
|
||||
->format('U');
|
||||
|
||||
if ($task['runNow'] == 1 || $nextRunDt <= Carbon::now()->format('U')) {
|
||||
$this->getLog()->info('poll: Running Task ' . $taskId);
|
||||
|
||||
try {
|
||||
// Pass to run.
|
||||
$this->run($request, $response, $taskId);
|
||||
} catch (\Exception $exception) {
|
||||
// The only thing which can fail inside run is core code,
|
||||
// so it is reasonable here to disable the task.
|
||||
$this->getLog()->error('poll: Task run error for taskId ' . $taskId
|
||||
. '. E = ' . $exception->getMessage());
|
||||
$this->getLog()->debug($exception->getTraceAsString());
|
||||
|
||||
// Set to error and disable.
|
||||
$this->store->update('
|
||||
UPDATE `task` SET status = :status, isActive = :isActive, lastRunMessage = :lastRunMessage
|
||||
WHERE taskId = :taskId
|
||||
', [
|
||||
'taskId' => $taskId,
|
||||
'status' => \Xibo\Entity\Task::$STATUS_ERROR,
|
||||
'isActive' => 0,
|
||||
'lastRunMessage' => 'Fatal Error: ' . $exception->getMessage()
|
||||
], 'xtr', true, false);
|
||||
}
|
||||
|
||||
// We have run a task
|
||||
$taskRun = true;
|
||||
|
||||
// We've run this task during this polling period
|
||||
$tasksRun[] = $taskId;
|
||||
|
||||
// As mentioned above, we only run 1 task at a time to allow for concurrent runs of XTR.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we haven't run a task, then stop
|
||||
if (!$taskRun) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->getLog()->debug('XTR poll stopped');
|
||||
$this->setNoOutput();
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
private function pollProcessTimeouts()
|
||||
{
|
||||
$count = $this->store->update('
|
||||
UPDATE `task` SET `status` = :newStatus
|
||||
WHERE `isActive` = 1
|
||||
AND `status` = :currentStatus
|
||||
AND `lastRunStartDt` < :timeout
|
||||
', [
|
||||
'timeout' => Carbon::now()->subHours(12)->format('U'),
|
||||
'currentStatus' => \Xibo\Entity\Task::$STATUS_RUNNING,
|
||||
'newStatus' => \Xibo\Entity\Task::$STATUS_TIMEOUT,
|
||||
], 'xtr', false, false);
|
||||
|
||||
if ($count > 0) {
|
||||
$this->getLog()->error($count . ' timed out tasks.');
|
||||
} else {
|
||||
$this->getLog()->debug('No timed out tasks.');
|
||||
}
|
||||
}
|
||||
}
|
||||
806
lib/Controller/Template.php
Normal file
806
lib/Controller/Template.php
Normal file
@@ -0,0 +1,806 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2025 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Parsedown;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Entity\SearchResult;
|
||||
use Xibo\Entity\SearchResults;
|
||||
use Xibo\Event\TemplateProviderEvent;
|
||||
use Xibo\Event\TemplateProviderListEvent;
|
||||
use Xibo\Factory\LayoutFactory;
|
||||
use Xibo\Factory\TagFactory;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\GeneralException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
use Xibo\Support\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class Template
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class Template extends Base
|
||||
{
|
||||
/**
|
||||
* @var LayoutFactory
|
||||
*/
|
||||
private $layoutFactory;
|
||||
|
||||
/**
|
||||
* @var TagFactory
|
||||
*/
|
||||
private $tagFactory;
|
||||
|
||||
/**
|
||||
* @var \Xibo\Factory\ResolutionFactory
|
||||
*/
|
||||
private $resolutionFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param LayoutFactory $layoutFactory
|
||||
* @param TagFactory $tagFactory
|
||||
* @param \Xibo\Factory\ResolutionFactory $resolutionFactory
|
||||
*/
|
||||
public function __construct($layoutFactory, $tagFactory, $resolutionFactory)
|
||||
{
|
||||
$this->layoutFactory = $layoutFactory;
|
||||
$this->tagFactory = $tagFactory;
|
||||
$this->resolutionFactory = $resolutionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display page logic
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
function displayPage(Request $request, Response $response)
|
||||
{
|
||||
// Call to render the template
|
||||
$this->getState()->template = 'template-page';
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data grid
|
||||
*
|
||||
* @SWG\Get(
|
||||
* path="/template",
|
||||
* operationId="templateSearch",
|
||||
* tags={"template"},
|
||||
* summary="Template Search",
|
||||
* description="Search templates this user has access to",
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/Layout")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function grid(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedQueryParams = $this->getSanitizer($request->getQueryParams());
|
||||
// Embed?
|
||||
$embed = ($sanitizedQueryParams->getString('embed') != null)
|
||||
? explode(',', $sanitizedQueryParams->getString('embed'))
|
||||
: [];
|
||||
|
||||
$templates = $this->layoutFactory->query($this->gridRenderSort($sanitizedQueryParams), $this->gridRenderFilter([
|
||||
'excludeTemplates' => 0,
|
||||
'tags' => $sanitizedQueryParams->getString('tags'),
|
||||
'layoutId' => $sanitizedQueryParams->getInt('templateId'),
|
||||
'layout' => $sanitizedQueryParams->getString('template'),
|
||||
'useRegexForName' => $sanitizedQueryParams->getCheckbox('useRegexForName'),
|
||||
'folderId' => $sanitizedQueryParams->getInt('folderId'),
|
||||
'logicalOperator' => $sanitizedQueryParams->getString('logicalOperator'),
|
||||
'logicalOperatorName' => $sanitizedQueryParams->getString('logicalOperatorName'),
|
||||
], $sanitizedQueryParams));
|
||||
|
||||
foreach ($templates as $template) {
|
||||
/* @var \Xibo\Entity\Layout $template */
|
||||
|
||||
if (in_array('regions', $embed)) {
|
||||
$template->load([
|
||||
'loadPlaylists' => in_array('playlists', $embed),
|
||||
'loadCampaigns' => in_array('campaigns', $embed),
|
||||
'loadPermissions' => in_array('permissions', $embed),
|
||||
'loadTags' => in_array('tags', $embed),
|
||||
'loadWidgets' => in_array('widgets', $embed)
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->isApi($request)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$template->includeProperty('buttons');
|
||||
|
||||
// Thumbnail
|
||||
$template->setUnmatchedProperty('thumbnail', '');
|
||||
if (file_exists($template->getThumbnailUri())) {
|
||||
$template->setUnmatchedProperty(
|
||||
'thumbnail',
|
||||
$this->urlFor($request, 'layout.download.thumbnail', ['id' => $template->layoutId])
|
||||
);
|
||||
}
|
||||
|
||||
// Parse down for description
|
||||
$template->setUnmatchedProperty(
|
||||
'descriptionWithMarkup',
|
||||
Parsedown::instance()->setSafeMode(true)->text($template->description),
|
||||
);
|
||||
|
||||
if ($this->getUser()->featureEnabled('template.modify')
|
||||
&& $this->getUser()->checkEditable($template)
|
||||
) {
|
||||
// Design Button
|
||||
$template->buttons[] = [
|
||||
'id' => 'layout_button_design',
|
||||
'linkType' => '_self', 'external' => true,
|
||||
'url' => $this->urlFor(
|
||||
$request,
|
||||
'layout.designer',
|
||||
['id' => $template->layoutId]
|
||||
) . '?isTemplateEditor=1',
|
||||
'text' => __('Alter Template')
|
||||
];
|
||||
|
||||
if ($template->isEditable()) {
|
||||
$template->buttons[] = ['divider' => true];
|
||||
|
||||
$template->buttons[] = array(
|
||||
'id' => 'layout_button_publish',
|
||||
'url' => $this->urlFor($request, 'layout.publish.form', ['id' => $template->layoutId]),
|
||||
'text' => __('Publish')
|
||||
);
|
||||
|
||||
$template->buttons[] = array(
|
||||
'id' => 'layout_button_discard',
|
||||
'url' => $this->urlFor($request, 'layout.discard.form', ['id' => $template->layoutId]),
|
||||
'text' => __('Discard')
|
||||
);
|
||||
|
||||
$template->buttons[] = ['divider' => true];
|
||||
} else {
|
||||
$template->buttons[] = ['divider' => true];
|
||||
|
||||
// Checkout Button
|
||||
$template->buttons[] = array(
|
||||
'id' => 'layout_button_checkout',
|
||||
'url' => $this->urlFor($request, 'layout.checkout.form', ['id' => $template->layoutId]),
|
||||
'text' => __('Checkout'),
|
||||
'dataAttributes' => [
|
||||
['name' => 'auto-submit', 'value' => true],
|
||||
[
|
||||
'name' => 'commit-url',
|
||||
'value' => $this->urlFor(
|
||||
$request,
|
||||
'layout.checkout',
|
||||
['id' => $template->layoutId]
|
||||
)
|
||||
],
|
||||
['name' => 'commit-method', 'value' => 'PUT']
|
||||
]
|
||||
);
|
||||
|
||||
$template->buttons[] = ['divider' => true];
|
||||
}
|
||||
|
||||
// Edit Button
|
||||
$template->buttons[] = array(
|
||||
'id' => 'layout_button_edit',
|
||||
'url' => $this->urlFor($request, 'template.edit.form', ['id' => $template->layoutId]),
|
||||
'text' => __('Edit')
|
||||
);
|
||||
|
||||
// Select Folder
|
||||
if ($this->getUser()->featureEnabled('folder.view')) {
|
||||
$template->buttons[] = [
|
||||
'id' => 'campaign_button_selectfolder',
|
||||
'url' => $this->urlFor($request, 'campaign.selectfolder.form', ['id' => $template->campaignId]),
|
||||
'text' => __('Select Folder'),
|
||||
'multi-select' => true,
|
||||
'dataAttributes' => [
|
||||
[
|
||||
'name' => 'commit-url',
|
||||
'value' => $this->urlFor(
|
||||
$request,
|
||||
'campaign.selectfolder',
|
||||
['id' => $template->campaignId]
|
||||
)
|
||||
],
|
||||
['name' => 'commit-method', 'value' => 'put'],
|
||||
['name' => 'id', 'value' => 'campaign_button_selectfolder'],
|
||||
['name' => 'text', 'value' => __('Move to Folder')],
|
||||
['name' => 'rowtitle', 'value' => $template->layout],
|
||||
['name' => 'form-callback', 'value' => 'moveFolderMultiSelectFormOpen']
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
// Copy Button
|
||||
$template->buttons[] = array(
|
||||
'id' => 'layout_button_copy',
|
||||
'url' => $this->urlFor($request, 'layout.copy.form', ['id' => $template->layoutId]),
|
||||
'text' => __('Copy')
|
||||
);
|
||||
}
|
||||
|
||||
// Extra buttons if have delete permissions
|
||||
if ($this->getUser()->featureEnabled('template.modify')
|
||||
&& $this->getUser()->checkDeleteable($template)) {
|
||||
// Delete Button
|
||||
$template->buttons[] = [
|
||||
'id' => 'layout_button_delete',
|
||||
'url' => $this->urlFor($request, 'layout.delete.form', ['id' => $template->layoutId]),
|
||||
'text' => __('Delete'),
|
||||
'multi-select' => true,
|
||||
'dataAttributes' => [
|
||||
[
|
||||
'name' => 'commit-url',
|
||||
'value' => $this->urlFor(
|
||||
$request,
|
||||
'layout.delete',
|
||||
['id' => $template->layoutId]
|
||||
)
|
||||
],
|
||||
['name' => 'commit-method', 'value' => 'delete'],
|
||||
['name' => 'id', 'value' => 'layout_button_delete'],
|
||||
['name' => 'text', 'value' => __('Delete')],
|
||||
['name' => 'sort-group', 'value' => 1],
|
||||
['name' => 'rowtitle', 'value' => $template->layout]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
$template->buttons[] = ['divider' => true];
|
||||
|
||||
// Extra buttons if we have modify permissions
|
||||
if ($this->getUser()->featureEnabled('template.modify')
|
||||
&& $this->getUser()->checkPermissionsModifyable($template)) {
|
||||
// Permissions button
|
||||
$template->buttons[] = [
|
||||
'id' => 'layout_button_permissions',
|
||||
'url' => $this->urlFor(
|
||||
$request,
|
||||
'user.permissions.form',
|
||||
['entity' => 'Campaign', 'id' => $template->campaignId]
|
||||
) . '?nameOverride=' . __('Template'),
|
||||
'text' => __('Share'),
|
||||
'multi-select' => true,
|
||||
'dataAttributes' => [
|
||||
[
|
||||
'name' => 'commit-url',
|
||||
'value' => $this->urlFor(
|
||||
$request,
|
||||
'user.permissions.multi',
|
||||
['entity' => 'Campaign', 'id' => $template->campaignId]
|
||||
)
|
||||
],
|
||||
['name' => 'commit-method', 'value' => 'post'],
|
||||
['name' => 'id', 'value' => 'layout_button_permissions'],
|
||||
['name' => 'text', 'value' => __('Share')],
|
||||
['name' => 'rowtitle', 'value' => $template->layout],
|
||||
['name' => 'sort-group', 'value' => 2],
|
||||
['name' => 'custom-handler', 'value' => 'XiboMultiSelectPermissionsFormOpen'],
|
||||
[
|
||||
'name' => 'custom-handler-url',
|
||||
'value' => $this->urlFor(
|
||||
$request,
|
||||
'user.permissions.multi.form',
|
||||
['entity' => 'Campaign']
|
||||
)
|
||||
],
|
||||
['name' => 'content-id-name', 'value' => 'campaignId']
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->getUser()->featureEnabled('layout.export')) {
|
||||
$template->buttons[] = ['divider' => true];
|
||||
|
||||
// Export Button
|
||||
$template->buttons[] = array(
|
||||
'id' => 'layout_button_export',
|
||||
'linkType' => '_self',
|
||||
'external' => true,
|
||||
'url' => $this->urlFor($request, 'layout.export', ['id' => $template->layoutId]),
|
||||
'text' => __('Export')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->layoutFactory->countLast();
|
||||
$this->getState()->setData($templates);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data grid
|
||||
*
|
||||
* @SWG\Get(
|
||||
* path="/template/search",
|
||||
* operationId="templateSearchAll",
|
||||
* tags={"template"},
|
||||
* summary="Template Search All",
|
||||
* description="Search all templates from local and connectors",
|
||||
* @SWG\Response(
|
||||
* response=200,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/SearchResult")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function search(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedQueryParams = $this->getSanitizer($request->getQueryParams());
|
||||
$provider = $sanitizedQueryParams->getString('provider', ['default' => 'both']);
|
||||
|
||||
$searchResults = new SearchResults();
|
||||
if ($provider === 'both' || $provider === 'local') {
|
||||
$templates = $this->layoutFactory->query(['layout'], $this->gridRenderFilter([
|
||||
'excludeTemplates' => 0,
|
||||
'layout' => $sanitizedQueryParams->getString('template'),
|
||||
'folderId' => $sanitizedQueryParams->getInt('folderId'),
|
||||
'orientation' => $sanitizedQueryParams->getString('orientation', ['defaultOnEmptyString' => true]),
|
||||
'publishedStatusId' => 1
|
||||
], $sanitizedQueryParams));
|
||||
|
||||
foreach ($templates as $template) {
|
||||
$searchResult = new SearchResult();
|
||||
$searchResult->id = $template->layoutId;
|
||||
$searchResult->source = 'local';
|
||||
$searchResult->title = $template->layout;
|
||||
|
||||
// Handle the description
|
||||
$searchResult->description = '';
|
||||
if (!empty($template->description)) {
|
||||
$searchResult->description = Parsedown::instance()->setSafeMode(true)->line($template->description);
|
||||
}
|
||||
$searchResult->orientation = $template->orientation;
|
||||
$searchResult->width = $template->width;
|
||||
$searchResult->height = $template->height;
|
||||
|
||||
if (!empty($template->tags)) {
|
||||
foreach ($template->getTags() as $tag) {
|
||||
if ($tag->tag === 'template') {
|
||||
continue;
|
||||
}
|
||||
$searchResult->tags[] = $tag->tag;
|
||||
}
|
||||
}
|
||||
|
||||
// Thumbnail
|
||||
$searchResult->thumbnail = '';
|
||||
if (file_exists($template->getThumbnailUri())) {
|
||||
$searchResult->thumbnail = $this->urlFor(
|
||||
$request,
|
||||
'layout.download.thumbnail',
|
||||
['id' => $template->layoutId]
|
||||
);
|
||||
}
|
||||
|
||||
$searchResults->data[] = $searchResult;
|
||||
}
|
||||
}
|
||||
|
||||
if ($provider === 'both' || $provider === 'remote') {
|
||||
// Hand off to any other providers that may want to provide results.
|
||||
$event = new TemplateProviderEvent(
|
||||
$searchResults,
|
||||
$sanitizedQueryParams->getInt('start', ['default' => 0]),
|
||||
$sanitizedQueryParams->getInt('length', ['default' => 15]),
|
||||
$sanitizedQueryParams->getString('template'),
|
||||
$sanitizedQueryParams->getString('orientation'),
|
||||
);
|
||||
|
||||
$this->getLog()->debug('Dispatching event. ' . $event->getName());
|
||||
try {
|
||||
$this->getDispatcher()->dispatch($event, $event->getName());
|
||||
} catch (\Exception $exception) {
|
||||
$this->getLog()->error('Template search: Exception in dispatched event: ' . $exception->getMessage());
|
||||
$this->getLog()->debug($exception->getTraceAsString());
|
||||
}
|
||||
}
|
||||
return $response->withJson($searchResults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Template Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
function addTemplateForm(Request $request, Response $response, $id)
|
||||
{
|
||||
// Get the layout
|
||||
$layout = $this->layoutFactory->getById($id);
|
||||
|
||||
// Check Permissions
|
||||
if (!$this->getUser()->checkViewable($layout)) {
|
||||
throw new AccessDeniedException(__('You do not have permissions to view this layout'));
|
||||
}
|
||||
|
||||
$this->getState()->template = 'template-form-add-from-layout';
|
||||
$this->getState()->setData([
|
||||
'layout' => $layout,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
/**
|
||||
* Add a Template
|
||||
* @SWG\Post(
|
||||
* path="/template",
|
||||
* operationId="templateAdd",
|
||||
* tags={"template"},
|
||||
* summary="Add a Template",
|
||||
* description="Add a new Template to the CMS",
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="formData",
|
||||
* description="The layout name",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="description",
|
||||
* in="formData",
|
||||
* description="The layout description",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="resolutionId",
|
||||
* in="formData",
|
||||
* description="If a Template is not provided, provide the resolutionId for this Layout.",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="returnDraft",
|
||||
* in="formData",
|
||||
* description="Should we return the Draft Layout or the Published Layout on Success?",
|
||||
* type="boolean",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/Layout"),
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new record",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
function add(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$name = $sanitizedParams->getString('name');
|
||||
$description = $sanitizedParams->getString('description');
|
||||
$resolutionId = $sanitizedParams->getInt('resolutionId');
|
||||
$enableStat = $sanitizedParams->getCheckbox('enableStat');
|
||||
$autoApplyTransitions = $sanitizedParams->getCheckbox('autoApplyTransitions');
|
||||
$folderId = $sanitizedParams->getInt('folderId');
|
||||
|
||||
if ($folderId === 1) {
|
||||
$this->checkRootFolderAllowSave();
|
||||
}
|
||||
|
||||
if (empty($folderId) || !$this->getUser()->featureEnabled('folder.view')) {
|
||||
$folderId = $this->getUser()->homeFolderId;
|
||||
}
|
||||
|
||||
// Tags
|
||||
if ($this->getUser()->featureEnabled('tag.tagging')) {
|
||||
$tags = $this->tagFactory->tagsFromString($sanitizedParams->getString('tags'));
|
||||
} else {
|
||||
$tags = [];
|
||||
}
|
||||
$tags[] = $this->tagFactory->tagFromString('template');
|
||||
|
||||
$layout = $this->layoutFactory->createFromResolution($resolutionId,
|
||||
$this->getUser()->userId,
|
||||
$name,
|
||||
$description,
|
||||
$tags,
|
||||
null
|
||||
);
|
||||
|
||||
// Set layout enableStat flag
|
||||
$layout->enableStat = $enableStat;
|
||||
|
||||
// Set auto apply transitions flag
|
||||
$layout->autoApplyTransitions = $autoApplyTransitions;
|
||||
|
||||
// Set folderId
|
||||
$layout->folderId = $folderId;
|
||||
|
||||
// Save
|
||||
$layout->save();
|
||||
|
||||
// Automatically checkout the new layout for edit
|
||||
$layout = $this->layoutFactory->checkoutLayout($layout, $sanitizedParams->getCheckbox('returnDraft'));
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => sprintf(__('Added %s'), $layout->layout),
|
||||
'id' => $layout->layoutId,
|
||||
'data' => $layout
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add template
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
* @SWG\Post(
|
||||
* path="/template/{layoutId}",
|
||||
* operationId="template.add.from.layout",
|
||||
* tags={"template"},
|
||||
* summary="Add a template from a Layout",
|
||||
* description="Use the provided layout as a base for a new template",
|
||||
* @SWG\Parameter(
|
||||
* name="layoutId",
|
||||
* in="path",
|
||||
* description="The Layout ID",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="includeWidgets",
|
||||
* in="formData",
|
||||
* description="Flag indicating whether to include the widgets in the Template",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="name",
|
||||
* in="formData",
|
||||
* description="The Template Name",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="tags",
|
||||
* in="formData",
|
||||
* description="Comma separated list of Tags for the template",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="description",
|
||||
* in="formData",
|
||||
* description="A description of the Template",
|
||||
* type="string",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(ref="#/definitions/Layout"),
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new record",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function addFromLayout(Request $request, Response $response, $id): Response
|
||||
{
|
||||
// Get the layout
|
||||
$layout = $this->layoutFactory->getById($id);
|
||||
|
||||
// Check Permissions
|
||||
if (!$this->getUser()->checkViewable($layout)) {
|
||||
throw new AccessDeniedException(__('You do not have permissions to view this layout'));
|
||||
}
|
||||
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
// Should the copy include the widgets
|
||||
$includeWidgets = ($sanitizedParams->getCheckbox('includeWidgets') == 1);
|
||||
|
||||
// Load without anything
|
||||
$layout->load([
|
||||
'loadPlaylists' => true,
|
||||
'loadWidgets' => $includeWidgets,
|
||||
'playlistIncludeRegionAssignments' => false,
|
||||
'loadTags' => false,
|
||||
'loadPermissions' => false,
|
||||
'loadCampaigns' => false
|
||||
]);
|
||||
$originalLayout = $layout;
|
||||
|
||||
$layout = clone $layout;
|
||||
|
||||
$layout->layout = $sanitizedParams->getString('name');
|
||||
if ($this->getUser()->featureEnabled('tag.tagging')) {
|
||||
$layout->updateTagLinks($this->tagFactory->tagsFromString($sanitizedParams->getString('tags')));
|
||||
} else {
|
||||
$layout->tags = [];
|
||||
}
|
||||
$layout->assignTag($this->tagFactory->tagFromString('template'));
|
||||
|
||||
$layout->description = $sanitizedParams->getString('description');
|
||||
$layout->folderId = $sanitizedParams->getInt('folderId');
|
||||
|
||||
if ($layout->folderId === 1) {
|
||||
$this->checkRootFolderAllowSave();
|
||||
}
|
||||
|
||||
// When saving a layout as a template, we should not include the empty canva region as that requires
|
||||
// a widget to be inside it.
|
||||
// https://github.com/xibosignage/xibo/issues/3574
|
||||
if (!$includeWidgets) {
|
||||
$this->getLog()->debug('addFromLayout: widgets have not been included, checking for empty regions');
|
||||
|
||||
$regionsWithWidgets = [];
|
||||
foreach ($layout->regions as $region) {
|
||||
if ($region->type === 'canvas') {
|
||||
$this->getLog()->debug('addFromLayout: Canvas region excluded from export');
|
||||
} else {
|
||||
$regionsWithWidgets[] = $region;
|
||||
}
|
||||
}
|
||||
$layout->regions = $regionsWithWidgets;
|
||||
}
|
||||
|
||||
$layout->setOwner($this->getUser()->userId, true);
|
||||
$layout->save();
|
||||
|
||||
if ($includeWidgets) {
|
||||
// Sub-Playlist
|
||||
foreach ($layout->regions as $region) {
|
||||
// Match our original region id to the id in the parent layout
|
||||
$original = $originalLayout->getRegion($region->getOriginalValue('regionId'));
|
||||
|
||||
// Make sure Playlist closure table from the published one are copied over
|
||||
$original->getPlaylist()->cloneClosureTable($region->getPlaylist()->playlistId);
|
||||
}
|
||||
}
|
||||
|
||||
// Return
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => sprintf(__('Saved %s'), $layout->layout),
|
||||
'id' => $layout->layoutId,
|
||||
'data' => $layout
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an Add/Edit form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws GeneralException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
function addForm(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'template-form-add';
|
||||
$this->getState()->setData([
|
||||
'resolutions' => $this->resolutionFactory->query(['resolution']),
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws GeneralException
|
||||
* @throws NotFoundException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
*/
|
||||
public function editForm(Request $request, Response $response, $id)
|
||||
{
|
||||
// Get the layout
|
||||
$template = $this->layoutFactory->getById($id);
|
||||
|
||||
// Check Permissions
|
||||
if (!$this->getUser()->checkEditable($template)) {
|
||||
throw new AccessDeniedException();
|
||||
}
|
||||
|
||||
$this->getState()->template = 'template-form-edit';
|
||||
$this->getState()->setData([
|
||||
'layout' => $template,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of Template providers with their details.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return Response|ResponseInterface
|
||||
*/
|
||||
public function providersList(Request $request, Response $response): Response|\Psr\Http\Message\ResponseInterface
|
||||
{
|
||||
$event = new TemplateProviderListEvent();
|
||||
$this->getDispatcher()->dispatch($event, $event->getName());
|
||||
|
||||
$providers = $event->getProviders();
|
||||
|
||||
return $response->withJson($providers);
|
||||
}
|
||||
}
|
||||
165
lib/Controller/Transition.php
Normal file
165
lib/Controller/Transition.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2023 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\TransitionFactory;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
|
||||
/**
|
||||
* Class Transition
|
||||
* @package Xibo\Controller
|
||||
*/
|
||||
class Transition extends Base
|
||||
{
|
||||
/**
|
||||
* @var TransitionFactory
|
||||
*/
|
||||
private $transitionFactory;
|
||||
|
||||
/**
|
||||
* Set common dependencies.
|
||||
* @param TransitionFactory $transitionFactory
|
||||
*/
|
||||
public function __construct($transitionFactory)
|
||||
{
|
||||
$this->transitionFactory = $transitionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* No display page functionaility
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
function displayPage(Request $request, Response $response)
|
||||
{
|
||||
$this->getState()->template = 'transition-page';
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
public function grid(Request $request, Response $response)
|
||||
{
|
||||
$sanitizedQueryParams = $this->getSanitizer($request->getQueryParams());
|
||||
|
||||
$filter = [
|
||||
'transition' => $sanitizedQueryParams->getString('transition'),
|
||||
'code' => $sanitizedQueryParams->getString('code'),
|
||||
'availableAsIn' => $sanitizedQueryParams->getInt('availableAsIn'),
|
||||
'availableAsOut' => $sanitizedQueryParams->getInt('availableAsOut')
|
||||
];
|
||||
|
||||
$transitions = $this->transitionFactory->query($this->gridRenderSort($sanitizedQueryParams), $this->gridRenderFilter($filter, $sanitizedQueryParams));
|
||||
|
||||
foreach ($transitions as $transition) {
|
||||
/* @var \Xibo\Entity\Transition $transition */
|
||||
|
||||
// If the module config is not locked, present some buttons
|
||||
if ($this->getConfig()->getSetting('TRANSITION_CONFIG_LOCKED_CHECKB') != 1 && $this->getConfig()->getSetting('TRANSITION_CONFIG_LOCKED_CHECKB') != 'Checked' ) {
|
||||
|
||||
// Edit button
|
||||
$transition->buttons[] = array(
|
||||
'id' => 'transition_button_edit',
|
||||
'url' => $this->urlFor($request,'transition.edit.form', ['id' => $transition->transitionId]),
|
||||
'text' => __('Edit')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->getState()->template = 'grid';
|
||||
$this->getState()->recordsTotal = $this->transitionFactory->countLast();
|
||||
$this->getState()->setData($transitions);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition Edit Form
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function editForm(Request $request, Response $response, $id)
|
||||
{
|
||||
if ($this->getConfig()->getSetting('TRANSITION_CONFIG_LOCKED_CHECKB') == 1 || $this->getConfig()->getSetting('TRANSITION_CONFIG_LOCKED_CHECKB') == 'Checked') {
|
||||
throw new AccessDeniedException(__('Transition Config Locked'));
|
||||
}
|
||||
|
||||
$transition = $this->transitionFactory->getById($id);
|
||||
|
||||
$this->getState()->template = 'transition-form-edit';
|
||||
$this->getState()->setData([
|
||||
'transition' => $transition,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Transition
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param $id
|
||||
* @return \Psr\Http\Message\ResponseInterface|Response
|
||||
* @throws AccessDeniedException
|
||||
* @throws \Xibo\Support\Exception\ControllerNotImplemented
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
public function edit(Request $request, Response $response, $id)
|
||||
{
|
||||
if ($this->getConfig()->getSetting('TRANSITION_CONFIG_LOCKED_CHECKB') == 1 || $this->getConfig()->getSetting('TRANSITION_CONFIG_LOCKED_CHECKB') == 'Checked') {
|
||||
throw new AccessDeniedException(__('Transition Config Locked'));
|
||||
}
|
||||
|
||||
$sanitizedParams = $this->getSanitizer($request->getParams());
|
||||
|
||||
$transition = $this->transitionFactory->getById($id);
|
||||
$transition->availableAsIn = $sanitizedParams->getCheckbox('availableAsIn');
|
||||
$transition->availableAsOut = $sanitizedParams->getCheckbox('availableAsOut');
|
||||
$transition->save();
|
||||
|
||||
$this->getState()->hydrate([
|
||||
'message' => sprintf(__('Edited %s'), $transition->transition),
|
||||
'id' => $transition->transitionId,
|
||||
'data' => $transition
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
}
|
||||
2567
lib/Controller/User.php
Normal file
2567
lib/Controller/User.php
Normal file
File diff suppressed because it is too large
Load Diff
1097
lib/Controller/UserGroup.php
Normal file
1097
lib/Controller/UserGroup.php
Normal file
File diff suppressed because it is too large
Load Diff
2033
lib/Controller/Widget.php
Normal file
2033
lib/Controller/Widget.php
Normal file
File diff suppressed because it is too large
Load Diff
414
lib/Controller/WidgetData.php
Normal file
414
lib/Controller/WidgetData.php
Normal file
@@ -0,0 +1,414 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2025 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Xibo\Controller;
|
||||
|
||||
use Slim\Http\Response as Response;
|
||||
use Slim\Http\ServerRequest as Request;
|
||||
use Xibo\Factory\ModuleFactory;
|
||||
use Xibo\Factory\WidgetDataFactory;
|
||||
use Xibo\Factory\WidgetFactory;
|
||||
use Xibo\Support\Exception\AccessDeniedException;
|
||||
use Xibo\Support\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Controller for managing Widget Data
|
||||
*/
|
||||
class WidgetData extends Base
|
||||
{
|
||||
public function __construct(
|
||||
private readonly WidgetDataFactory $widgetDataFactory,
|
||||
private readonly WidgetFactory $widgetFactory,
|
||||
private readonly ModuleFactory $moduleFactory
|
||||
) {
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
/**
|
||||
* @SWG\Get(
|
||||
* path="/playlist/widget/data/{id}",
|
||||
* operationId="getWidgetData",
|
||||
* tags={"widget"},
|
||||
* summary="Get data for Widget",
|
||||
* description="Return all of the fallback data currently assigned to this Widget",
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Widget ID that this data should be added to",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="#/definitions/WidgetData")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
// phpcs:enable
|
||||
public function get(Request $request, Response $response, int $id): Response
|
||||
{
|
||||
$widget = $this->widgetFactory->getById($id);
|
||||
if (!$this->getUser()->checkEditable($widget)) {
|
||||
throw new AccessDeniedException(__('This Widget is not shared with you with edit permission'));
|
||||
}
|
||||
|
||||
return $response->withJson($this->widgetDataFactory->getByWidgetId($widget->widgetId));
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
/**
|
||||
* @SWG\Post(
|
||||
* path="/playlist/widget/data/{id}",
|
||||
* operationId="addWidgetData",
|
||||
* tags={"widget"},
|
||||
* summary="Add a data to a Widget",
|
||||
* description="Add fallback data to a data Widget",
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Widget ID that this data should be added to",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="data",
|
||||
* in="path",
|
||||
* description="A JSON formatted string containing a single data item for this widget's data type",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="displayOrder",
|
||||
* in="formData",
|
||||
* description="Optional integer to say which position this data should appear if there is more than one data item",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=201,
|
||||
* description="successful operation",
|
||||
* @SWG\Header(
|
||||
* header="Location",
|
||||
* description="Location of the new record",
|
||||
* type="string"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
// phpcs:enable
|
||||
public function add(Request $request, Response $response, int $id): Response
|
||||
{
|
||||
// Check that we have permission to edit this widget
|
||||
$widget = $this->widgetFactory->getById($id);
|
||||
if (!$this->getUser()->checkEditable($widget)) {
|
||||
throw new AccessDeniedException(__('This Widget is not shared with you with edit permission'));
|
||||
}
|
||||
|
||||
// Get the other params.
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
$widgetData = $this->widgetDataFactory
|
||||
->create(
|
||||
$widget->widgetId,
|
||||
$this->parseAndValidate($widget, $params->getArray('data')),
|
||||
$params->getInt('displayOrder', ['default' => 1]),
|
||||
)
|
||||
->save();
|
||||
|
||||
// Update the widget modified dt
|
||||
$widget->modifiedDt =
|
||||
|
||||
// Successful
|
||||
$this->getState()->hydrate([
|
||||
'httpStatus' => 201,
|
||||
'message' => __('Added data for Widget'),
|
||||
'id' => $widgetData->id,
|
||||
'data' => $widgetData,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
/**
|
||||
* @SWG\Put(
|
||||
* path="/playlist/widget/data/{id}/{dataId}",
|
||||
* operationId="editWidgetData",
|
||||
* tags={"widget"},
|
||||
* summary="Edit data on a Widget",
|
||||
* description="Edit fallback data on a data Widget",
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Widget ID that this data is attached to",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="dataId",
|
||||
* in="path",
|
||||
* description="The ID of the data to be edited",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="data",
|
||||
* in="path",
|
||||
* description="A JSON formatted string containing a single data item for this widget's data type",
|
||||
* type="string",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="displayOrder",
|
||||
* in="formData",
|
||||
* description="Optional integer to say which position this data should appear if there is more than one data item",
|
||||
* type="integer",
|
||||
* required=false
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
// phpcs:enable
|
||||
public function edit(Request $request, Response $response, int $id, int $dataId): Response
|
||||
{
|
||||
// Check that we have permission to edit this widget
|
||||
$widget = $this->widgetFactory->getById($id);
|
||||
if (!$this->getUser()->checkEditable($widget)) {
|
||||
throw new AccessDeniedException(__('This Widget is not shared with you with edit permission'));
|
||||
}
|
||||
|
||||
// Make sure this dataId is for this widget
|
||||
$widgetData = $this->widgetDataFactory->getById($dataId);
|
||||
|
||||
if ($id !== $widgetData->widgetId) {
|
||||
throw new AccessDeniedException(__('This widget data does not belong to this widget'));
|
||||
}
|
||||
|
||||
// Get params and process the edit
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
$widgetData->data = $this->parseAndValidate($widget, $params->getArray('data'));
|
||||
$widgetData->displayOrder = $params->getInt('displayOrder', ['default' => 1]);
|
||||
$widgetData->save();
|
||||
|
||||
// Successful
|
||||
$this->getState()->hydrate([
|
||||
'message' => __('Edited data for Widget'),
|
||||
'id' => $widgetData->id,
|
||||
'data' => $widgetData,
|
||||
'httpStatus' => 204,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
/**
|
||||
* @SWG\Delete(
|
||||
* path="/playlist/widget/data/{id}/{dataId}",
|
||||
* operationId="deleteWidgetData",
|
||||
* tags={"widget"},
|
||||
* summary="Delete data on a Widget",
|
||||
* description="Delete fallback data on a data Widget",
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Widget ID that this data is attached to",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="dataId",
|
||||
* in="path",
|
||||
* description="The ID of the data to be deleted",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
// phpcs:enable
|
||||
public function delete(Request $request, Response $response, int $id, int $dataId): Response
|
||||
{
|
||||
// Check that we have permission to edit this widget
|
||||
$widget = $this->widgetFactory->getById($id);
|
||||
if (!$this->getUser()->checkEditable($widget)) {
|
||||
throw new AccessDeniedException(__('This Widget is not shared with you with edit permission'));
|
||||
}
|
||||
|
||||
// Make sure this dataId is for this widget
|
||||
$widgetData = $this->widgetDataFactory->getById($dataId);
|
||||
|
||||
if ($id !== $widgetData->widgetId) {
|
||||
throw new AccessDeniedException(__('This widget data does not belong to this widget'));
|
||||
}
|
||||
|
||||
// Delete it.
|
||||
$widgetData->delete();
|
||||
|
||||
// Successful
|
||||
$this->getState()->hydrate(['message' => __('Deleted'), 'httpStatus' => 204]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
/**
|
||||
* @SWG\Definition(
|
||||
* definition="WidgetDataOrder",
|
||||
* @SWG\Property(
|
||||
* property="dataId",
|
||||
* type="integer",
|
||||
* description="Data ID"
|
||||
* ),
|
||||
* @SWG\Property(
|
||||
* property="displayOrder",
|
||||
* type="integer",
|
||||
* description="Desired display order"
|
||||
* )
|
||||
* )
|
||||
*
|
||||
* @SWG\Post(
|
||||
* path="/playlist/widget/data/{id}/order",
|
||||
* operationId="orderWidgetData",
|
||||
* tags={"widget"},
|
||||
* summary="Update the order of data on a Widget",
|
||||
* description="Provide all data to be ordered on a widget",
|
||||
* @SWG\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Widget ID that this data is attached to",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="dataId",
|
||||
* in="path",
|
||||
* description="The ID of the data to be deleted",
|
||||
* type="integer",
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Parameter(
|
||||
* name="order",
|
||||
* in="body",
|
||||
* description="An array of any widget data records that should be re-ordered",
|
||||
* @SWG\Schema(
|
||||
* type="array",
|
||||
* @SWG\Items(ref="WidgetDataOrder")
|
||||
* ),
|
||||
* required=true
|
||||
* ),
|
||||
* @SWG\Response(
|
||||
* response=204,
|
||||
* description="successful operation"
|
||||
* )
|
||||
* )
|
||||
* @throws \Xibo\Support\Exception\GeneralException
|
||||
*/
|
||||
// phpcs:enable
|
||||
public function setOrder(Request $request, Response $response, int $id): Response
|
||||
{
|
||||
// Check that we have permission to edit this widget
|
||||
$widget = $this->widgetFactory->getById($id);
|
||||
if (!$this->getUser()->checkEditable($widget)) {
|
||||
throw new AccessDeniedException(__('This Widget is not shared with you with edit permission'));
|
||||
}
|
||||
|
||||
// Expect an array of `id` in order.
|
||||
$params = $this->getSanitizer($request->getParams());
|
||||
foreach ($params->getArray('order', ['default' => []]) as $item) {
|
||||
$itemParams = $this->getSanitizer($item);
|
||||
|
||||
// Make sure this dataId is for this widget
|
||||
$widgetData = $this->widgetDataFactory->getById($itemParams->getInt('dataId'));
|
||||
$widgetData->displayOrder = $itemParams->getInt('displayOrder');
|
||||
|
||||
if ($id !== $widgetData->widgetId) {
|
||||
throw new AccessDeniedException(__('This widget data does not belong to this widget'));
|
||||
}
|
||||
|
||||
// Save it
|
||||
$widgetData->save();
|
||||
}
|
||||
|
||||
// Successful
|
||||
$this->getState()->hydrate([
|
||||
'message' => __('Updated the display order for data on Widget'),
|
||||
'id' => $widget->widgetId,
|
||||
'httpStatus' => 204,
|
||||
]);
|
||||
|
||||
return $this->render($request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and validate the data provided in params.
|
||||
* @throws \Xibo\Support\Exception\InvalidArgumentException
|
||||
* @throws \Xibo\Support\Exception\NotFoundException
|
||||
*/
|
||||
private function parseAndValidate(\Xibo\Entity\Widget $widget, array $item): array
|
||||
{
|
||||
// Check that this module is a data widget
|
||||
$module = $this->moduleFactory->getByType($widget->type);
|
||||
if (!$module->isDataProviderExpected()) {
|
||||
throw new InvalidArgumentException(__('This is not a data widget'));
|
||||
}
|
||||
|
||||
if ($module->fallbackData !== 1) {
|
||||
throw new InvalidArgumentException(__('Fallback data is not expected for this Widget'));
|
||||
}
|
||||
|
||||
// Parse out the data string we've been given and make sure it's valid according to this widget's datatype
|
||||
$data = [];
|
||||
$params = $this->getSanitizer($item);
|
||||
|
||||
$dataType = $this->moduleFactory->getDataTypeById($module->dataType);
|
||||
foreach ($dataType->fields as $field) {
|
||||
if ($field->isRequired && !$params->hasParam($field->id)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Data is missing a field called %s',
|
||||
$field->title
|
||||
));
|
||||
}
|
||||
|
||||
$value = match ($field->type) {
|
||||
'number' => $params->getDouble($field->id),
|
||||
default => $params->getString($field->id),
|
||||
};
|
||||
$data[$field->id] = $value;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user