531 lines
26 KiB
PHP
531 lines
26 KiB
PHP
<?php
|
|
|
|
namespace routes;
|
|
|
|
use classes\authentication;
|
|
use classes\response;
|
|
use classes\shelly_relay_inventory;
|
|
use dynamicimages\images\machine_1;
|
|
use objects\categories_o;
|
|
use objects\department_lanes_o;
|
|
use objects\department_selfserve_tasks_o;
|
|
use objects\logs_o;
|
|
use traits\route_t;
|
|
|
|
class departmentLanesRoute
|
|
{
|
|
use route_t;
|
|
|
|
public function run(): void
|
|
{
|
|
$this->get('/department/lanes/status-toggles', function () {
|
|
global $response;
|
|
|
|
$this->requirePermission('list_department_lanes');
|
|
self::requireParameters(['department_id']);
|
|
$department_id = (int)self::getParameter('department_id');
|
|
self::requireType($department_id, self::type_int());
|
|
self::requireMinValue($department_id, 1);
|
|
self::requireDepartmentAccess($department_id);
|
|
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
(new logs_o())->add('department_lanes', 'global', 1, 0, 'LIST_DEPARTMENT_LANE_STATUS_TOGGLES', 'User tried to list department lane status toggles without being logged in');
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
|
|
(new logs_o())->add('department_lanes', 'global', 1, $user->id, 'LIST_DEPARTMENT_LANE_STATUS_TOGGLES', 'User listed department lane status toggles for department ' . $department_id);
|
|
$response->success(
|
|
array_map(
|
|
static fn (department_lanes_o $department_lane): array => $department_lane->asArray(),
|
|
(new department_lanes_o())->getDepartmentLanes($department_id)
|
|
)
|
|
);
|
|
},
|
|
[
|
|
'list_department_lanes' => 'List department lane status toggles'
|
|
]
|
|
);
|
|
|
|
$this->get('/department/lanes', function () {
|
|
|
|
// Require the user to be logged in
|
|
global $response;
|
|
$this->requirePermission('list_department_lanes');
|
|
// Get the user object
|
|
$user = (new authentication())->get_user();
|
|
// Check if the request was successful
|
|
if ($user) {
|
|
// Check if the id parameter is set
|
|
if (self::isParametersSet(['id'])) {
|
|
// If it is, return the department lane with that id
|
|
$department_lane = (new department_lanes_o())->select((int)$response->getRequestParameter('id'));
|
|
// Check if the department lane exists
|
|
if (!$department_lane->exists()) {
|
|
// Return an error
|
|
$response->error('Department lane not found', 404);
|
|
}
|
|
self::requireDepartmentAccess((int)$department_lane->department->value());
|
|
// Log the incident
|
|
(new logs_o())->add('department_lanes', 'global', 1, $user->id, 'VIEW_DEPARTMENT_LANE', 'User viewed department lane with id ' . $department_lane->id);
|
|
// Return the department lane
|
|
$response->success(
|
|
$department_lane->asArray()
|
|
);
|
|
}
|
|
// Log the incident
|
|
(new logs_o())->add('department_lanes', 'global', 1, $user->id, 'LIST_DEPARTMENT_LANES', 'User listed department lanes');
|
|
// Return the list of departments
|
|
$response->success(
|
|
(new department_lanes_o())
|
|
->setSearchableFields([
|
|
// The fields that can be searched. This would otherwise make it possible to get secret information from the database, simply by searching for it and getting the result count back
|
|
'id',
|
|
'name',
|
|
'department',
|
|
'relay_in_id',
|
|
'relay_out_id',
|
|
'relay_machine_id',
|
|
'relay_machine_program_picker_id',
|
|
'relay_machine_cleaner_id',
|
|
'dynamic_image_id',
|
|
'machine_type_id',
|
|
'selfserve_enabled',
|
|
])
|
|
->listObjectsWithPaginationIfSet(
|
|
function ($department_lane) use ($user) {
|
|
// Create the department lane object
|
|
$department_lane_o = (new department_lanes_o())->select((int)$department_lane['id']);
|
|
// Return the object as an array
|
|
return $department_lane_o->asArray();
|
|
},
|
|
(new department_lanes_o())->forceRestrictFilters(
|
|
[
|
|
// This makes sure that the user can only see lanes from departments they explicitly have access to
|
|
'department' => $user->getGroup()->getDepartments(),
|
|
]
|
|
)
|
|
)
|
|
);
|
|
} else {
|
|
// Log the incident
|
|
(new logs_o())->add('department_lanes', 'global', 1, 0, 'LIST_DEPARTMENT_LANES', 'User tried to list department lanes without being logged in');
|
|
// Return an error
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'list_department_lanes' => 'List all department lanes'
|
|
]
|
|
);
|
|
|
|
$this->get('/department/lanes/relay-options', function () {
|
|
|
|
global $response;
|
|
$this->requirePermission('list_department_lanes');
|
|
$user = (new authentication())->get_user();
|
|
|
|
if ($user) {
|
|
try {
|
|
$options = (new shelly_relay_inventory())->listRelayOptions();
|
|
(new logs_o())->add(
|
|
'department_lanes',
|
|
'global',
|
|
1,
|
|
$user->id,
|
|
'LIST_DEPARTMENT_LANE_RELAY_OPTIONS',
|
|
'User listed available Shelly relay options'
|
|
);
|
|
$response->success($options);
|
|
} catch (\Throwable $e) {
|
|
(new logs_o())->add(
|
|
'department_lanes',
|
|
'global',
|
|
0,
|
|
$user->id,
|
|
'LIST_DEPARTMENT_LANE_RELAY_OPTIONS',
|
|
'Failed to list Shelly relay options: ' . $e->getMessage()
|
|
);
|
|
$response->error('Failed to fetch Shelly relay options: ' . $e->getMessage(), 400);
|
|
}
|
|
} else {
|
|
(new logs_o())->add(
|
|
'department_lanes',
|
|
'global',
|
|
1,
|
|
0,
|
|
'LIST_DEPARTMENT_LANE_RELAY_OPTIONS',
|
|
'User tried to list Shelly relay options without being logged in'
|
|
);
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'list_department_lanes' => 'List available Shelly relay options for department lanes'
|
|
]
|
|
);
|
|
|
|
/**
|
|
* Generate dynamic image for a department lane (machine UI)
|
|
*
|
|
* Query parameters:
|
|
* - department (int, required)
|
|
* - lane (int, required)
|
|
* - buttons (array|json|csv, optional) → ordered highlighted button IDs (0-indexed), "reset", "start", or "program_picker"
|
|
* - current_step (int >= 0, optional) → current click/step indicator
|
|
* - only_current_step (bool/int, optional) → if true, only draw current step highlight
|
|
* - vehicle_type (int|null, optional) → normalized but currently not used by machine_1
|
|
*
|
|
* Response: image/png
|
|
*/
|
|
$this->get('/department/lanes/dynamic-image', function () {
|
|
global $response;
|
|
// Reuse listing permission; viewing image is tied to lane visibility
|
|
// Authenticated user and department access validation
|
|
//$this->requirePermission('view_department_lane_image');
|
|
|
|
|
|
// Required params
|
|
self::requireParameters(['department', 'lane']);
|
|
$department_id = (int) self::getParameter('department');
|
|
$lane_id = (int) self::getParameter('lane');
|
|
self::requireType($department_id, self::type_int());
|
|
self::requireType($lane_id, self::type_int());
|
|
self::requireMinValue($department_id, 1);
|
|
self::requireMinValue($lane_id, 1);
|
|
|
|
|
|
// Load lane and verify it belongs to department
|
|
$lane = (new department_lanes_o())->select($lane_id);
|
|
if (!$lane->exists()) {
|
|
$response->error('Department lane not found', 404);
|
|
}
|
|
if ((int)$lane->department->value() !== $department_id) {
|
|
$response->error('Lane does not belong to the specified department', 400);
|
|
}
|
|
|
|
// Resolve dynamic image id → class (support id=1 for now)
|
|
$dynamic_image_id = $lane->dynamic_image_id->value();
|
|
if ($response->isRequestParameterSet('dynamic_image_id')) {
|
|
$dynamic_image_override = $response->getRequestParameter('dynamic_image_id');
|
|
if ($dynamic_image_override === null || $dynamic_image_override === '' || strtolower((string)$dynamic_image_override) === 'null') {
|
|
$dynamic_image_id = null;
|
|
} else {
|
|
$dynamic_image_id = (int)$dynamic_image_override;
|
|
self::requireMinValue($dynamic_image_id, 1);
|
|
}
|
|
}
|
|
if ($dynamic_image_id === null) {
|
|
$response->error('No dynamic image configured for this lane', 404);
|
|
}
|
|
$dynamic_image_id = (int)$dynamic_image_id;
|
|
|
|
// Parse optional params
|
|
$buttons = null;
|
|
if ($response->isRequestParameterSet('buttons')) {
|
|
$buttons = $response->getRequestParameter('buttons');
|
|
try {
|
|
$buttons = department_selfserve_tasks_o::normalizeButtonsInput($buttons);
|
|
} catch (\Exception $e) {
|
|
$response->error('Invalid buttons parameter: ' . $e->getMessage(), 400);
|
|
}
|
|
}
|
|
|
|
$current_step = 0;
|
|
if ($response->isRequestParameterSet('current_step')) {
|
|
$cs = (int)$response->getRequestParameter('current_step');
|
|
self::requireMinValue($cs, 0);
|
|
$current_step = $cs;
|
|
}
|
|
|
|
$only_current_step = false;
|
|
if ($response->isRequestParameterSet('only_current_step')) {
|
|
$val = $response->getRequestParameter('only_current_step');
|
|
// Accept true/false, 1/0, '1'/'0'
|
|
$only_current_step = in_array(strtolower((string)$val), ['1', 'true', 'yes', 'on'], true);
|
|
}
|
|
|
|
// Vehicle type (normalized; may be unused by concrete image class at present)
|
|
$vehicle_type = null;
|
|
if ($response->isRequestParameterSet('vehicle_type')) {
|
|
try {
|
|
$vehicle_type = department_selfserve_tasks_o::normalizeVehicleTypeInput($response->getRequestParameter('vehicle_type'));
|
|
} catch (\Exception $e) {
|
|
$response->error('Invalid vehicle_type parameter: ' . $e->getMessage(), 400);
|
|
}
|
|
}
|
|
|
|
// Thumb position (normalized)
|
|
$thumb_position = null;
|
|
if ($response->isRequestParameterSet('thumb_position')) {
|
|
$thumb_position = (int)$response->getRequestParameter('thumb_position');
|
|
self::requireMinValue($thumb_position, 1);
|
|
self::requireMaxValue($thumb_position, 12);
|
|
}
|
|
|
|
// Cache check
|
|
$cacheKey = null;
|
|
if (defined('redis')) {
|
|
// Cache only the default image variant to avoid unbounded cache key growth
|
|
// from request-controlled parameters (buttons/current_step/etc.).
|
|
$isDefaultVariant = $buttons === null
|
|
&& $current_step === 0
|
|
&& !(bool)$only_current_step
|
|
&& $vehicle_type === null;
|
|
|
|
if ($isDefaultVariant) {
|
|
$cacheParams = [
|
|
'department' => $department_id,
|
|
'lane' => $lane_id,
|
|
'dynamic_image_id' => $dynamic_image_id,
|
|
];
|
|
$cacheKey = 'dynamic_image:' . md5(json_encode($cacheParams));
|
|
$cachedImage = redis->get($cacheKey);
|
|
if ($cachedImage) {
|
|
header('Content-Type: image/png');
|
|
header('Content-Length: ' . strlen($cachedImage));
|
|
echo $cachedImage;
|
|
exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Instantiate and configure the image class based on dynamic_image_id
|
|
switch ($dynamic_image_id) {
|
|
case 1:
|
|
$image = new machine_1();
|
|
if ($thumb_position !== null) {
|
|
$image->thumb_position = $thumb_position;
|
|
}
|
|
break;
|
|
default:
|
|
$response->error('Unsupported dynamic image id: ' . $dynamic_image_id, 400);
|
|
}
|
|
|
|
// Apply parameters
|
|
if (is_array($buttons)) {
|
|
$image->highlighted_buttons = $buttons;
|
|
}
|
|
$image->current_step = $current_step;
|
|
$image->only_generate_current_step = (bool)$only_current_step;
|
|
// Note: $vehicle_type currently not used by machine_1; reserved for future images.
|
|
|
|
// Compose and serve the image
|
|
try {
|
|
$image->setup();
|
|
|
|
// If caching is enabled, we need to capture the output or use export
|
|
if ($cacheKey && defined('redis')) {
|
|
$dataUri = $image->exportAsBase64('png');
|
|
if (preg_match('/^data:image\/png;base64,(.*)$/', $dataUri, $matches)) {
|
|
$imageData = base64_decode($matches[1]);
|
|
if ($imageData !== false) {
|
|
redis->setEx($cacheKey, $imageData, 86400); // Cache for 24 hours
|
|
header('Content-Type: image/png');
|
|
header('Content-Length: ' . strlen($imageData));
|
|
echo $imageData;
|
|
exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
$image->servePicture('png');
|
|
exit; // Ensure no extra output is appended
|
|
} catch (\Exception $e) {
|
|
$response->error('Failed to generate image: ' . $e->getMessage(), 500);
|
|
}
|
|
}, [
|
|
'list_department_lanes' => 'View dynamic image for a department lane',
|
|
]);
|
|
|
|
$this->post('/department/lanes', function () {
|
|
|
|
// Require the user to be logged in
|
|
global $response;
|
|
$this->requirePermission('add_department_lane');
|
|
// Get the user object
|
|
$user = (new authentication())->get_user();
|
|
// Check if the request was successful
|
|
if ($user) {
|
|
// Log the incident
|
|
(new logs_o())->add('department_lanes', 'global', 1, $user->id, 'ADD_DEPARTMENT_LANE', 'User added a department lane');
|
|
// Get the request data
|
|
$name = $response->getRequestParameter('name') ?? null;
|
|
$department = $response->getRequestParameter('department') ?? null;
|
|
$relay_in_id = self::normalizeRelayRequestParameter($response->getRequestParameter('relay_in_id') ?? null);
|
|
$relay_out_id = self::normalizeRelayRequestParameter($response->getRequestParameter('relay_out_id') ?? null);
|
|
$relay_machine_id = self::normalizeRelayRequestParameter($response->getRequestParameter('relay_machine_id') ?? null);
|
|
$relay_machine_program_picker_id = self::normalizeRelayRequestParameter($response->getRequestParameter('relay_machine_program_picker_id') ?? null);
|
|
$relay_machine_cleaner_id = self::normalizeRelayRequestParameter($response->getRequestParameter('relay_machine_cleaner_id') ?? null);
|
|
|
|
$dynamic_image_id = $response->getRequestParameter('dynamic_image_id') ?? null;
|
|
$machine_type_id = $response->getRequestParameter('machine_type_id') ?? null;
|
|
$selfserve_enabled = self::isParametersSet(['selfserve_enabled'])
|
|
? department_lanes_o::normalizeSelfServeEnabledValue($response->getRequestParameter('selfserve_enabled'))
|
|
: true;
|
|
if ($dynamic_image_id !== null) {
|
|
$did = (int)$dynamic_image_id;
|
|
$this->requireType($did, $this->type_int());
|
|
$this->requireMinValue($did, 1);
|
|
$dynamic_image_id = $did;
|
|
}
|
|
if ($machine_type_id !== null && $machine_type_id !== '' && strtolower((string)$machine_type_id) !== 'null') {
|
|
$machine_type_id = (int)$machine_type_id;
|
|
$this->requireType($machine_type_id, $this->type_int());
|
|
$this->requireMinValue($machine_type_id, 1);
|
|
} else {
|
|
$machine_type_id = null;
|
|
}
|
|
// Check if the required fields are set
|
|
if ($name && $department) {
|
|
self::requireDepartmentAccess((int)$department);
|
|
// Add the department lane
|
|
$created_lane = (new department_lanes_o())->add((int)$department, (string)$name, $relay_in_id, $relay_out_id, $relay_machine_id, $relay_machine_program_picker_id, $relay_machine_cleaner_id, $dynamic_image_id, $machine_type_id, $selfserve_enabled);
|
|
// Return a success message
|
|
$response->success([
|
|
'message' => 'Department lane added',
|
|
'lane' => $created_lane->asArray(),
|
|
]);
|
|
} else {
|
|
// Return an error
|
|
$response->error('Missing required fields', 400);
|
|
}
|
|
|
|
} else {
|
|
// Log the incident
|
|
(new logs_o())->add('department_lanes', 'global', 1, 0, 'ADD_DEPARTMENT_LANE', 'User tried to add a department lane without being logged in');
|
|
// Return an error
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'add_department_lane' => 'Add a department lane'
|
|
]
|
|
);
|
|
|
|
$this->put('/department/lanes', function () {
|
|
|
|
// Require the user to be logged in
|
|
global $response;
|
|
$this->requirePermission('edit_department_lane');
|
|
// Get the user object
|
|
$user = (new authentication())->get_user();
|
|
// Check if the request was successful
|
|
if ($user) {
|
|
// Log the incident
|
|
(new logs_o())->add('department_lanes', 'global', 1, $user->id, 'EDIT_DEPARTMENT_LANE', 'User edited a department lane');
|
|
// Get the request data
|
|
$id = $response->getRequestParameter('id') ?? null;
|
|
$name = $response->getRequestParameter('name') ?? null;
|
|
$department = $response->getRequestParameter('department') ?? null;
|
|
$relay_in_id = self::normalizeRelayRequestParameter($response->getRequestParameter('relay_in_id') ?? null);
|
|
$relay_out_id = self::normalizeRelayRequestParameter($response->getRequestParameter('relay_out_id') ?? null);
|
|
$relay_machine_id = self::normalizeRelayRequestParameter($response->getRequestParameter('relay_machine_id') ?? null);
|
|
$relay_machine_program_picker_id = self::normalizeRelayRequestParameter($response->getRequestParameter('relay_machine_program_picker_id') ?? null);
|
|
$relay_machine_cleaner_id = self::normalizeRelayRequestParameter($response->getRequestParameter('relay_machine_cleaner_id') ?? null);
|
|
$dynamic_image_id = $response->getRequestParameter('dynamic_image_id') ?? null;
|
|
$machine_type_id = $response->getRequestParameter('machine_type_id') ?? null;
|
|
|
|
// Check what fields are set
|
|
self::requireParameters(['id']);
|
|
// Get the department lane object
|
|
$department_lane = (new department_lanes_o())->select((int)$id);
|
|
// Check if the department lane exists
|
|
if (!$department_lane->exists()) {
|
|
// Return an error
|
|
$response->error('Department lane not found', 404);
|
|
}
|
|
self::requireDepartmentAccess((int)$department_lane->department->value());
|
|
$was_selfserve_enabled = $department_lane->isSelfServeEnabled();
|
|
// Update the department lane fields that are set
|
|
if (self::isParametersSet(['name'])) {
|
|
$department_lane->name->set($name);
|
|
}
|
|
if (self::isParametersSet(['department'])) {
|
|
self::requireDepartmentAccess((int)$department);
|
|
$department_lane->department->set((int)$department);
|
|
}
|
|
if (self::isParametersSet(['relay_in_id'])) {
|
|
self::syncDepartmentLaneRelayValue($department_lane->relay_in_id, $relay_in_id);
|
|
}
|
|
if (self::isParametersSet(['relay_out_id'])) {
|
|
self::syncDepartmentLaneRelayValue($department_lane->relay_out_id, $relay_out_id);
|
|
}
|
|
if (self::isParametersSet(['relay_machine_id'])) {
|
|
self::syncDepartmentLaneRelayValue($department_lane->relay_machine_id, $relay_machine_id);
|
|
}
|
|
if (self::isParametersSet(['relay_machine_program_picker_id'])) {
|
|
self::syncDepartmentLaneRelayValue($department_lane->relay_machine_program_picker_id, $relay_machine_program_picker_id);
|
|
}
|
|
if (self::isParametersSet(['relay_machine_cleaner_id'])) {
|
|
self::syncDepartmentLaneRelayValue($department_lane->relay_machine_cleaner_id, $relay_machine_cleaner_id);
|
|
}
|
|
if (self::isParametersSet(['dynamic_image_id'])) {
|
|
$param = $response->getRequestParameter('dynamic_image_id');
|
|
if ($param === null || $param === '' || (is_string($param) && strtolower($param) === 'null')) {
|
|
$department_lane->dynamic_image_id->nullify();
|
|
} else {
|
|
$did = (int)$param;
|
|
$this->requireType($did, $this->type_int());
|
|
$this->requireMinValue($did, 1);
|
|
$department_lane->dynamic_image_id->set($did);
|
|
}
|
|
}
|
|
if (self::isParametersSet(['machine_type_id'])) {
|
|
$param = $machine_type_id;
|
|
if ($param === null || $param === '' || (is_string($param) && strtolower($param) === 'null')) {
|
|
$department_lane->machine_type_id->nullify();
|
|
} else {
|
|
$machineTypeId = (int)$param;
|
|
$this->requireType($machineTypeId, $this->type_int());
|
|
$this->requireMinValue($machineTypeId, 1);
|
|
$department_lane->machine_type_id->set($machineTypeId);
|
|
}
|
|
}
|
|
if (self::isParametersSet(['selfserve_enabled'])) {
|
|
$next_selfserve_enabled = department_lanes_o::normalizeSelfServeEnabledValue($response->getRequestParameter('selfserve_enabled'));
|
|
$department_lane->selfserve_enabled->set($next_selfserve_enabled);
|
|
if ($was_selfserve_enabled && !$next_selfserve_enabled) {
|
|
department_lanes_o::disableSelfServeRelaysBestEffort((int)$department_lane->id);
|
|
}
|
|
}
|
|
// Return a success message
|
|
$response->success([
|
|
'message' => 'Department lane updated',
|
|
'lane' => $department_lane->asArray(),
|
|
]);
|
|
} else {
|
|
// Log the incident
|
|
(new logs_o())->add('department_lanes', 'global', 1, 0, 'EDIT_DEPARTMENT_LANE', 'User tried to edit a department lane without being logged in');
|
|
// Return an error
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'edit_department_lane' => 'Edit a department lane'
|
|
]
|
|
);
|
|
}
|
|
|
|
private static function normalizeRelayRequestParameter(mixed $value): ?string
|
|
{
|
|
$normalized = trim((string)($value ?? ''));
|
|
if ($normalized === '' || strtolower($normalized) === 'null') {
|
|
return null;
|
|
}
|
|
|
|
return $normalized;
|
|
}
|
|
|
|
private static function syncDepartmentLaneRelayValue(mixed $field, mixed $value): void
|
|
{
|
|
$normalized = self::normalizeRelayRequestParameter($value);
|
|
if ($normalized === null) {
|
|
$field->nullify();
|
|
return;
|
|
}
|
|
|
|
$field->set($normalized);
|
|
}
|
|
}
|