feat(message_bizppurio): 알림 템플릿 편집 통합·검수 의견·관리탭 개선 및 언어팩 번역 보존 결함군 수정
비즈뿌리오 알림톡 UX 개편 후속: - 코어/게시판/이커머스 알림 템플릿 [편집] 모달에 확장 채널 편집 영역 (extension_point 2종 + hidden_template_editor 채널 메타)을 신설하고, 알림톡 템플릿·문자(SMS)·수신자 규칙을 한 창에서 통합 저장하도록 개정 (행 하단은 상태 요약 전용, 승인 여부 2단 배지) - 검수 신청에 검수자 전달 의견(comment) 동봉 — kapi request 로만 전달, 행에는 저장하지 않음 (FormRequest 신설 + API 문서 백필) - 관리 탭 모바일 카드뷰·도구줄 코어 관례 통일, 사이트맵 알림 정의 다국어 라벨 결측 수정 + 코어 정의 라벨 패리티 테스트 신설 언어팩 번역 소실·보존 결함군 (보완 실측 → 커밋 전 점검에서 연쇄 확정, 전부 실패 테스트 선행 후 수정): - [기본값 복원]이 활성 언어팩 번역(ja)을 영구 소실 → 복원 기본값에 시딩과 같은 주입기(SSoT)로 팩 로케일 병합 (notification·identity) - 코어 알림/본인인증 시더의 언어팩 주입 전면 불능 → 주입기가 연관/리스트 양형태 수용 + identity 시더의 config 복합 키 보존 - 실패한 언어팩 업데이트가 active 팩을 installed 로 방치 → 상태 복원 확장 - 병합·제거의 사용자 수정 보존 판정 사문(연관 전제 isset) → dot-path 판정 교정, 팩 주입 저장의 user_overrides 오염 차단(시딩 바인딩), 템플릿 2모델 translatableTrackableFields 선언(beta.4 설계 누락), 같은 프로세스 활성화 시 시더 번역 필터 stale(캡처 인스턴스 캐시) 교정 - 7.0.9 업그레이드 스텝 신설 — 활성 팩 seed 재동기화로 기설치본의 소실 번역 자동 복구 (멱등·운영자 수정 보존·팩별 실패 격리)
This commit is contained in:
1 parent
40f62e79aa
commit
e7fa1b5535
71 files changed
+7155
-8693
No files matched your search
@@ -41,29 +41,37 @@ class LanguagePackTranslationRepository implements LanguagePackTranslationReposi
|
||||
*/
|
||||
public function applySeedFromPack(LanguagePack $pack, array $seedBundle): array
|
||||
{
|
||||
$audit = [];
|
||||
$locale = $pack->locale;
|
||||
// 팩 주입은 시딩 컨텍스트다 — 바인딩 없이 저장하면 UserOverridesAwareBuilder 가
|
||||
// 주입 자체를 "사용자 수정"으로 오인해 user_overrides 를 오염시키고, 그 선언이
|
||||
// 이후 시더 재실행의 컬럼 갱신을 영구히 얼린다 (#597 에서 실측·교정).
|
||||
app()->instance('user_overrides.seeding', true);
|
||||
try {
|
||||
$audit = [];
|
||||
$locale = $pack->locale;
|
||||
|
||||
if (! empty($seedBundle['permissions'])) {
|
||||
$this->applyByIdentifier(Permission::class, $pack, $seedBundle['permissions'], $locale, ['name', 'description'], 'identifier', $audit);
|
||||
}
|
||||
if (! empty($seedBundle['roles'])) {
|
||||
$this->applyByIdentifier(Role::class, $pack, $seedBundle['roles'], $locale, ['name', 'description'], 'identifier', $audit);
|
||||
}
|
||||
if (! empty($seedBundle['menus']) && $pack->scope !== LanguagePackScope::Plugin->value) {
|
||||
$this->applyByIdentifier(Menu::class, $pack, $seedBundle['menus'], $locale, ['name'], 'slug', $audit);
|
||||
}
|
||||
if (! empty($seedBundle['notifications'])) {
|
||||
$this->applyNotifications($pack, $seedBundle['notifications'], $locale, $audit);
|
||||
}
|
||||
if (! empty($seedBundle['identity_messages'])) {
|
||||
$this->applyIdentityMessages($pack, $seedBundle['identity_messages'], $locale, $audit);
|
||||
}
|
||||
if (! empty($seedBundle['manifest'])) {
|
||||
$this->applyManifest($pack, $seedBundle['manifest'], $locale, $audit);
|
||||
}
|
||||
if (! empty($seedBundle['permissions'])) {
|
||||
$this->applyByIdentifier(Permission::class, $pack, $seedBundle['permissions'], $locale, ['name', 'description'], 'identifier', $audit);
|
||||
}
|
||||
if (! empty($seedBundle['roles'])) {
|
||||
$this->applyByIdentifier(Role::class, $pack, $seedBundle['roles'], $locale, ['name', 'description'], 'identifier', $audit);
|
||||
}
|
||||
if (! empty($seedBundle['menus']) && $pack->scope !== LanguagePackScope::Plugin->value) {
|
||||
$this->applyByIdentifier(Menu::class, $pack, $seedBundle['menus'], $locale, ['name'], 'slug', $audit);
|
||||
}
|
||||
if (! empty($seedBundle['notifications'])) {
|
||||
$this->applyNotifications($pack, $seedBundle['notifications'], $locale, $audit);
|
||||
}
|
||||
if (! empty($seedBundle['identity_messages'])) {
|
||||
$this->applyIdentityMessages($pack, $seedBundle['identity_messages'], $locale, $audit);
|
||||
}
|
||||
if (! empty($seedBundle['manifest'])) {
|
||||
$this->applyManifest($pack, $seedBundle['manifest'], $locale, $audit);
|
||||
}
|
||||
|
||||
return $audit;
|
||||
return $audit;
|
||||
} finally {
|
||||
app()->forgetInstance('user_overrides.seeding');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,6 +81,23 @@ class LanguagePackTranslationRepository implements LanguagePackTranslationReposi
|
||||
* @return array<int, array<string, mixed>> 감사 로그 항목
|
||||
*/
|
||||
public function stripLocaleFromPack(LanguagePack $pack): array
|
||||
{
|
||||
// applySeedFromPack 과 동일 — 제거도 시딩 컨텍스트 (user_overrides 오염 차단)
|
||||
app()->instance('user_overrides.seeding', true);
|
||||
try {
|
||||
return $this->stripLocaleFromPackBody($pack);
|
||||
} finally {
|
||||
app()->forgetInstance('user_overrides.seeding');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* stripLocaleFromPack 의 본문 (시딩 바인딩 하에서 실행).
|
||||
*
|
||||
* @param LanguagePack $pack 비활성화된 언어팩
|
||||
* @return array<int, array<string, mixed>> 감사 로그 항목
|
||||
*/
|
||||
private function stripLocaleFromPackBody(LanguagePack $pack): array
|
||||
{
|
||||
$audit = [];
|
||||
$locale = $pack->locale;
|
||||
@@ -402,7 +427,7 @@ class LanguagePackTranslationRepository implements LanguagePackTranslationReposi
|
||||
}
|
||||
|
||||
$hasLocaleKey = array_key_exists($locale, $current);
|
||||
$isOverridden = isset($overrides[$column]);
|
||||
$isOverridden = $this->isColumnLocaleOverridden($overrides, $column, $locale);
|
||||
|
||||
if ($hasLocaleKey && $isOverridden) {
|
||||
$audit[] = [
|
||||
@@ -413,6 +438,7 @@ class LanguagePackTranslationRepository implements LanguagePackTranslationReposi
|
||||
'locale' => $locale,
|
||||
'reason' => 'user_overrides',
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -443,7 +469,7 @@ class LanguagePackTranslationRepository implements LanguagePackTranslationReposi
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($overrides[$column])) {
|
||||
if ($this->isColumnLocaleOverridden($overrides, $column, $locale)) {
|
||||
$audit[] = [
|
||||
'action' => 'preserved',
|
||||
'table' => $row->getTable(),
|
||||
@@ -452,6 +478,7 @@ class LanguagePackTranslationRepository implements LanguagePackTranslationReposi
|
||||
'locale' => $locale,
|
||||
'reason' => 'user_overrides',
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -464,4 +491,27 @@ class LanguagePackTranslationRepository implements LanguagePackTranslationReposi
|
||||
$row->saveQuietly();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 해당 컬럼·로케일이 운영자 수정(user_overrides)으로 보존 대상인지 판정합니다.
|
||||
*
|
||||
* 운영자 수정의 실제 기록 형식은 dot-path 리스트(`"{column}.{locale}"` —
|
||||
* HasUserOverrides 의 updating 이벤트가 기록, beta.4 가 레거시 항목을 dot-path 로
|
||||
* 전환)이므로 그 형식만 본다. 종전의 `isset($overrides[$column])` 판정은 어떤
|
||||
* 생산자도 쓰지 않는 연관 형식을 전제해 항상 false 였고, 그 결과 활성화/비활성화가
|
||||
* 운영자 수정 번역을 오류 없이 덮어쓰거나 제거했다 (#597 에서 발견·교정).
|
||||
*
|
||||
* 컬럼 전체 항목(`"subject"` 등)은 의도적으로 보존 사유로 보지 않는다 — 그 형식은
|
||||
* 시더 재실행(syncFromUpgrade)의 컬럼 보호 선언이며, 팩 병합까지 차단하면 팩
|
||||
* 업데이트가 자기 로케일 번역을 갱신하지 못하게 된다 (종전에도 차단된 적 없음).
|
||||
*
|
||||
* @param array<int, string> $overrides user_overrides 리스트
|
||||
* @param string $column 다국어 JSON 컬럼명
|
||||
* @param string $locale 대상 로케일
|
||||
* @return bool 보존 대상 여부
|
||||
*/
|
||||
protected function isColumnLocaleOverridden(array $overrides, string $column, string $locale): bool
|
||||
{
|
||||
return in_array($column.'.'.$locale, $overrides, true);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user