diff --git a/app/Console/Commands/SettingsRestoreFromBackupCommand.php b/app/Console/Commands/SettingsRestoreFromBackupCommand.php new file mode 100644 index 0000000..9993e88 --- /dev/null +++ b/app/Console/Commands/SettingsRestoreFromBackupCommand.php @@ -0,0 +1,262 @@ +option('table')) ?: 'settings_copy1'; + $dryRun = (bool) $this->option('dry-run'); + $overwrite = (bool) $this->option('overwrite'); + $fillFromSuperAdmin = (bool) $this->option('fill-from-superadmin'); + + if (! Schema::hasTable($table)) { + $this->error("جدول بکاپ «{$table}» وجود ندارد."); + + return self::FAILURE; + } + + $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]; + + foreach ($keys as $key) { + $backup = $backupRows[$key] ?? null; + $local = $localRows->get($key); + + $value = $backup['value'] ?? null; + $source = $value === null || $value === '' ? '' : 'backup'; + if (($value === null || $value === '') && array_key_exists($key, $superAdminValues)) { + $value = $superAdminValues[$key]; + $source = 'superadmin'; + } + + if (! $local) { + $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 = (string) ($local->value ?? ''); + $writeValue = $value !== null && $value !== '' + && ($localValue === '' || ($overwrite && $localValue !== (string) $value)); + + $metaChanged = false; + if ($backup) { + $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) { + $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 skip=%d (backup=%s, superadmin=%d)', + $dryRun ? '[dry-run] ' : '', + $counts['inserted'], + $counts['updated'], + $counts['skipped'], + $table, + count($superAdminValues) + )); + + if ($dryRun) { + $this->comment('هیچ چیزی نوشته نشد. برای اجرا همین دستور را بدون --dry-run بزن.'); + } + + return self::SUCCESS; + } + + /** + * ردیف‌های can_edit=0 بکاپ، بدون کلیدهای سوپرادمین و runtime. + * + * @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 || $value === '') { + continue; + } + $values[$key] = (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; + } +} diff --git a/app/Http/Controllers/Admin/SettingsController.php b/app/Http/Controllers/Admin/SettingsController.php index c490692..3e90edd 100644 --- a/app/Http/Controllers/Admin/SettingsController.php +++ b/app/Http/Controllers/Admin/SettingsController.php @@ -65,29 +65,38 @@ class SettingsController extends Controller public function edit(Request $request) { - $setting = Setting::where('key', $request->key)->first(); + $key = trim((string) $request->key); + $isDeployAdmin = $this->isDeployAdminUser(); + $isSystemDeploy = $request->boolean('system_deploy'); + $setting = Setting::where('key', $key)->first(); $isNew = !$setting; + if (SuperAdminProgramSettingsOverlay::isSuperAdminOwned($key)) { + return response()->json([ + 'message' => 'این تنظیم از سوپرادمین کنترل می‌شود و در پنل خانگی قابل ویرایش نیست', + ], 403); + } + if ($isNew) { - if ($request->boolean('system_deploy')) { + if ($isSystemDeploy && !$isDeployAdmin) { return response()->json([ - 'message' => 'تنظیمات پیشرفته فقط از سوپرادمین اضافه می‌شوند', + 'message' => 'افزودن تنظیم پیشرفته فقط برای ادمین دیپلوی مجاز است', ], 403); } $setting = new Setting(); - $setting->key = $request->key; - $setting->can_edit = 1; + $setting->key = $key; + $setting->can_edit = $isSystemDeploy ? 0 : 1; } elseif ( - SuperAdminProgramSettingsOverlay::isRuntime((string) $request->key) - && ! $this->isPanelManagedKey((string) $request->key) + SuperAdminProgramSettingsOverlay::isRuntime($key) + && ! $this->isPanelManagedKey($key) ) { return response()->json([ 'message' => 'این مقدار توسط سیستم به‌روز می‌شود و از پنل قابل ویرایش نیست', ], 403); - } elseif (!$setting->can_edit && !$this->isPanelManagedKey((string) $request->key)) { + } elseif (!$setting->can_edit && !$isDeployAdmin && !$this->isPanelManagedKey($key)) { return response()->json([ - 'message' => 'این تنظیم از سوپرادمین کنترل می‌شود و در پنل خانگی قابل ویرایش نیست', + 'message' => 'این تنظیم پیشرفته است و فقط ادمین دیپلوی می‌تواند آن را تغییر دهد', ], 403); } diff --git a/app/Services/SuperAdminProgramSettingsOverlay.php b/app/Services/SuperAdminProgramSettingsOverlay.php index aba9229..a77d122 100644 --- a/app/Services/SuperAdminProgramSettingsOverlay.php +++ b/app/Services/SuperAdminProgramSettingsOverlay.php @@ -22,6 +22,26 @@ class SuperAdminProgramSettingsOverlay private const LAST_GOOD_CACHE_MINUTES = 1440; + /** + * تنها کلیدهایی که مالکشان سوپرادمین است — گروه «دسترسی مشتری به قابلیت‌ها». + * هر کلید دیگری مالکش جدول settings همین نصب است و در overlay دور ریخته می‌شود. + */ + public const SUPERADMIN_OWNED_KEYS = [ + 'customer_direct_sms', + 'customer_special_pricing', + 'enable_conditional_order', + 'enable_customer_level_profit', + 'enable_product_funds', + 'group_sms', + 'has_page_earn_money', + 'has_page_gift_card', + 'has_page_notification', + 'has_page_price_trigger', + 'jibit_enabled', + 'permision__enabled', + 'show_to_users_chat', + ]; + /** کلیدهایی که هنوز صفحهٔ اختصاصی ادمین خانگی مالک‌شان است */ public const LOCAL_OWNED_KEYS = [ 'tracking_systems', @@ -114,7 +134,7 @@ class SuperAdminProgramSettingsOverlay continue; } $key = trim((string) ($row['key'] ?? '')); - if ($key === '' || in_array($key, self::LOCAL_OWNED_KEYS, true) || in_array($key, self::RUNTIME_KEYS, true)) { + if ($key === '' || ! self::isSuperAdminOwned($key)) { continue; } $map[$key] = $row; @@ -192,4 +212,9 @@ class SuperAdminProgramSettingsOverlay { return in_array($key, self::LOCAL_OWNED_KEYS, true); } + + public static function isSuperAdminOwned(string $key): bool + { + return in_array(trim($key), self::SUPERADMIN_OWNED_KEYS, true); + } }