608 lines
20 KiB
PHP
608 lines
20 KiB
PHP
<?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;
|
|
}
|
|
}
|