> */ 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 { $table = trim((string) $this->option('table')) ?: 'settings_copy1'; $dryRun = (bool) $this->option('dry-run'); $fillFromSuperAdmin = (bool) $this->option('fill-from-superadmin'); $forceOverwrite = (bool) $this->option('force-overwrite'); if (! Schema::hasTable($table)) { $this->error("جدول بکاپ «{$table}» وجود ندارد."); return self::FAILURE; } if ($forceOverwrite) { $this->warn('--force-overwrite روشن است: value غیرخالی لوکال هم ممکن است عوض شود.'); } $backupRows = $this->readBackup($table); $superAdminValues = $fillFromSuperAdmin ? $this->superAdminValues() : []; if ($backupRows === [] && $superAdminValues === []) { $this->warn('هیچ کلید قابل بازگردانی پیدا نشد.'); return self::SUCCESS; } $keys = array_values(array_unique(array_merge( array_keys($backupRows), array_keys($superAdminValues) ))); sort($keys); $localRows = Setting::query() ->whereIn('key', $keys) ->get() ->keyBy('key'); $hasType = Setting::hasTypeColumn(); $report = []; $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, $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) { $row = new Setting(); $row->key = $key; $row->value = $value ?? ''; $row->title_fa = $backup['title_fa'] ?? $key; $row->description = $backup['description'] ?? ''; $row->is_active = $backup['is_active'] ?? 1; $row->can_edit = 0; if ($hasType) { $row->type = $backup['type'] ?? ''; } $row->save(); } continue; } $localValue = trim((string) ($local->value ?? '')); $localIsSeed = $localValue !== '' && $this->isSeedDefault($key, $localValue); $saReal = isset($superAdminValues[$key]) && ! $this->isSeedDefault($key, $superAdminValues[$key]) ? $superAdminValues[$key] : null; // value واقعی موجود: دست نخوره // استثنا: لوکال فقط seed است و SA مقدار واقعی دارد → اصلاح اشتباه قبلی $allowReplaceSeed = $fillFromSuperAdmin && $localIsSeed && $saReal !== null; if ($localValue !== '' && ! $forceOverwrite && ! $allowReplaceSeed) { $counts['kept']++; continue; } if ($allowReplaceSeed) { $value = $saReal; $source = 'superadmin'; } $writeValue = $value !== null && $value !== '' && ! $this->isSeedDefault($key, $value) && ( $localValue === '' || $allowReplaceSeed || ($forceOverwrite && $localValue !== (string) $value) ); // فقط meta — و فقط وقتی value را عوض نمی‌کنیم و لوکال seed/خالی نیست // برای ردیف با value واقعی، meta هم دست نمی‌زنیم (جلوگیری از side-effect) $metaChanged = false; if ($backup && ($localValue === '' || $allowReplaceSeed || $forceOverwrite)) { $metaChanged = $this->metaDiffers($local, $backup, $hasType); } if (! $writeValue && ! $metaChanged) { $counts['skipped']++; continue; } $counts['updated']++; $report[] = [ $key, $this->mask($key, $localValue), $writeValue ? $this->mask($key, $value) : '(بدون تغییر)', $writeValue ? 'UPDATE '.$source : 'META', ]; if ($dryRun) { continue; } if ($writeValue) { $local->value = $value; } if ($backup && $metaChanged) { $local->title_fa = $backup['title_fa'] ?: ($local->title_fa ?: $key); if (($backup['description'] ?? '') !== '') { $local->description = $backup['description']; } if ($hasType && ($backup['type'] ?? '') !== '') { $local->type = $backup['type']; } } $local->save(); } if ($report !== []) { $this->table(['key', 'مقدار لوکال', 'مقدار جدید', 'اقدام'], $report); } $this->info(sprintf( '%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 واقعی لوکال دست نخورده | seed_ignored = پیش‌فرض limangold/... از منبع پر کردن حذف شد'); if ($dryRun) { $this->comment('هیچ چیزی نوشته نشد. برای اجرا همین دستور را بدون --dry-run بزن.'); } return self::SUCCESS; } /** * @return array{0: ?string, 1: string, 2: bool} [value, source, ignoredSeed] */ 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', $ignoredSeed]; } if ($backupValue !== null && $backupValue !== '') { return [$backupValue, 'backup', $ignoredSeed]; } return [null, '', $ignoredSeed]; } 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::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; } /** * @return array> */ private function readBackup(string $table): array { $hasType = Schema::hasColumn($table, 'type'); $columns = ['key', 'value', 'title_fa', 'description', 'is_active', 'can_edit']; if ($hasType) { $columns[] = 'type'; } $rows = []; foreach (DB::table($table)->select($columns)->orderBy('key')->get() as $row) { $key = trim((string) ($row->key ?? '')); if ($key === '' || ! $this->isRestorable($key)) { continue; } if ((int) ($row->can_edit ?? 0) !== 0) { continue; } $rows[$key] = [ 'value' => $row->value === null ? '' : (string) $row->value, 'title_fa' => (string) ($row->title_fa ?? ''), 'description' => (string) ($row->description ?? ''), 'is_active' => (int) ($row->is_active ?? 1), 'type' => $hasType ? (string) ($row->type ?? '') : '', ]; } return $rows; } /** * @return array */ private function superAdminValues(): array { $items = app(SuperAdminProgramSettingsClient::class)->fetchItems(); if (! is_array($items)) { $this->warn('سوپرادمین در دسترس نبود — فالبک نادیده گرفته شد.'); return []; } $values = []; foreach ($items as $item) { if (! is_array($item)) { continue; } $key = trim((string) ($item['key'] ?? '')); if ($key === '' || ! $this->isRestorable($key)) { continue; } $value = $item['value'] ?? null; if ($value === null || trim((string) $value) === '') { continue; } $values[$key] = trim((string) $value); } return $values; } private function isRestorable(string $key): bool { if (SuperAdminProgramSettingsOverlay::isSuperAdminOwned($key)) { return false; } return ! SuperAdminProgramSettingsOverlay::isRuntime($key); } /** @param array $backup */ private function metaDiffers(Setting $local, array $backup, bool $hasType): bool { if (($backup['title_fa'] ?? '') !== '' && (string) ($local->title_fa ?? '') !== $backup['title_fa']) { return true; } if (($backup['description'] ?? '') !== '' && (string) ($local->description ?? '') !== $backup['description']) { return true; } return $hasType && ($backup['type'] ?? '') !== '' && (string) ($local->type ?? '') !== $backup['type']; } private function mask(string $key, ?string $value): string { $value = (string) $value; if ($value === '') { return '(خالی)'; } if (Setting::isSecretOrServerOnlyKey($key)) { return '***('.mb_strlen($value).' نویسه)'; } return mb_strlen($value) > 40 ? mb_substr($value, 0, 37).'…' : $value; } }