Add dynamic image generation for department lanes
- Implement `/department/lanes/dynamic-image` endpoint for machine UI dynamic image rendering. - Add OpenAPI specification for the new endpoint with query parameters for buttons, current step, and vehicle type. - Include `DepartmentLanesImageTest` for lightweight testing of image behavior and input normalization. - Update `departmentLanesRoute.php` with logic for parameter handling and image composition based on lane configuration.
This commit is contained in:
@@ -1958,6 +1958,77 @@ paths:
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
|
||||
/department/lanes/dynamic-image:
|
||||
get:
|
||||
tags:
|
||||
- Departments
|
||||
summary: Generate dynamic image for a department lane
|
||||
description: |
|
||||
Returns a composed machine UI image for the specified department lane.
|
||||
You can optionally highlight button indices, set the current step indicator, and toggle only-current-step mode.
|
||||
operationId: getDepartmentLaneDynamicImage
|
||||
parameters:
|
||||
- name: department
|
||||
in: query
|
||||
required: true
|
||||
description: Department ID
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
- name: lane
|
||||
in: query
|
||||
required: true
|
||||
description: Lane ID
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
- name: buttons
|
||||
in: query
|
||||
required: false
|
||||
description: Highlighted button IDs (0-indexed). Accepts CSV, JSON array, or repeated query params.
|
||||
schema:
|
||||
oneOf:
|
||||
- type: string
|
||||
- type: array
|
||||
items:
|
||||
type: integer
|
||||
- name: current_step
|
||||
in: query
|
||||
required: false
|
||||
description: Current step indicator (non-negative integer)
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 0
|
||||
- name: only_current_step
|
||||
in: query
|
||||
required: false
|
||||
description: If true, only draw the current step highlight
|
||||
schema:
|
||||
type: boolean
|
||||
- name: vehicle_type
|
||||
in: query
|
||||
required: false
|
||||
description: Vehicle type selection override (nullable non-negative integer)
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 0
|
||||
responses:
|
||||
'200':
|
||||
description: Dynamic image rendered successfully
|
||||
content:
|
||||
image/png:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
|
||||
/guest/validation/customer-number:
|
||||
post:
|
||||
tags:
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
namespace routes;
|
||||
|
||||
use classes\authentication;
|
||||
use classes\response;
|
||||
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;
|
||||
|
||||
@@ -75,6 +78,127 @@ class departmentLanesRoute
|
||||
]
|
||||
);
|
||||
|
||||
/**
|
||||
* Generate dynamic image for a department lane (machine UI)
|
||||
*
|
||||
* Query parameters:
|
||||
* - department (int, required)
|
||||
* - lane (int, required)
|
||||
* - buttons (array|json|csv, optional) → highlighted button IDs (0-indexed)
|
||||
* - 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
|
||||
$this->requirePermission('list_department_lanes');
|
||||
|
||||
// Authenticated user and department access validation
|
||||
$user = (new authentication())->get_user();
|
||||
if (!$user) {
|
||||
$response->error('Invalid session', 400);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// Department access control (if group limits apply)
|
||||
if (method_exists($user, 'getGroup') && $user->getGroup()) {
|
||||
$authorized_department_ids = $user->getGroup()->getDepartments();
|
||||
if (is_array($authorized_department_ids) && !in_array($department_id, $authorized_department_ids, true)) {
|
||||
$response->error('You do not have access to this department', 403);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 ($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);
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate and configure the image class based on dynamic_image_id
|
||||
switch ($dynamic_image_id) {
|
||||
case 1:
|
||||
$image = new machine_1();
|
||||
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();
|
||||
$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
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
// Lightweight, env-agnostic test for dynamic images construction
|
||||
// This test avoids requiring full app bootstrap and Imagick composition by
|
||||
// leveraging exportAsBase64() fallback when no canvas is initialized.
|
||||
|
||||
// Minimal stubs to satisfy dependencies when running without full bootstrap
|
||||
namespace classes { class db {} class object_property { public function __construct(...$a){} public function value(){return null;} public function set($v){} } }
|
||||
namespace traits { trait db_object_t {} }
|
||||
|
||||
namespace {
|
||||
if (!defined('WD')) { define('WD', dirname(__DIR__, 2)); }
|
||||
|
||||
require_once WD . '/modules/dynamicimages/traits/dynamicimages_asset_t.php';
|
||||
require_once WD . '/modules/dynamicimages/interfaces/dynamicimages_asset_i.php';
|
||||
require_once WD . '/modules/dynamicimages/classes/dynamicimages_asset.php';
|
||||
require_once WD . '/modules/dynamicimages/interfaces/dynamicimages_image_i.php';
|
||||
require_once WD . '/modules/dynamicimages/traits/dynamicimages_image_t.php';
|
||||
require_once WD . '/modules/dynamicimages/classes/dynamicimages_image.php';
|
||||
require_once WD . '/modules/dynamicimages/images/machine_1.php';
|
||||
require_once WD . '/objects/department_selfserve_tasks_o.php';
|
||||
|
||||
use dynamicimages\images\machine_1;
|
||||
use objects\department_selfserve_tasks_o;
|
||||
|
||||
function ok($m){ echo "\n\033[32m✔ $m\033[0m\n"; }
|
||||
function fail($m){ echo "\n\033[31m✖ $m\033[0m\n"; }
|
||||
|
||||
echo "\nDepartmentLanesImageTest starting...\n";
|
||||
|
||||
// 1) Buttons normalization examples
|
||||
try {
|
||||
$arr = department_selfserve_tasks_o::normalizeButtonsInput('0, 2,3 , 5');
|
||||
if ($arr === [0,2,3,5]) { ok('CSV buttons normalization works'); } else { fail('CSV normalization mismatch: '.json_encode($arr)); }
|
||||
} catch (\Exception $e) { fail('CSV normalization threw: '.$e->getMessage()); }
|
||||
|
||||
try {
|
||||
$arr = department_selfserve_tasks_o::normalizeButtonsInput('[1, 1, "2", 4]');
|
||||
if ($arr === [1,2,4]) { ok('JSON buttons normalization with de-dup works'); } else { fail('JSON normalization mismatch: '.json_encode($arr)); }
|
||||
} catch (\Exception $e) { fail('JSON normalization threw: '.$e->getMessage()); }
|
||||
|
||||
// 2) Vehicle type normalization examples
|
||||
try { $v = department_selfserve_tasks_o::normalizeVehicleTypeInput('3'); if ($v===3) ok('Vehicle type numeric string ok'); else fail('Vehicle type mismatch'); } catch (\Exception $e) { fail($e->getMessage()); }
|
||||
try { $v = department_selfserve_tasks_o::normalizeVehicleTypeInput(null); if ($v===null) ok('Vehicle type null ok'); else fail('Vehicle type null mismatch'); } catch (\Exception $e) { fail($e->getMessage()); }
|
||||
|
||||
// 3) Sanity: instantiate machine_1 and export base64 from first asset (no canvas)
|
||||
try {
|
||||
$img = new machine_1();
|
||||
// Set some sample parameters (not used until setup(), which we skip to avoid Imagick requirement)
|
||||
$img->highlighted_buttons = [0,2,5];
|
||||
$img->current_step = 1;
|
||||
$dataUri = $img->exportAsBase64();
|
||||
if (is_string($dataUri) && str_starts_with($dataUri, 'data:image/')) {
|
||||
ok('machine_1 exportAsBase64 fallback returns a data URI');
|
||||
} else {
|
||||
fail('machine_1 exportAsBase64 returned unexpected value');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
fail('machine_1 instantiation/export threw: '.$e->getMessage());
|
||||
}
|
||||
|
||||
echo "\nDepartmentLanesImageTest completed.\n";
|
||||
}
|
||||
Reference in New Issue
Block a user