[ [ 'title' => '启用品类', 'value' => (int)Db::name('catalog_categories')->where('is_enabled', 1)->count(), 'desc' => '当前前台可用的鉴定品类数量', ], [ 'title' => '启用品牌', 'value' => (int)Db::name('catalog_brands')->where('is_enabled', 1)->count(), 'desc' => '已配置并启用的品牌数量', ], ], ]); } public function categories(Request $request) { $rows = Db::name('catalog_categories') ->field([ 'id', 'name', 'code', 'sort_order', 'is_enabled', 'need_shipping', 'supported_service_types', ]) ->order('sort_order', 'asc') ->select() ->toArray(); $categoryVisuals = $this->categoryVisualMap($request); $templateSummaryMap = []; $appraisalTemplateSummaryMap = []; if ($rows) { $categoryIds = array_map(fn (array $item) => (int)$item['id'], $rows); $templateRows = Db::name('upload_templates') ->field(['id', 'scope_id']) ->where('scope_type', 'category') ->whereIn('scope_id', $categoryIds) ->where('is_enabled', 1) ->select() ->toArray(); $templateIds = array_map(fn (array $item) => (int)$item['id'], $templateRows); $itemCountMap = []; if ($templateIds) { $itemRows = Db::name('upload_template_items') ->fieldRaw('template_id, COUNT(*) AS item_count') ->whereIn('template_id', $templateIds) ->where('is_enabled', 1) ->group('template_id') ->select() ->toArray(); foreach ($itemRows as $item) { $itemCountMap[(int)$item['template_id']] = (int)$item['item_count']; } } foreach ($templateRows as $item) { $categoryId = (int)($item['scope_id'] ?? 0); if ($categoryId <= 0) { continue; } if (!isset($templateSummaryMap[$categoryId])) { $templateSummaryMap[$categoryId] = [ 'template_count' => 0, 'item_count' => 0, ]; } $templateSummaryMap[$categoryId]['template_count'] += 1; $templateSummaryMap[$categoryId]['item_count'] += $itemCountMap[(int)$item['id']] ?? 0; } $appraisalTemplateRows = Db::name('appraisal_templates') ->field(['id', 'scope_id', 'is_default']) ->where('scope_type', 'category') ->whereIn('scope_id', $categoryIds) ->where('is_enabled', 1) ->order('is_default', 'desc') ->order('id', 'desc') ->select() ->toArray(); $appraisalTemplateIds = array_map(fn (array $item) => (int)$item['id'], $appraisalTemplateRows); $pointCountMap = []; if ($appraisalTemplateIds) { $pointRows = Db::name('appraisal_template_key_points') ->fieldRaw('template_id, COUNT(*) AS point_count') ->whereIn('template_id', $appraisalTemplateIds) ->group('template_id') ->select() ->toArray(); foreach ($pointRows as $item) { $pointCountMap[(int)$item['template_id']] = (int)$item['point_count']; } } foreach ($appraisalTemplateRows as $item) { $categoryId = (int)($item['scope_id'] ?? 0); if ($categoryId <= 0) { continue; } if (isset($appraisalTemplateSummaryMap[$categoryId])) { continue; } if (!isset($appraisalTemplateSummaryMap[$categoryId])) { $appraisalTemplateSummaryMap[$categoryId] = [ 'template_count' => 0, 'point_count' => 0, ]; } $appraisalTemplateSummaryMap[$categoryId]['template_count'] = 1; $appraisalTemplateSummaryMap[$categoryId]['point_count'] += $pointCountMap[(int)$item['id']] ?? 0; } } $list = array_map(function (array $item) use ($templateSummaryMap, $appraisalTemplateSummaryMap, $categoryVisuals) { $summary = $templateSummaryMap[(int)$item['id']] ?? ['template_count' => 0, 'item_count' => 0]; $appraisalSummary = $appraisalTemplateSummaryMap[(int)$item['id']] ?? ['template_count' => 0, 'point_count' => 0]; $codeKey = $this->categoryMatchKey((string)$item['code']); $nameKey = $this->categoryMatchKey((string)$item['name']); return [ 'id' => (int)$item['id'], 'name' => $item['name'], 'code' => $item['code'], 'image_url' => $categoryVisuals['code:' . $codeKey] ?? $categoryVisuals['name:' . $nameKey] ?? '', 'sort_order' => (int)$item['sort_order'], 'is_enabled' => (bool)$item['is_enabled'], 'need_shipping' => (bool)$item['need_shipping'], 'supported_service_types' => $this->decodeJsonArray($item['supported_service_types'] ?? null), 'upload_template_count' => (int)$summary['template_count'], 'upload_template_item_count' => (int)$summary['item_count'], 'upload_template_summary' => (int)$summary['template_count'] > 0 ? sprintf('%d 套模板 / %d 项采集项', (int)$summary['template_count'], (int)$summary['item_count']) : '未配置模板', 'appraisal_template_count' => 1, 'appraisal_template_point_count' => (int)$appraisalSummary['point_count'], 'appraisal_template_summary' => sprintf('%d 个自定义鉴定项', (int)$appraisalSummary['point_count']), ]; }, $rows); return api_success(['list' => $list]); } public function uploadTemplates(Request $request) { $categoryId = (int)$request->input('category_id', 0); if ($categoryId <= 0) { return api_error('品类 ID 不能为空', 422); } $category = Db::name('catalog_categories')->where('id', $categoryId)->find(); if (!$category) { return api_error('品类不存在', 404); } $serviceProviders = $this->decodeJsonArray($category['supported_service_types'] ?? null); if (!$serviceProviders) { $serviceProviders = ['anxinyan']; } $existingRows = Db::name('upload_templates') ->where('scope_type', 'category') ->where('scope_id', $categoryId) ->whereIn('service_provider', $serviceProviders) ->order('id', 'desc') ->select() ->toArray(); $existingMap = []; foreach ($existingRows as $row) { $provider = (string)($row['service_provider'] ?? ''); if ($provider === '' || isset($existingMap[$provider])) { continue; } $existingMap[$provider] = $row; } $list = array_map(function (string $serviceProvider) use ($categoryId, $category, $existingMap, $request) { $existing = $existingMap[$serviceProvider] ?? null; $items = []; if ($existing) { $itemRows = Db::name('upload_template_items') ->where('template_id', (int)$existing['id']) ->order('sort_order', 'asc') ->order('id', 'asc') ->select() ->toArray(); $items = array_map(fn (array $item) => [ 'id' => (int)$item['id'], 'item_code' => (string)$item['item_code'], 'item_name' => (string)$item['item_name'], 'is_required' => (bool)$item['is_required'], 'guide_text' => (string)$item['guide_text'], 'sample_image_url' => $this->templateSampleImageService()->normalizeUrl((string)$item['sample_image_url'], $request), 'max_upload_count' => (int)$item['max_upload_count'], 'sort_order' => (int)$item['sort_order'], 'is_enabled' => (bool)$item['is_enabled'], ], $itemRows); } return [ 'id' => $existing ? (int)$existing['id'] : null, 'category_id' => $categoryId, 'category_name' => (string)$category['name'], 'service_provider' => $serviceProvider, 'service_provider_text' => $this->serviceProviderText($serviceProvider), 'name' => $existing['name'] ?? sprintf('%s-%s上传模板', (string)$category['name'], $this->serviceProviderText($serviceProvider)), 'code' => $existing['code'] ?? sprintf('upload_category_%d_%s', $categoryId, $serviceProvider), 'is_enabled' => $existing ? (bool)$existing['is_enabled'] : true, 'is_default' => $existing ? (bool)$existing['is_default'] : ($serviceProvider === 'anxinyan'), 'items' => $items, ]; }, $serviceProviders); return api_success([ 'category' => [ 'id' => $categoryId, 'name' => (string)$category['name'], 'code' => (string)$category['code'], ], 'list' => $list, ]); } public function saveUploadTemplates(Request $request) { $categoryId = (int)$request->input('category_id', 0); $templates = $request->input('templates', []); if ($categoryId <= 0) { return api_error('品类 ID 不能为空', 422); } if (!is_array($templates)) { return api_error('模板数据格式不正确', 422); } $category = Db::name('catalog_categories')->where('id', $categoryId)->find(); if (!$category) { return api_error('品类不存在', 404); } $serviceProviders = $this->decodeJsonArray($category['supported_service_types'] ?? null); if (!$serviceProviders) { $serviceProviders = ['anxinyan']; } $allowedProviders = array_fill_keys($serviceProviders, true); $now = date('Y-m-d H:i:s'); $defaultTemplateId = 0; $orphanSampleImageUrls = []; Db::startTrans(); try { foreach ($templates as $template) { if (!is_array($template)) { continue; } $serviceProvider = trim((string)($template['service_provider'] ?? '')); if ($serviceProvider === '' || !isset($allowedProviders[$serviceProvider])) { continue; } $templateId = (int)($template['id'] ?? 0); $exists = null; if ($templateId > 0) { $exists = Db::name('upload_templates')->where('id', $templateId)->find(); } if (!$exists) { $exists = Db::name('upload_templates') ->where('scope_type', 'category') ->where('scope_id', $categoryId) ->where('service_provider', $serviceProvider) ->order('id', 'desc') ->find(); } $payload = [ 'name' => trim((string)($template['name'] ?? '')) ?: sprintf('%s-%s上传模板', (string)$category['name'], $this->serviceProviderText($serviceProvider)), 'code' => trim((string)($template['code'] ?? '')) ?: sprintf('upload_category_%d_%s', $categoryId, $serviceProvider), 'scope_type' => 'category', 'scope_id' => $categoryId, 'service_provider' => $serviceProvider, 'is_default' => !empty($template['is_default']) ? 1 : 0, 'is_enabled' => array_key_exists('is_enabled', $template) ? (!empty($template['is_enabled']) ? 1 : 0) : 1, 'updated_at' => $now, ]; if ($exists) { Db::name('upload_templates')->where('id', (int)$exists['id'])->update($payload); $savedTemplateId = (int)$exists['id']; } else { $payload['created_at'] = $now; $savedTemplateId = (int)Db::name('upload_templates')->insertGetId($payload); } if ($serviceProvider === 'anxinyan') { $defaultTemplateId = $savedTemplateId; } $existingItemRows = Db::name('upload_template_items') ->where('template_id', $savedTemplateId) ->select() ->toArray(); $existingSampleUrls = array_values(array_filter(array_map( fn (array $item) => $this->templateSampleImageService()->storagePath((string)($item['sample_image_url'] ?? '')), $existingItemRows ))); Db::name('upload_template_items')->where('template_id', $savedTemplateId)->delete(); $items = is_array($template['items'] ?? null) ? $template['items'] : []; $insertRows = []; $nextSampleUrls = []; foreach ($items as $index => $item) { if (!is_array($item)) { continue; } $itemCode = trim((string)($item['item_code'] ?? '')); $itemName = trim((string)($item['item_name'] ?? '')); if ($itemCode === '' || $itemName === '') { continue; } $insertRows[] = [ 'template_id' => $savedTemplateId, 'item_code' => $itemCode, 'item_name' => $itemName, 'is_required' => !empty($item['is_required']) ? 1 : 0, 'guide_text' => trim((string)($item['guide_text'] ?? '')), 'sample_image_url' => $this->templateSampleImageService()->storagePath((string)($item['sample_image_url'] ?? '')), 'max_upload_count' => max(1, (int)($item['max_upload_count'] ?? 1)), 'sort_order' => (int)($item['sort_order'] ?? (($index + 1) * 10)), 'is_enabled' => array_key_exists('is_enabled', $item) ? (!empty($item['is_enabled']) ? 1 : 0) : 1, 'created_at' => $now, 'updated_at' => $now, ]; $sampleUrl = $this->templateSampleImageService()->storagePath((string)($item['sample_image_url'] ?? '')); if ($sampleUrl !== '') { $nextSampleUrls[] = $sampleUrl; } } if ($insertRows) { Db::name('upload_template_items')->insertAll($insertRows); } $removedSampleUrls = array_values(array_diff($existingSampleUrls, $nextSampleUrls)); if ($removedSampleUrls) { $orphanSampleImageUrls = array_values(array_unique(array_merge($orphanSampleImageUrls, $removedSampleUrls))); } } Db::name('catalog_categories')->where('id', $categoryId)->update([ 'default_upload_template_id' => $defaultTemplateId > 0 ? $defaultTemplateId : null, 'updated_at' => $now, ]); Db::commit(); } catch (\Throwable $e) { Db::rollback(); return api_error('上传模板保存失败', 500, [ 'detail' => $e->getMessage(), ]); } foreach ($orphanSampleImageUrls as $fileUrl) { $this->templateSampleImageService()->delete($fileUrl); } return api_success([ 'category_id' => $categoryId, ], '上传模板已保存'); } public function appraisalTemplates(Request $request) { $categoryId = (int)$request->input('category_id', 0); if ($categoryId <= 0) { return api_error('品类 ID 不能为空', 422); } $category = Db::name('catalog_categories')->where('id', $categoryId)->find(); if (!$category) { return api_error('品类不存在', 404); } $template = Db::name('appraisal_templates') ->where('scope_type', 'category') ->where('scope_id', $categoryId) ->where('is_enabled', 1) ->order('is_default', 'desc') ->order('id', 'desc') ->find(); $points = []; if ($template) { $pointRows = Db::name('appraisal_template_key_points') ->where('template_id', (int)$template['id']) ->order('sort_order', 'asc') ->order('id', 'asc') ->select() ->toArray(); $points = array_map(fn (array $item) => [ 'id' => (int)$item['id'], 'point_code' => (string)$item['point_code'], 'point_name' => (string)$item['point_name'], 'point_type' => (string)$item['point_type'], 'options' => $this->decodeJsonArray($item['options_json'] ?? null), 'sort_order' => (int)$item['sort_order'], 'is_required' => (bool)$item['is_required'], ], $pointRows); } $payload = [ 'id' => $template ? (int)$template['id'] : null, 'category_id' => $categoryId, 'category_name' => (string)$category['name'], 'service_provider' => 'category', 'service_provider_text' => '通用品类模板', 'name' => $template['name'] ?? sprintf('%s鉴定模板', (string)$category['name']), 'code' => $template['code'] ?? sprintf('appraisal_category_%d', $categoryId), 'is_enabled' => true, 'is_default' => true, 'result_options' => [], 'condition_options' => [], 'valuation_hint' => '', 'key_points' => $points, ]; return api_success([ 'category' => [ 'id' => $categoryId, 'name' => (string)$category['name'], 'code' => (string)$category['code'], ], 'template' => $payload, 'list' => [$payload], ]); } public function saveAppraisalTemplates(Request $request) { $categoryId = (int)$request->input('category_id', 0); $template = $request->input('template', null); $templates = $request->input('templates', []); if ($categoryId <= 0) { return api_error('品类 ID 不能为空', 422); } if (!is_array($template)) { $template = is_array($templates) ? ($templates[0] ?? []) : []; } if (!is_array($template)) { return api_error('模板数据格式不正确', 422); } $category = Db::name('catalog_categories')->where('id', $categoryId)->find(); if (!$category) { return api_error('品类不存在', 404); } $now = date('Y-m-d H:i:s'); Db::startTrans(); try { $exists = Db::name('appraisal_templates') ->where('scope_type', 'category') ->where('scope_id', $categoryId) ->order('is_default', 'desc') ->order('id', 'desc') ->find(); $payload = [ 'name' => sprintf('%s鉴定模板', (string)$category['name']), 'code' => sprintf('appraisal_category_%d', $categoryId), 'scope_type' => 'category', 'scope_id' => $categoryId, 'service_provider' => 'category', 'is_default' => 1, 'is_enabled' => 1, 'result_options_json' => json_encode([], JSON_UNESCAPED_UNICODE), 'condition_rule_json' => json_encode([], JSON_UNESCAPED_UNICODE), 'valuation_rule_json' => json_encode([], JSON_UNESCAPED_UNICODE), 'updated_at' => $now, ]; if ($exists) { Db::name('appraisal_templates')->where('id', (int)$exists['id'])->update($payload); $savedTemplateId = (int)$exists['id']; } else { $payload['created_at'] = $now; $savedTemplateId = (int)Db::name('appraisal_templates')->insertGetId($payload); } $otherTemplateIds = Db::name('appraisal_templates') ->where('scope_type', 'category') ->where('scope_id', $categoryId) ->where('id', '<>', $savedTemplateId) ->column('id'); if ($otherTemplateIds) { Db::name('appraisal_templates')->whereIn('id', $otherTemplateIds)->update([ 'is_enabled' => 0, 'is_default' => 0, 'updated_at' => $now, ]); } Db::name('appraisal_template_key_points')->where('template_id', $savedTemplateId)->delete(); $points = is_array($template['key_points'] ?? null) ? $template['key_points'] : []; $insertRows = []; foreach ($points as $index => $point) { if (!is_array($point)) { continue; } $pointName = trim((string)($point['point_name'] ?? '')); if ($pointName === '') { continue; } $pointCode = $this->normalizeCode((string)($point['point_code'] ?? '')) ?: sprintf('point_%d', $index + 1); $pointType = trim((string)($point['point_type'] ?? 'text')); if (!in_array($pointType, ['text', 'textarea', 'select', 'boolean'], true)) { $pointType = 'text'; } $insertRows[] = [ 'template_id' => $savedTemplateId, 'point_code' => $pointCode, 'point_name' => $pointName, 'point_type' => $pointType, 'options_json' => json_encode($this->normalizeArray($point['options'] ?? []), JSON_UNESCAPED_UNICODE), 'sort_order' => (int)($point['sort_order'] ?? (($index + 1) * 10)), 'is_required' => !empty($point['is_required']) ? 1 : 0, 'created_at' => $now, 'updated_at' => $now, ]; } if ($insertRows) { Db::name('appraisal_template_key_points')->insertAll($insertRows); } Db::name('catalog_categories')->where('id', $categoryId)->update([ 'default_appraisal_template_id' => $savedTemplateId, 'updated_at' => $now, ]); Db::commit(); } catch (\Throwable $e) { Db::rollback(); return api_error('鉴定模板保存失败', 500, [ 'detail' => $e->getMessage(), ]); } return api_success([ 'category_id' => $categoryId, ], '鉴定模板已保存'); } public function uploadTemplateSampleImage(Request $request) { try { $asset = $this->templateSampleImageService()->upload($request); return api_success($asset, '示例图上传成功'); } catch (\Throwable $e) { return api_error($e->getMessage(), 422); } } public function deleteUploadTemplateSampleImage(Request $request) { $fileUrl = trim((string)$request->input('file_url', '')); if ($fileUrl === '') { return api_error('文件地址不能为空', 422); } $this->templateSampleImageService()->delete($fileUrl); return api_success([ 'file_url' => $fileUrl, ], '示例图已删除'); } public function saveCategory(Request $request) { $id = (int)$request->input('id', 0); $name = trim((string)$request->input('name', '')); $code = trim((string)$request->input('code', '')); $imageUrl = trim((string)$request->input('image_url', '')); if ($name === '' || $code === '') { return api_error('品类名称和编码不能为空', 422); } $previous = $id > 0 ? Db::name('catalog_categories')->field(['name', 'code'])->where('id', $id)->find() : null; $payload = [ 'name' => $name, 'code' => $code, 'sort_order' => (int)$request->input('sort_order', 0), 'is_enabled' => $request->input('is_enabled', true) ? 1 : 0, 'need_shipping' => $request->input('need_shipping', true) ? 1 : 0, 'supported_service_types' => json_encode($this->normalizeArray($request->input('supported_service_types', [])), JSON_UNESCAPED_UNICODE), 'updated_at' => date('Y-m-d H:i:s'), ]; if ($id > 0) { Db::name('catalog_categories')->where('id', $id)->update($payload); $this->saveCategoryVisual($name, $code, $imageUrl, is_array($previous) ? $previous : null); return api_success(['id' => $id], '更新成功'); } $payload['created_at'] = date('Y-m-d H:i:s'); $newId = Db::name('catalog_categories')->insertGetId($payload); $this->saveCategoryVisual($name, $code, $imageUrl); return api_success(['id' => (int)$newId], '创建成功'); } public function brands(Request $request) { $rows = Db::name('catalog_brands') ->alias('b') ->leftJoin('catalog_brand_categories cbc', 'cbc.brand_id = b.id') ->leftJoin('catalog_categories c', 'c.id = cbc.category_id') ->field([ 'b.id', 'b.name', 'b.en_name', 'b.code', 'b.sort_order', 'b.is_enabled', 'b.supported_service_types', 'GROUP_CONCAT(DISTINCT cbc.category_id) AS category_ids', 'GROUP_CONCAT(DISTINCT c.name) AS category_names', ]) ->group('b.id') ->order('b.sort_order', 'asc') ->select() ->toArray(); $list = array_map(function (array $item) { return [ 'id' => (int)$item['id'], 'name' => $item['name'], 'en_name' => $item['en_name'], 'code' => $item['code'], 'sort_order' => (int)$item['sort_order'], 'is_enabled' => (bool)$item['is_enabled'], 'category_ids' => $this->decodeIntList($item['category_ids'] ?? ''), 'category_names' => $item['category_names'] ?: '', 'supported_service_types' => $this->decodeJsonArray($item['supported_service_types'] ?? null), ]; }, $rows); return api_success(['list' => $list]); } public function saveBrand(Request $request) { $id = (int)$request->input('id', 0); $name = trim((string)$request->input('name', '')); $enName = trim((string)$request->input('en_name', '')); $code = trim((string)$request->input('code', '')); $categoryIds = $this->normalizeIntArray($request->input('category_ids', [])); if ($name === '' || $code === '') { return api_error('品牌名称和编码不能为空', 422); } $payload = [ 'name' => $name, 'en_name' => $enName, 'code' => $code, 'sort_order' => (int)$request->input('sort_order', 0), 'is_enabled' => $request->input('is_enabled', true) ? 1 : 0, 'supported_service_types' => json_encode($this->normalizeArray($request->input('supported_service_types', [])), JSON_UNESCAPED_UNICODE), 'updated_at' => date('Y-m-d H:i:s'), ]; Db::startTrans(); try { if ($id > 0) { Db::name('catalog_brands')->where('id', $id)->update($payload); Db::name('catalog_brand_categories')->where('brand_id', $id)->delete(); foreach ($categoryIds as $categoryId) { Db::name('catalog_brand_categories')->insert([ 'brand_id' => $id, 'category_id' => $categoryId, 'created_at' => date('Y-m-d H:i:s'), ]); } Db::commit(); return api_success(['id' => $id], '更新成功'); } $payload['logo'] = ''; $payload['created_at'] = date('Y-m-d H:i:s'); $newId = Db::name('catalog_brands')->insertGetId($payload); foreach ($categoryIds as $categoryId) { Db::name('catalog_brand_categories')->insert([ 'brand_id' => $newId, 'category_id' => $categoryId, 'created_at' => date('Y-m-d H:i:s'), ]); } Db::commit(); return api_success(['id' => (int)$newId], '创建成功'); } catch (\Throwable $e) { Db::rollback(); return api_error('品牌保存失败', 500, [ 'detail' => $e->getMessage(), ]); } } private function decodeJsonArray(mixed $value): array { if (is_array($value)) { return array_values($value); } if (is_string($value) && $value !== '') { $decoded = json_decode($value, true); return is_array($decoded) ? array_values($decoded) : []; } return []; } private function decodeJsonObject(mixed $value): array { if (is_array($value)) { return $value; } if (is_string($value) && $value !== '') { $decoded = json_decode($value, true); return is_array($decoded) ? $decoded : []; } return []; } private function normalizeArray(mixed $value): array { if (is_array($value)) { return array_values(array_filter(array_map(static fn ($item) => trim((string)$item), $value), static fn ($item) => $item !== '')); } if (is_string($value) && $value !== '') { return array_values(array_filter(array_map('trim', explode(',', $value)), static fn ($item) => $item !== '')); } return []; } private function normalizeCode(string $value): string { $normalized = strtolower(trim($value)); $normalized = (string)preg_replace('/[^a-z0-9_]+/', '_', $normalized); $normalized = trim($normalized, '_'); return $normalized; } private function normalizeIntArray(mixed $value): array { if (!is_array($value)) { return []; } return array_values(array_filter(array_map(static fn ($item) => (int)$item, $value), static fn ($item) => $item > 0)); } private function decodeIntList(string $value): array { if ($value === '') { return []; } return array_values(array_filter(array_map(static fn ($item) => (int)$item, explode(',', $value)), static fn ($item) => $item > 0)); } private function serviceProviderText(string $serviceProvider): string { return $serviceProvider === 'zhongjian' ? '中检鉴定' : '实物鉴定'; } private function categoryVisualMap(Request $request): array { $items = (new ContentService())->getHomeConfig()['category_visuals'] ?? []; if (!is_array($items)) { return []; } $map = []; $storage = new FileStorageService(); foreach ($items as $item) { if (!is_array($item)) { continue; } $imageUrl = trim((string)($item['image_url'] ?? '')); if ($imageUrl === '') { continue; } $imageUrl = $storage->normalizeUrl($imageUrl, $request); $categoryCode = $this->categoryMatchKey((string)($item['category_code'] ?? '')); if ($categoryCode !== '') { $map['code:' . $categoryCode] = $imageUrl; } $categoryName = $this->categoryMatchKey((string)($item['category_name'] ?? '')); if ($categoryName !== '') { $map['name:' . $categoryName] = $imageUrl; } } return $map; } private function saveCategoryVisual(string $categoryName, string $categoryCode, string $imageUrl, ?array $previous = null): void { $contentService = new ContentService(); $homeConfig = $contentService->getHomeConfig(); $items = is_array($homeConfig['category_visuals'] ?? null) ? $homeConfig['category_visuals'] : []; $removeKeys = [ 'code:' . $this->categoryMatchKey($categoryCode), 'name:' . $this->categoryMatchKey($categoryName), ]; if ($previous) { $removeKeys[] = 'code:' . $this->categoryMatchKey((string)($previous['code'] ?? '')); $removeKeys[] = 'name:' . $this->categoryMatchKey((string)($previous['name'] ?? '')); } $removeKeys = array_values(array_filter(array_unique($removeKeys), static fn ($key) => !str_ends_with($key, ':'))); $nextItems = []; foreach ($items as $item) { if (!is_array($item)) { continue; } $itemKeys = [ 'code:' . $this->categoryMatchKey((string)($item['category_code'] ?? '')), 'name:' . $this->categoryMatchKey((string)($item['category_name'] ?? '')), ]; if (array_intersect($removeKeys, $itemKeys)) { continue; } $nextItems[] = $item; } $nextItems[] = [ 'category_name' => $categoryName, 'category_code' => $categoryCode, 'image_url' => $imageUrl, ]; $homeConfig['category_visuals'] = $nextItems; $contentService->saveHomeConfig($homeConfig); } private function categoryMatchKey(string $value): string { $value = trim($value); $normalized = preg_replace('/[\s\p{Cf}]+/u', '', $value); return strtolower($normalized ?? $value); } private function templateSampleImageService(): CatalogTemplateSampleImageService { return new CatalogTemplateSampleImageService(); } }