Initial Upload

This commit is contained in:
Matt Batchelder
2025-12-02 10:32:59 -05:00
commit 05ce0da296
2240 changed files with 467811 additions and 0 deletions

607
lib/Controller/Action.php Normal file
View 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;
}
}