setting pushed

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-23 22:34:18 +03:30
co-authored by Cursor
parent d3997cfc8f
commit d2fd315e8a
3 changed files with 306 additions and 10 deletions
@@ -0,0 +1,262 @@
<?php
namespace App\Console\Commands;
use App\Models\Setting;
use App\Services\SuperAdminProgramSettingsClient;
use App\Services\SuperAdminProgramSettingsOverlay;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
/**
* بازگرداندن مقادیر تنظیمات can_edit=0 از جدول بکاپ (پیش‌فرض settings_copy1)
* به جدول settings — بعد از این‌که مالکیت این کلیدها از سوپرادمین به همین نصب برگشت.
*
* کلیدهای گروه «دسترسی مشتری به قابلیت‌ها» (مالک: سوپرادمین) و کلیدهای runtime رد می‌شوند.
*
* فالبک --fill-from-superadmin باید قبل از دیپلوی سوپرادمینِ جدید اجرا شود؛
* بعد از آن دیپلوی، این کلیدها از سوپرادمین حذف شده‌اند و فالبکی نمی‌ماند.
*/
class SettingsRestoreFromBackupCommand extends Command
{
protected $signature = 'settings:restore-from-backup
{--table=settings_copy1 : جدول بکاپ}
{--dry-run : فقط گزارش، بدون نوشتن}
{--overwrite : مقدار لوکال غیرخالی را هم بازنویسی کن}
{--fill-from-superadmin : کلیدهای غایب/خالی در بکاپ را از سوپرادمین پر کن}';
protected $description = 'بازگرداندن مقادیر تنظیمات پیشرفته از جدول بکاپ به settings';
public function handle(): int
{
$table = trim((string) $this->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<string, array<string, mixed>>
*/
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<string, string>
*/
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<string, mixed> $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;
}
}
@@ -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);
}
@@ -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);
}
}