fix(settings): never plant deploy seed defaults on restore-from-backup
Ignore limangold/payping seed URLs from backup or superadmin when filling; keep any real local value untouched unless force-overwrite. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -13,12 +13,10 @@ use Illuminate\Support\Facades\Schema;
|
||||
* بازگرداندن مقادیر تنظیمات can_edit=0 از جدول بکاپ (پیشفرض settings_copy1)
|
||||
* به جدول settings — بعد از اینکه مالکیت این کلیدها از سوپرادمین به همین نصب برگشت.
|
||||
*
|
||||
* قاعده سخت: اگر ردیف لوکال value غیرخالی دارد، هرگز عوض نمیشود
|
||||
* (حتی با --overwrite؛ برای اجبار از --force-overwrite استفاده کن).
|
||||
*
|
||||
* پر کردن فقط برای ردیف غایب یا value خالی:
|
||||
* 1) اگر --fill-from-superadmin و سوپرادمین مقدار دارد → همان
|
||||
* 2) وگرنه مقدار بکاپ
|
||||
* قواعد سخت:
|
||||
* 1) value غیرخالی لوکال هرگز عوض نمیشود (مگر --force-overwrite).
|
||||
* 2) پیشفرضهای seed/deploy (مثل limangold.ir) هرگز بهعنوان مقدار واقعی نوشته نمیشوند.
|
||||
* 3) با --fill-from-superadmin برای خالی/غایب: اول SA واقعی، بعد بکاپ واقعی.
|
||||
*
|
||||
* کلیدهای گروه «دسترسی مشتری به قابلیتها» و runtime رد میشوند.
|
||||
*/
|
||||
@@ -32,10 +30,34 @@ class SettingsRestoreFromBackupCommand extends Command
|
||||
|
||||
protected $description = 'بازگرداندن مقادیر تنظیمات پیشرفته از جدول بکاپ به settings (بدون دست زدن به value موجود)';
|
||||
|
||||
/** پیشفرضهای deploy که در بکاپ ماندهاند و نباید بر مقدار واقعی سوپرادمین بچربند */
|
||||
private const DEPLOY_DEFAULT_VALUES = [
|
||||
/**
|
||||
* پیشفرضهای INSERT در deploy_settings_const — اگر فقط همینها در بکاپ/SA باشند،
|
||||
* نوشته نمیشوند (خالی میماند تا مقدار واقعی پروژه از بین نرود).
|
||||
*
|
||||
* @var array<string, list<string>>
|
||||
*/
|
||||
private const KEY_SEED_DEFAULTS = [
|
||||
'montzar_base_url' => [
|
||||
'https://limangold.ir',
|
||||
'http://limangold.ir',
|
||||
],
|
||||
'payping_fin_url' => [
|
||||
'https://limangold.ir/kernel/admin/fin',
|
||||
],
|
||||
'payping_start_url' => [
|
||||
'https://payping.limangold.ir/payment/start',
|
||||
],
|
||||
'payping_verify_url' => [
|
||||
'https://payping.limangold.ir/payment/verify',
|
||||
],
|
||||
];
|
||||
|
||||
/** دامنه/مقدارهایی که در هر کلیدی seed محسوب میشوند */
|
||||
private const GLOBAL_SEED_MARKERS = [
|
||||
'https://limangold.ir',
|
||||
'http://limangold.ir',
|
||||
'https://payping.limangold.ir',
|
||||
'http://payping.limangold.ir',
|
||||
];
|
||||
|
||||
public function handle(): int
|
||||
@@ -77,18 +99,29 @@ class SettingsRestoreFromBackupCommand extends Command
|
||||
|
||||
$hasType = Setting::hasTypeColumn();
|
||||
$report = [];
|
||||
$counts = ['inserted' => 0, 'updated' => 0, 'skipped' => 0, 'kept' => 0];
|
||||
$counts = [
|
||||
'inserted' => 0,
|
||||
'updated' => 0,
|
||||
'skipped' => 0,
|
||||
'kept' => 0,
|
||||
'seed_ignored' => 0,
|
||||
];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$backup = $backupRows[$key] ?? null;
|
||||
$local = $localRows->get($key);
|
||||
[$value, $source] = $this->resolveFillValue(
|
||||
[$value, $source, $ignoredSeed] = $this->resolveFillValue(
|
||||
$key,
|
||||
$backup['value'] ?? null,
|
||||
$superAdminValues[$key] ?? null,
|
||||
$fillFromSuperAdmin
|
||||
);
|
||||
if ($ignoredSeed) {
|
||||
$counts['seed_ignored']++;
|
||||
}
|
||||
|
||||
if (! $local) {
|
||||
// ردیف غایب: فقط اگر مقدار واقعی داریم value میگذاریم؛ وگرنه خالی
|
||||
$counts['inserted']++;
|
||||
$report[] = [$key, '—', $this->mask($key, $value), 'INSERT '.($source ?: 'empty')];
|
||||
if (! $dryRun) {
|
||||
@@ -109,30 +142,38 @@ class SettingsRestoreFromBackupCommand extends Command
|
||||
}
|
||||
|
||||
$localValue = trim((string) ($local->value ?? ''));
|
||||
$localIsDeployDefault = $this->isDeployDefault($localValue);
|
||||
$localIsSeed = $localValue !== '' && $this->isSeedDefault($key, $localValue);
|
||||
$saReal = isset($superAdminValues[$key]) && ! $this->isSeedDefault($key, $superAdminValues[$key])
|
||||
? $superAdminValues[$key]
|
||||
: null;
|
||||
|
||||
// قاعده سخت: value واقعی موجود دست نخوره
|
||||
// استثنا: لوکال فقط پیشفرض deploy (مثل limangold.ir) است و SA مقدار واقعی دارد
|
||||
$allowReplaceDefault = $fillFromSuperAdmin
|
||||
&& $localIsDeployDefault
|
||||
&& isset($superAdminValues[$key])
|
||||
&& ! $this->isDeployDefault($superAdminValues[$key]);
|
||||
// value واقعی موجود: دست نخوره
|
||||
// استثنا: لوکال فقط seed است و SA مقدار واقعی دارد → اصلاح اشتباه قبلی
|
||||
$allowReplaceSeed = $fillFromSuperAdmin && $localIsSeed && $saReal !== null;
|
||||
|
||||
if ($localValue !== '' && ! $forceOverwrite && ! $allowReplaceDefault) {
|
||||
if ($localValue !== '' && ! $forceOverwrite && ! $allowReplaceSeed) {
|
||||
$counts['kept']++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($allowReplaceSeed) {
|
||||
$value = $saReal;
|
||||
$source = 'superadmin';
|
||||
}
|
||||
|
||||
$writeValue = $value !== null && $value !== ''
|
||||
&& ! $this->isSeedDefault($key, $value)
|
||||
&& (
|
||||
$localValue === ''
|
||||
|| $allowReplaceDefault
|
||||
|| $allowReplaceSeed
|
||||
|| ($forceOverwrite && $localValue !== (string) $value)
|
||||
);
|
||||
|
||||
// فقط meta — و فقط وقتی value را عوض نمیکنیم و لوکال seed/خالی نیست
|
||||
// برای ردیف با value واقعی، meta هم دست نمیزنیم (جلوگیری از side-effect)
|
||||
$metaChanged = false;
|
||||
if ($backup) {
|
||||
if ($backup && ($localValue === '' || $allowReplaceSeed || $forceOverwrite)) {
|
||||
$metaChanged = $this->metaDiffers($local, $backup, $hasType);
|
||||
}
|
||||
|
||||
@@ -157,7 +198,7 @@ class SettingsRestoreFromBackupCommand extends Command
|
||||
if ($writeValue) {
|
||||
$local->value = $value;
|
||||
}
|
||||
if ($backup) {
|
||||
if ($backup && $metaChanged) {
|
||||
$local->title_fa = $backup['title_fa'] ?: ($local->title_fa ?: $key);
|
||||
if (($backup['description'] ?? '') !== '') {
|
||||
$local->description = $backup['description'];
|
||||
@@ -174,16 +215,17 @@ class SettingsRestoreFromBackupCommand extends Command
|
||||
}
|
||||
|
||||
$this->info(sprintf(
|
||||
'%sinsert=%d update=%d kept=%d skip=%d (backup=%s, superadmin=%d)',
|
||||
'%sinsert=%d update=%d kept=%d skip=%d seed_ignored=%d (backup=%s, superadmin=%d)',
|
||||
$dryRun ? '[dry-run] ' : '',
|
||||
$counts['inserted'],
|
||||
$counts['updated'],
|
||||
$counts['kept'],
|
||||
$counts['skipped'],
|
||||
$counts['seed_ignored'],
|
||||
$table,
|
||||
count($superAdminValues)
|
||||
));
|
||||
$this->comment('kept = ردیفهایی که value غیرخالی داشتند و دست نخورده ماندند.');
|
||||
$this->comment('kept = value واقعی لوکال دست نخورده | seed_ignored = پیشفرض limangold/... از منبع پر کردن حذف شد');
|
||||
|
||||
if ($dryRun) {
|
||||
$this->comment('هیچ چیزی نوشته نشد. برای اجرا همین دستور را بدون --dry-run بزن.');
|
||||
@@ -193,45 +235,72 @@ class SettingsRestoreFromBackupCommand extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* برای پر کردن خالی/غایب: با --fill-from-superadmin اول SA، بعد بکاپ.
|
||||
*
|
||||
* @return array{0: ?string, 1: string}
|
||||
* @return array{0: ?string, 1: string, 2: bool} [value, source, ignoredSeed]
|
||||
*/
|
||||
private function resolveFillValue(?string $backupValue, ?string $superAdminValue, bool $preferSuperAdmin): array
|
||||
{
|
||||
$backupValue = $backupValue === null ? null : trim($backupValue);
|
||||
$superAdminValue = $superAdminValue === null ? null : trim($superAdminValue);
|
||||
private function resolveFillValue(
|
||||
string $key,
|
||||
?string $backupRaw,
|
||||
?string $superAdminRaw,
|
||||
bool $preferSuperAdmin
|
||||
): array {
|
||||
$backupWasSeed = $backupRaw !== null && trim($backupRaw) !== '' && $this->isSeedDefault($key, trim($backupRaw));
|
||||
$saWasSeed = $superAdminRaw !== null && trim($superAdminRaw) !== '' && $this->isSeedDefault($key, trim($superAdminRaw));
|
||||
$ignoredSeed = $backupWasSeed || $saWasSeed;
|
||||
|
||||
$backupValue = $this->nullIfSeed($key, $backupRaw);
|
||||
$superAdminValue = $this->nullIfSeed($key, $superAdminRaw);
|
||||
|
||||
if ($preferSuperAdmin && $superAdminValue !== null && $superAdminValue !== '') {
|
||||
return [$superAdminValue, 'superadmin'];
|
||||
return [$superAdminValue, 'superadmin', $ignoredSeed];
|
||||
}
|
||||
|
||||
if ($backupValue !== null && $backupValue !== '') {
|
||||
return [$backupValue, 'backup'];
|
||||
return [$backupValue, 'backup', $ignoredSeed];
|
||||
}
|
||||
|
||||
return [null, ''];
|
||||
return [null, '', $ignoredSeed];
|
||||
}
|
||||
|
||||
private function isDeployDefault(string $value): bool
|
||||
private function nullIfSeed(string $key, ?string $value): ?string
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
$value = trim($value);
|
||||
if ($value === '') {
|
||||
return null;
|
||||
}
|
||||
if ($this->isSeedDefault($key, $value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function isSeedDefault(string $key, string $value): bool
|
||||
{
|
||||
$normalized = rtrim(strtolower(trim($value)), '/');
|
||||
if ($normalized === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (self::DEPLOY_DEFAULT_VALUES as $default) {
|
||||
foreach (self::KEY_SEED_DEFAULTS[$key] ?? [] as $default) {
|
||||
if ($normalized === rtrim(strtolower($default), '/')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (self::GLOBAL_SEED_MARKERS as $marker) {
|
||||
$m = rtrim(strtolower($marker), '/');
|
||||
if ($normalized === $m || str_starts_with($normalized, $m.'/')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* ردیفهای can_edit=0 بکاپ، بدون کلیدهای سوپرادمین و runtime.
|
||||
*
|
||||
* @return array<string, array<string, mixed>>
|
||||
*/
|
||||
private function readBackup(string $table): array
|
||||
@@ -264,8 +333,6 @@ class SettingsRestoreFromBackupCommand extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* مقدار غیرخالی کلیدهای غیرسوپرادمینی که هنوز در سوپرادمین ماندهاند.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function superAdminValues(): array
|
||||
|
||||
Reference in New Issue
Block a user