fix(core,basic): 검색 봇 화면 렌더러 패리티 결손 일괄 수정

일반 화면(React)과 봇 화면(PHP)은 같은 레이아웃을 각각 렌더하는데 봇
화면 쪽 지원 범위가 뒤처져, 확장이 교체한 본문이 통째로 빈 채 나갔다
(공개 이슈 ). 같은 결손 계열을 전수 조사해 함께 닫는다.

- 확장 주입 props 를 렌더 진입 시점에 해석해 자손 전체에 상속
 → 게시글·답글 원글·댓글·페이지·상품 설명 5개 지점 복구
- 사용자 작성 HTML 정화기 신설 — isHtml 판정에 따라 이스케이프/정화.
 미정화 상태에서 봇 경로로 저장형 스크립트가 실제 실행됐다
- 노드 키 패리티: conditions / type:iterator / responsive 매칭 규칙 /
 children 문자열 / text 우선순위 / $t:defer / 레이아웃 초기 상태
- 표현식: 단일 바인딩 판정 교체, ||·&& 를 값 반환(JS 시맨틱)으로,
 비교식이 raw 경로에서 null 이 되던 문제
- 데이터소스: 엔드포인트 표현식 전면 해석 + 레이아웃 8곳 선언 보강
- 봇 페이지 캐시 1회 전량 무효화 업그레이드 스텝

지원 범위 SSoT 표를 문서에 고정하고, 한쪽만 고치는 것을 막는 검사 룰과
레이아웃 전수 순회 테스트로 잠갔다.
This commit is contained in:
HeuJung
2026-07-27 10:03:06 +09:00
parent b505ac7ba7
commit 0dfb45951b
34 changed files with 5244 additions and 172 deletions
+25 -13
View File
@@ -15,12 +15,18 @@ class DataSourceResolver
* @param array $routeParams 라우트 파라미터 (예: ['id' => '123', 'slug' => 'clothes'])
* @param string $locale 로케일
* @param array $queryParams URL 쿼리 파라미터 (예: ['page' => '2', 'sort' => 'latest'])
* @param array $extraContext 엔드포인트/params 표현식 평가에 사용할 추가 컨텍스트 (_global 등)
* @return array data_source ID → 응답 데이터 매핑
*/
public function resolve(array $dataSources, array $seoDataSourceIds, array $routeParams, string $locale, array $queryParams = []): array
public function resolve(array $dataSources, array $seoDataSourceIds, array $routeParams, string $locale, array $queryParams = [], array $extraContext = []): array
{
$context = [];
$evalContext = array_merge($extraContext, [
'route' => array_merge($extraContext['route'] ?? [], $routeParams),
'query' => $queryParams,
]);
foreach ($dataSources as $ds) {
$dsId = $ds['id'] ?? '';
@@ -34,11 +40,11 @@ class DataSourceResolver
continue;
}
// 라우트 파라미터 치환 (예: {{route.id}} → 123)
$endpoint = $this->resolveEndpoint($endpoint, $routeParams);
// 엔드포인트 표현식 치환 ({{route.id}}, {{query.q}}, {{_global.xxx}})
$endpoint = $this->resolveEndpoint($endpoint, $evalContext);
// params 해석 ({{query.xxx}}, {{route.xxx}} 표현식 치환)
$resolvedParams = $this->resolveParams($ds['params'] ?? [], $routeParams, $queryParams);
$resolvedParams = $this->resolveParams($ds['params'] ?? [], $routeParams, $queryParams, $extraContext);
// API 호출
$data = $this->callApi($endpoint, $ds, $locale, $resolvedParams);
@@ -51,16 +57,21 @@ class DataSourceResolver
/**
* 엔드포인트의 표현식을 치환합니다.
*
* `{{route.xxx}}` 뿐 아니라 `{{query.xxx}}` / `{{_global.xxx}}` 등 컨텍스트 표현식을
* 모두 해석합니다 — 검색 결과처럼 엔드포인트 문자열 안에서 쿼리·전역 상태를 보간하는
* 데이터소스가 있기 때문입니다. 미해석 시 표현식이 URL에 그대로 남아 호출이 실패합니다.
*
* @param string $endpoint 원본 엔드포인트
* @param array $routeParams 라우트 파라미터
* @param array $evalContext 평가 컨텍스트 (route/query/_global 등)
* @return string 치환된 엔드포인트
*/
private function resolveEndpoint(string $endpoint, array $routeParams): string
private function resolveEndpoint(string $endpoint, array $evalContext): string
{
// {{route.xxx}} 및 {{route?.xxx}} 패턴 치환 (optional chaining 포함)
return (string) preg_replace_callback('/\{\{route\??\.(\w+)\}\}/', function ($matches) use ($routeParams) {
return $routeParams[$matches[1]] ?? '';
}, $endpoint);
if (! str_contains($endpoint, '{{')) {
return $endpoint;
}
return app(ExpressionEvaluator::class)->evaluate($endpoint, $evalContext);
}
/**
@@ -72,19 +83,20 @@ class DataSourceResolver
* @param array $params 원본 params 정의
* @param array $routeParams 라우트 파라미터
* @param array $queryParams 쿼리 파라미터
* @param array $extraContext 추가 평가 컨텍스트 (_global 등)
* @return array 해석된 params (키 → 값)
*/
private function resolveParams(array $params, array $routeParams, array $queryParams): array
private function resolveParams(array $params, array $routeParams, array $queryParams, array $extraContext = []): array
{
if (empty($params)) {
return [];
}
$resolved = [];
$evalContext = [
$evalContext = array_merge($extraContext, [
'route' => $routeParams,
'query' => $queryParams,
];
]);
$evaluator = app(ExpressionEvaluator::class);