diff --git a/services/nginx/app/cron/Cron.php b/services/nginx/app/cron/Cron.php index 5dd223e5..8ac8497a 100644 --- a/services/nginx/app/cron/Cron.php +++ b/services/nginx/app/cron/Cron.php @@ -19,6 +19,8 @@ use classes\slack as Slack; use classes\email as Email; use classes\gatewayapi as GatewayAPI; use dynamicimages\images\machine_1; +use modules\selfserve\config\selfserve_dynamic_image_size_c; +use modules\selfserve\selfserve_c; use goals\classes\goals_criteria; use goals\services\goals_progress_alert_renderer; use goals\helpers\goals_criteria_progress_alert_destination as Dest; @@ -33,6 +35,8 @@ use objects\users_o; use routes\moduleWeatherAPIRoute; require_once __DIR__ . '/../classes/economic_transfer_executor.php'; + +const DYNAMIC_IMAGE_RELEVANT_MAX_WIDTH = 1600; require_once __DIR__ . '/../classes/economic_transfer_queue_schema_bootstrap.php'; require_once __DIR__ . '/../classes/economic_transfer_queue.php'; require_once __DIR__ . '/../classes/workfeed_employee_name_formatter.php'; @@ -930,6 +934,7 @@ function buildDynamicImageCacheKey(array $variant): string 'current_step' => (int)($variant['current_step'] ?? 0), 'only_current_step' => (bool)($variant['only_current_step'] ?? false), 'vehicle_type' => $variant['vehicle_type'] ?? null, + 'dynamic_image_size' => getSelfServeDynamicImageSizeModeForCron(), ]; $json = json_encode($cacheParams); @@ -962,6 +967,9 @@ function renderDynamicImageVariant(int $dynamicImageId, ?array $buttons, int $cu $image->current_step = max(0, $currentStep); $image->only_generate_current_step = $onlyCurrentStep; $image->setup(); + if (getSelfServeDynamicImageSizeModeForCron() === selfserve_dynamic_image_size_c::SIZE_RELEVANT) { + $image->resizeToMaxWidth(DYNAMIC_IMAGE_RELEVANT_MAX_WIDTH); + } $dataUri = $image->exportAsBase64('png'); if (!preg_match('/^data:image\/png;base64,(.*)$/', $dataUri, $matches)) { @@ -985,6 +993,19 @@ function renderDynamicImageVariant(int $dynamicImageId, ?array $buttons, int $cu } } +function getSelfServeDynamicImageSizeModeForCron(): string +{ + try { + $mode = (string)(new selfserve_c())->dynamic_image_size->getVariableValue(); + } catch (Throwable) { + return selfserve_dynamic_image_size_c::SIZE_ORIGINAL; + } + + return in_array($mode, [selfserve_dynamic_image_size_c::SIZE_ORIGINAL, selfserve_dynamic_image_size_c::SIZE_RELEVANT], true) + ? $mode + : selfserve_dynamic_image_size_c::SIZE_ORIGINAL; +} + /** * @param mixed $value * @return array diff --git a/services/nginx/app/modules/dynamicimages/traits/dynamicimages_image_t.php b/services/nginx/app/modules/dynamicimages/traits/dynamicimages_image_t.php index 43c5fefb..725b1b7a 100644 --- a/services/nginx/app/modules/dynamicimages/traits/dynamicimages_image_t.php +++ b/services/nginx/app/modules/dynamicimages/traits/dynamicimages_image_t.php @@ -226,6 +226,20 @@ trait dynamicimages_image_t return $this; } + public function resizeToMaxWidth(int $maxWidth): dynamicimages_image_i + { + $this->assertCanvasInitialized(); + if ($maxWidth <= 0) { + throw new \InvalidArgumentException('Resize max width must be a positive integer.'); + } + if ($this->canvasWidth === null || $this->canvasHeight === null || $this->canvasWidth <= $maxWidth) { + return $this; + } + + $height = (int)round($this->canvasHeight * ($maxWidth / $this->canvasWidth)); + return $this->resize($maxWidth, max(1, $height)); + } + public function crop(int $width, int $height, int $x, int $y): dynamicimages_image_i { $this->assertCanvasInitialized(); @@ -382,4 +396,4 @@ trait dynamicimages_image_t { $this->outputImage($format, $quality); } -} \ No newline at end of file +} diff --git a/services/nginx/app/modules/selfserve/config/selfserve_dynamic_image_size_c.php b/services/nginx/app/modules/selfserve/config/selfserve_dynamic_image_size_c.php new file mode 100644 index 00000000..463fcdf3 --- /dev/null +++ b/services/nginx/app/modules/selfserve/config/selfserve_dynamic_image_size_c.php @@ -0,0 +1,32 @@ +allowUpdate([ selfserve_enabled_c::class, selfserve_minute_product_c::class, - selfserve_machine_wash_minutes_included_c::class + selfserve_machine_wash_minutes_included_c::class, + selfserve_dynamic_image_size_c::class ]); $this->enabled = new selfserve_enabled_c(); $this->minute_product = new selfserve_minute_product_c(); $this->machine_wash_minutes_included = new selfserve_machine_wash_minutes_included_c(); + $this->dynamic_image_size = new selfserve_dynamic_image_size_c(); } } diff --git a/services/nginx/app/routes/departmentLanesRoute.php b/services/nginx/app/routes/departmentLanesRoute.php index 01711850..e5368c9a 100644 --- a/services/nginx/app/routes/departmentLanesRoute.php +++ b/services/nginx/app/routes/departmentLanesRoute.php @@ -6,6 +6,8 @@ use classes\authentication; use classes\response; use classes\shelly_relay_inventory; use dynamicimages\images\machine_1; +use modules\selfserve\config\selfserve_dynamic_image_size_c; +use modules\selfserve\selfserve_c; use objects\categories_o; use objects\department_lanes_o; use objects\department_selfserve_tasks_o; @@ -16,6 +18,8 @@ class departmentLanesRoute { use route_t; + private const RELEVANT_DYNAMIC_IMAGE_MAX_WIDTH = 1600; + public function run(): void { $this->get('/department/lanes/status-toggles', function () { @@ -219,6 +223,7 @@ class departmentLanesRoute $response->error('No dynamic image configured for this lane', 404); } $dynamic_image_id = (int)$dynamic_image_id; + $dynamic_image_size = self::getSelfServeDynamicImageSizeMode(); // Parse optional params $buttons = null; @@ -278,6 +283,7 @@ class departmentLanesRoute 'department' => $department_id, 'lane' => $lane_id, 'dynamic_image_id' => $dynamic_image_id, + 'dynamic_image_size' => $dynamic_image_size, ]; $cacheKey = 'dynamic_image:' . md5(json_encode($cacheParams)); $cachedImage = redis->get($cacheKey); @@ -313,6 +319,9 @@ class departmentLanesRoute // Compose and serve the image try { $image->setup(); + if ($dynamic_image_size === selfserve_dynamic_image_size_c::SIZE_RELEVANT) { + $image->resizeToMaxWidth(self::RELEVANT_DYNAMIC_IMAGE_MAX_WIDTH); + } // If caching is enabled, we need to capture the output or use export if ($cacheKey && defined('redis')) { @@ -527,4 +536,17 @@ class departmentLanesRoute $field->set($normalized); } + + private static function getSelfServeDynamicImageSizeMode(): string + { + try { + $mode = (string)(new selfserve_c())->dynamic_image_size->getVariableValue(); + } catch (\Throwable) { + return selfserve_dynamic_image_size_c::SIZE_ORIGINAL; + } + + return in_array($mode, [selfserve_dynamic_image_size_c::SIZE_ORIGINAL, selfserve_dynamic_image_size_c::SIZE_RELEVANT], true) + ? $mode + : selfserve_dynamic_image_size_c::SIZE_ORIGINAL; + } } diff --git a/services/nginx/app/tests/Unit/DynamicImages/DepartmentLaneDynamicImageRouteTest.php b/services/nginx/app/tests/Unit/DynamicImages/DepartmentLaneDynamicImageRouteTest.php index d6a7bb48..9b33e6ad 100644 --- a/services/nginx/app/tests/Unit/DynamicImages/DepartmentLaneDynamicImageRouteTest.php +++ b/services/nginx/app/tests/Unit/DynamicImages/DepartmentLaneDynamicImageRouteTest.php @@ -38,6 +38,28 @@ namespace { expect($content)->toContain("switch (\$dynamic_image_id)"); }); + it('uses the selfserve dynamic image size config for route output and cache keys', function (): void { + $route = file_get_contents(app_path('routes/departmentLanesRoute.php')); + $config = file_get_contents(app_path('modules/selfserve/config/selfserve_dynamic_image_size_c.php')); + $moduleConfig = file_get_contents(app_path('modules/selfserve/selfserve_c.php')); + $imageTrait = file_get_contents(app_path('modules/dynamicimages/traits/dynamicimages_image_t.php')); + + expect($route)->not->toBeFalse(); + expect($config)->not->toBeFalse(); + expect($moduleConfig)->not->toBeFalse(); + expect($imageTrait)->not->toBeFalse(); + expect($config)->toContain("self::SIZE_ORIGINAL"); + expect($config)->toContain("self::SIZE_RELEVANT"); + expect($config)->toContain("[self::SIZE_ORIGINAL, self::SIZE_RELEVANT]"); + expect($config)->toContain("'dynamic_image_size'"); + expect($moduleConfig)->toContain("selfserve_dynamic_image_size_c::class"); + expect($moduleConfig)->toContain('public selfserve_dynamic_image_size_c $dynamic_image_size;'); + expect($route)->toContain('$dynamic_image_size = self::getSelfServeDynamicImageSizeMode();'); + expect($route)->toContain("'dynamic_image_size' => \$dynamic_image_size"); + expect($route)->toContain("resizeToMaxWidth(self::RELEVANT_DYNAMIC_IMAGE_MAX_WIDTH)"); + expect($imageTrait)->toContain('function resizeToMaxWidth(int $maxWidth)'); + }); + it('accepts ordered dynamic image button tokens including program picker reset start and zero', function (): void { expect(department_selfserve_tasks_o::normalizeButtonsInput('["program_picker","reset",0,2,"start",5]'))->toBe([ 'program_picker', diff --git a/services/nginx/app/tests/Unit/DynamicImages/DynamicImagePreRenderCronWiringTest.php b/services/nginx/app/tests/Unit/DynamicImages/DynamicImagePreRenderCronWiringTest.php index d206591b..3349537d 100644 --- a/services/nginx/app/tests/Unit/DynamicImages/DynamicImagePreRenderCronWiringTest.php +++ b/services/nginx/app/tests/Unit/DynamicImages/DynamicImagePreRenderCronWiringTest.php @@ -13,4 +13,7 @@ it('registers dynamic image pre-render cron task and related helpers', function expect($content)->toContain('normalizeButtonsInput'); expect($content)->toContain('dynamic_image:'); expect($content)->toContain('machine_1'); + expect($content)->toContain('getSelfServeDynamicImageSizeModeForCron'); + expect($content)->toContain("'dynamic_image_size' => getSelfServeDynamicImageSizeModeForCron()"); + expect($content)->toContain('resizeToMaxWidth(DYNAMIC_IMAGE_RELEVANT_MAX_WIDTH)'); });