변경 파일 목록
총 6개
변경 파일 코드
추가삭제
변경 전DesignEditorAssetRepositoryStub.php
| <?php | |
| /* | |
| * Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved | |
| * | |
| * Unauthorized copying or redistribution of this file in source and binary forms via any medium | |
| * is strictly prohibited. | |
| */ | |
| namespace Bundle\Component\DesignEditor; | |
| /** | |
| * 디자인 에디터 에셋 저장소 Stub (테스트용) | |
| * | |
| * 단위 테스트를 위한 인메모리 구현체 | |
| * 실제 DB 스키마(es_designEditorAsset, es_designEditorFolder)와 동일한 필드 구조 사용 | |
| * | |
| * @author NHN godo | |
| */ | |
| class DesignEditorAssetRepositoryStub implements DesignEditorAssetRepositoryInterface | |
| { | |
| /** @var array 임시 에셋 데이터 */ | |
| protected array $assets = []; | |
| public function __construct() | |
| { | |
| $this->initializeStubData(); | |
| } | |
| /** | |
| * 임시 에셋 데이터 초기화 | |
| */ | |
| protected function initializeStubData(): void | |
| { | |
| $this->assets = [ | |
| [ | |
| 'assetSno' => 1, | |
| 'folderSno' => 1, | |
| 'skinSno' => 1, // JOIN 결과로 포함 | |
| 'folderSlug' => 'custom', // JOIN 결과로 포함 | |
| 'assetCode' => 'custom--test_page', | |
| 'assetTitle' => '테스트 페이지', | |
| 'assetType' => 'html', | |
| 'assetIcon' => null, | |
| 'assetContents' => json_encode([ | |
| 'version' => '1.0.0', | |
| 'page' => [ | |
| 'id' => 'custom-test-page', | |
| 'title' => '테스트 페이지', | |
| 'seo' => [ | |
| 'title' => '테스트 페이지 SEO 타이틀', | |
| 'description' => '테스트 페이지 설명입니다.', | |
| 'keywords' => '테스트, 페이지', | |
| ], | |
| 'sections' => [], | |
| ], | |
| ]), | |
| 'assetPath' => 'custom/test_page.html', | |
| 'pageUrl' => 'custom/test_page.php', | |
| 'deleteFl' => 'n', | |
| 'regDt' => date('Y-m-d H:i:s'), | |
| 'modDt' => null, | |
| ], | |
| [ | |
| 'assetSno' => 2, | |
| 'folderSno' => 1, | |
| 'skinSno' => 1, | |
| 'folderSlug' => 'custom', | |
| 'assetCode' => 'custom--event_page', | |
| 'assetTitle' => '이벤트 페이지', | |
| 'assetType' => 'html', | |
| 'assetIcon' => null, | |
| 'assetContents' => null, | |
| 'assetPath' => 'custom/event_page.html', | |
| 'pageUrl' => 'custom/event_page.php', | |
| 'deleteFl' => 'n', | |
| 'regDt' => date('Y-m-d H:i:s'), | |
| 'modDt' => null, | |
| ], | |
| ]; | |
| } | |
| /** | |
| * 커스텀 에셋 추가 (테스트용) | |
| * | |
| * @param array $asset | |
| */ | |
| public function addAsset(array $asset): void | |
| { | |
| $this->assets[] = $asset; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function findByPageUrl(int $skinSno, string $pageUrl): ?array | |
| { | |
| foreach ($this->assets as $asset) { | |
| if ($asset['skinSno'] === $skinSno | |
| && $asset['pageUrl'] === $pageUrl | |
| && $asset['deleteFl'] === 'n') { | |
| return $asset; | |
| } | |
| } | |
| return null; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function findByAssetCode(int $skinSno, string $assetCode): ?array | |
| { | |
| foreach ($this->assets as $asset) { | |
| if ($asset['skinSno'] === $skinSno | |
| && $asset['assetCode'] === $assetCode | |
| && $asset['deleteFl'] === 'n') { | |
| return $asset; | |
| } | |
| } | |
| return null; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function findAllBySkin(int $skinSno): array | |
| { | |
| return array_filter($this->assets, function ($asset) use ($skinSno) { | |
| return $asset['skinSno'] === $skinSno && $asset['deleteFl'] === 'n'; | |
| }); | |
| } | |
| } | |
변경 후DesignEditorAssetRepositoryStub.php
변경 전DesignEditorRouter.php
| return $this->repository->findByPageUrl($skinSno, $pageUrl); | |
| } | |
| /** | |
| * URI 정규화 | |
| * | |
변경 후DesignEditorRouter.php
| return $this->repository->findByPageUrl($skinSno, $pageUrl); | |
| } | |
| /** | |
| * PC(front) 스킨 기준으로 pageUrl 에셋 매칭 | |
| * | |
| * 요청 디바이스와 무관하게 운영중인 PC 스킨에서 조회한다. | |
| * 디자인 에디터 에셋은 반응형 스킨(skinDevice=front)에만 생성되므로, | |
| * 관리자 화면에서 페이지 존재 여부를 확인할 때 이 경로를 사용한다. | |
| * | |
| * 스킨 활성 여부(isDesignEditorSkinEnabled)는 확인하지 않는다. | |
| * 적응형 스킨도 es_designEditorSkin 에 등록되어 있어 판별력이 없고, | |
| * 실제 판별은 에셋 조회가 담당한다. | |
| * | |
| * @param string $pageUrl 페이지 URL (예: custom/my_page.php) | |
| * @return array|null 에셋 정보 또는 null (매칭 실패) | |
| */ | |
| public function matchFrontPageUrl(string $pageUrl): ?array | |
| { | |
| $skinSno = $this->skinResolver->getFrontSkinSno(); | |
| if (is_null($skinSno)) { | |
| return null; | |
| } | |
| return $this->repository->findByPageUrl($skinSno, $this->normalizeUri($pageUrl)); | |
| } | |
| /** | |
| * URI 정규화 | |
| * | |
변경 전DesignEditorSkinRepositoryInterface.php
변경 후DesignEditorSkinRepositoryInterface.php
| <?php | |
| /* | |
| * Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved | |
| * | |
| * Unauthorized copying or redistribution of this file in source and binary forms via any medium | |
| * is strictly prohibited. | |
| */ | |
| namespace Bundle\Component\DesignEditor; | |
| /** | |
| * 디자인 에디터 스킨 저장소 인터페이스 | |
| * | |
| * 스킨 리졸버가 사용하는 스킨 조회 인터페이스 | |
| * 실제 DB 구현체와 테스트용 Stub 구현체를 교체할 수 있도록 인터페이스로 분리 | |
| * | |
| * @author NHN godo | |
| */ | |
| interface DesignEditorSkinRepositoryInterface | |
| { | |
| /** | |
| * 스킨 코드·스킨 디바이스로 스킨 정보 조회 | |
| * | |
| * @param string $skinCode 스킨 코드 (design.skin 정책의 frontLive / mobileLive) | |
| * @param string $skinDevice 스킨 디바이스 (front | mobile) | |
| * @return array 스킨 정보 배열 (skinSno·skinCode·skinType·skinDevice·skinName 등), 없으면 빈 배열 | |
| */ | |
| public function findBySkinCodeAndSkinDevice(string $skinCode, string $skinDevice): array; | |
| } | |
변경 전DesignEditorSkinResolver.php
| namespace Bundle\Component\DesignEditor; | |
| use Origin\Model\Design\DesignEditorSkin; | |
| /** | |
| * 디자인 에디터 스킨 리졸버 | |
| * | |
| /** @var array|null 캐시된 디자인 에디터 스킨 정보 */ | |
| protected ?array $designEditorSkin = null; | |
| /** | |
| * 디자인 에디터 스킨 사용 여부 확인 | |
| * | |
| return $this->doGetCurrentSkinName(); | |
| } | |
| /** | |
| * 디자인 에디터 스킨 사용 여부 확인 (구현) | |
| * | |
| // 디바이스 타입 결정 | |
| $skinDevice = \Request::isMobile() ? 'mobile' : 'front'; | |
| // es_designEditorSkin 테이블에서 해당 스킨 조회 | |
| $skin = DesignEditorSkin::query() | |
| ->where('skinCode', $skinCode) | |
| ->where('skinDevice', $skinDevice) | |
| ->first(); | |
| if ($skin) { | |
| $this->designEditorSkin = $skin->toArray(); | |
| } | |
| return $this->designEditorSkin; | |
| return $this->skinConfig; | |
| } | |
| } | |
변경 후DesignEditorSkinResolver.php
| namespace Bundle\Component\DesignEditor; | |
| /** | |
| * 디자인 에디터 스킨 리졸버 | |
| * | |
| /** @var array|null 캐시된 디자인 에디터 스킨 정보 */ | |
| protected ?array $designEditorSkin = null; | |
| /** @var DesignEditorSkinRepositoryInterface 스킨 저장소 */ | |
| protected DesignEditorSkinRepositoryInterface $skinRepository; | |
| /** | |
| * @param DesignEditorSkinRepositoryInterface|null $skinRepository 스킨 저장소. 미주입(인자 없는 new - 튜닝 호환) 시 컨테이너에서 가져온다 | |
| */ | |
| public function __construct(?DesignEditorSkinRepositoryInterface $skinRepository = null) | |
| { | |
| $this->skinRepository = $skinRepository ?? \App::getInstance(DesignEditorSkinRepositoryInterface::class); | |
| } | |
| /** | |
| * 디자인 에디터 스킨 사용 여부 확인 | |
| * | |
| return $this->doGetCurrentSkinName(); | |
| } | |
| /** | |
| * PC(front) 디자인 에디터 스킨 번호 조회 | |
| * | |
| * 요청 디바이스와 무관하게 운영중인 PC 스킨을 기준으로 조회한다. | |
| * 관리자 화면처럼 프론트 요청 문맥이 아닌 곳에서 사용한다. | |
| * | |
| * @return int|null 스킨 일련번호 또는 null | |
| */ | |
| public function getFrontSkinSno(): ?int | |
| { | |
| return $this->doGetFrontSkinSno(); | |
| } | |
| /** | |
| * 디자인 에디터 스킨 사용 여부 확인 (구현) | |
| * | |
| // 디바이스 타입 결정 | |
| $skinDevice = \Request::isMobile() ? 'mobile' : 'front'; | |
| // es_designEditorSkin 테이블에서 해당 스킨 조회 - 스킨 번호가 있는 행만 유효한 스킨으로 본다 | |
| $skin = $this->skinRepository->findBySkinCodeAndSkinDevice($skinCode, $skinDevice); | |
| if (!empty($skin['skinSno'])) { | |
| $this->designEditorSkin = $skin; | |
| } | |
| return $this->designEditorSkin; | |
| return $this->skinConfig; | |
| } | |
| /** | |
| * PC(front) 스킨 번호 조회 (구현) | |
| * | |
| * @return int|null | |
| */ | |
| protected function doGetFrontSkinSno(): ?int | |
| { | |
| $skinCode = $this->getSkinConfig()['frontLive'] ?? null; | |
| if (empty($skinCode)) { | |
| return null; | |
| } | |
| $skin = $this->skinRepository->findBySkinCodeAndSkinDevice($skinCode, 'front'); | |
| return $skin['skinSno'] ?? null; | |
| } | |
| } | |
변경 전DesignEditorSkinResolverStub.php
| <?php | |
| /* | |
| * Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved | |
| * | |
| * Unauthorized copying or redistribution of this file in source and binary forms via any medium | |
| * is strictly prohibited. | |
| */ | |
| namespace Bundle\Component\DesignEditor; | |
| /** | |
| * 디자인 에디터 스킨 리졸버 Stub (테스트용) | |
| * | |
| * 단위 테스트를 위한 설정 가능한 구현체 | |
| */ | |
| class DesignEditorSkinResolverStub extends DesignEditorSkinResolver | |
| { | |
| /** | |
| * @param bool $enabled 디자인 에디터 스킨 활성화 여부 | |
| * @param int|null $skinSno 스킨 번호 | |
| * @param string|null $skinName 스킨명 | |
| */ | |
| public function __construct( | |
| protected bool $enabled = true, | |
| protected ?int $skinSno = 1, | |
| protected ?string $skinName = 'test_skin' | |
| ) { | |
| } | |
| /** | |
| * 활성화 여부 설정 | |
| * | |
| * @param bool $enabled | |
| */ | |
| public function setEnabled(bool $enabled): void | |
| { | |
| $this->enabled = $enabled; | |
| } | |
| /** | |
| * 스킨 번호 설정 | |
| * | |
| * @param int|null $skinSno | |
| */ | |
| public function setSkinSno(?int $skinSno): void | |
| { | |
| $this->skinSno = $skinSno; | |
| } | |
| /** | |
| * 스킨명 설정 | |
| * | |
| * @param string|null $skinName | |
| */ | |
| public function setSkinName(?string $skinName): void | |
| { | |
| $this->skinName = $skinName; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| protected function doIsDesignEditorSkinEnabled(): bool | |
| { | |
| return $this->enabled; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| protected function doGetCurrentSkinSno(): ?int | |
| { | |
| return $this->skinSno; | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| protected function doGetCurrentSkinName(): ?string | |
| { | |
| return $this->skinName; | |
| } | |
| } | |
변경 후DesignEditorSkinResolverStub.php
변경 전SeoTag.php
| namespace Bundle\Component\Policy; | |
| use App; | |
| use Component\Database\DBTableField; | |
| use Encryptor; | |
| use Framework\Utility\StringUtils; | |
| $realPath = gd_implode(DIRECTORY_SEPARATOR, [SYSSRCPATH, 'Bundle', 'Controller', $viewDirectory, ucfirst($_tmp['dirname']), (gd_implode("", array_map("ucfirst", explode("_", $_tmp['filename'])))) . 'Controller.php']); | |
| } | |
| } | |
| if (!is_file($realPath)) { | |
| return ['result' => false, 'code' => 'NOT FILE']; | |
| } | |
| if($etcPageFl === true){ | |
| } | |
| } | |
| /** | |
| * saveSeoTag | |
변경 후SeoTag.php
| namespace Bundle\Component\Policy; | |
| use App; | |
| use Bundle\Component\DesignEditor\DesignEditorRouter; | |
| use Component\Database\DBTableField; | |
| use Encryptor; | |
| use Framework\Utility\StringUtils; | |
| $realPath = gd_implode(DIRECTORY_SEPARATOR, [SYSSRCPATH, 'Bundle', 'Controller', $viewDirectory, ucfirst($_tmp['dirname']), (gd_implode("", array_map("ucfirst", explode("_", $_tmp['filename'])))) . 'Controller.php']); | |
| } | |
| } | |
| if (!is_file($realPath) && !$this->existsDesignEditorPage($arrData['path'], $arrData['deviceFl'])) { | |
| return ['result' => false, 'code' => 'NOT FILE']; | |
| } | |
| if($etcPageFl === true){ | |
| } | |
| } | |
| /** | |
| * 디자인 에디터로 생성된 페이지 존재 여부 | |
| * | |
| * 디자인 에디터 페이지는 컨트롤러 파일 없이 에셋 레코드로만 존재하므로 | |
| * 파일 기반 검증으로는 통과할 수 없다. 에셋은 반응형 스킨(skinDevice=front)에만 | |
| * 생성되므로 PC 쇼핑몰 등록에 한해 조회한다. | |
| * | |
| * 쿼리스트링이 붙은 경로는 제외한다. 기타 페이지 SEO 출력 조회는 REQUEST_URI | |
| * 원본과 비교하므로 기존에도 지원하지 않던 형태이며, 이번 변경으로 열리지 않게 한다. | |
| * | |
| * 조회 중 예외는 종전 판정(NOT FILE)으로 되돌린다. 이 분기는 is_file() 실패 뒤에만 | |
| * 실행되므로, 여기서 예외가 새면 기존에 "존재하지 않습니다" 였던 모든 입력이 | |
| * "처리 중 오류" 로 바뀐다. 런타임 라우팅(Application::dispatchDesignEditorPage)과 같은 방침이다. | |
| * | |
| * @param string $path 입력 페이지 경로 (예: promotion/test01.php) | |
| * @param string $deviceFl 디바이스 구분 (p: PC 쇼핑몰, m: 모바일 쇼핑몰) | |
| * | |
| * @return bool | |
| */ | |
| protected function existsDesignEditorPage(string $path, string $deviceFl): bool | |
| { | |
| if ($deviceFl !== 'p' || str_contains($path, '?')) { | |
| return false; | |
| } | |
| try { | |
| // 컨테이너 등록 키(ContainerDefinitionsOrigin)가 FQCN 이라 Bundle\ 접두사로 참조한다 - Application::dispatchDesignEditorPage 와 동일 | |
| $router = \App::getInstance(DesignEditorRouter::class); | |
| return $router->matchFrontPageUrl($path) !== null; | |
| } catch (\Throwable $e) { | |
| Logger::channel('designEditor')->warning(__METHOD__ . ' : 디자인 에디터 에셋 조회 실패', ['exception' => $e, 'path' => $path]); | |
| return false; | |
| } | |
| } | |
| /** | |
| * saveSeoTag | |