select(Setting::adminSelectColumns()) ->orderBy('key'); if ($request->boolean('system_only')) { $local = $query->get(); return response()->json($this->authorizeSettingsPayload( $this->composeSystemSettings($local) )); } if ($request->boolean('editable_only')) { $local = $query->get(); return response()->json($this->authorizeSettingsPayload( $this->composeEditableSettings($local) )); } $local = $query->get(); return response()->json($this->authorizeSettingsPayload($this->composeGetSettings($local))); } /** * جدول settings همین نسخه — بدون overlay سوپرادمین. * سوپرادمین برای ایمپورت از این مسیر can_edit=0 را برمی‌دارد. */ public function localTable() { $rows = Setting::query() ->select(Setting::adminSelectColumns()) ->orderBy('key') ->get(); return response()->json($this->authorizeSettingsPayload($rows)); } public function edit(Request $request) { $setting = Setting::where('key', $request->key)->first(); $isNew = !$setting; if ($isNew) { if ($request->boolean('system_deploy')) { return response()->json([ 'message' => 'تنظیمات پیشرفته فقط از سوپرادمین اضافه می‌شوند', ], 403); } $setting = new Setting(); $setting->key = $request->key; $setting->can_edit = 1; } elseif ( SuperAdminProgramSettingsOverlay::isRuntime((string) $request->key) && ! $this->isPanelManagedKey((string) $request->key) ) { return response()->json([ 'message' => 'این مقدار توسط سیستم به‌روز می‌شود و از پنل قابل ویرایش نیست', ], 403); } elseif (!$setting->can_edit && !$this->isPanelManagedKey((string) $request->key)) { return response()->json([ 'message' => 'این تنظیم از سوپرادمین کنترل می‌شود و در پنل خانگی قابل ویرایش نیست', ], 403); } $previousValue = $setting->exists ? (string) ($setting->value ?? '') : null; $setting->value = $request->value; $setting->description = $request->description; if ($request->has('is_active')) { $setting->is_active = filter_var($request->is_active, FILTER_VALIDATE_BOOLEAN) ? 1 : 0; } elseif ($isNew) { $setting->is_active = 1; } $setting->title_fa = $request->title_fa ?? ''; if (Setting::hasTypeColumn()) { $setting->type = $request->type ?? ''; } $setting->save(); if ( $request->key === 'price_channel_message_text' && $previousValue !== (string) ($request->value ?? '') ) { dispatch(function () { app(PriceChannelBroadcastService::class)->broadcastAfterTemplateChange(); })->afterResponse(); } if ($isNew) { $projectName = Setting::where('key', 'name_fa')->value('value') ?: 'پروژه'; $settingTitle = trim($request->title_fa ?? '') ?: $request->key; $this->Send( self::NEW_SETTING_NOTIFY_MOBILE, "تنظیم جدید: {$settingTitle}\nپروژه: {$projectName}", false ); } return response()->json($setting); } private function normalizeAdminMobile(?string $mobile): string { $digits = preg_replace('/\D+/', '', (string) $mobile) ?? ''; if (str_starts_with($digits, '98') && strlen($digits) >= 12) { $digits = substr($digits, 2); } if (strlen($digits) === 11 && str_starts_with($digits, '0')) { $digits = substr($digits, 1); } return $digits; } private function isDeployAdminUser(): bool { $user = auth('user')->user() ?? auth()->user(); if (!$user) { return false; } return $this->normalizeAdminMobile($user->mobile ?? '') === self::DEPLOY_ADMIN_MOBILE_SUFFIX; } private function isPanelManagedKey(string $key): bool { return in_array($key, self::PANEL_MANAGED_KEYS, true); } /** * تنظیمات پیشرفته: can_edit=0 (سوپرادمین) + کلیدهای can_edit=1 که کد مستقیم می‌نویسد. */ private function composeSystemSettings($localRows) { $overlay = app(SuperAdminProgramSettingsOverlay::class); $saMap = $overlay->map(); $byKey = []; if ($saMap !== null) { foreach ($saMap as $key => $item) { if (! is_array($item)) { continue; } if ( SuperAdminProgramSettingsOverlay::isRuntime((string) $key) || SuperAdminProgramSettingsOverlay::isLocalOwned((string) $key) ) { continue; } $byKey[(string) $key] = $this->overlayRowToSetting($item); } } else { foreach ($localRows as $row) { if (! $this->isEditableCanEdit($row->can_edit ?? null)) { $byKey[(string) $row->key] = $row; } } } foreach ($localRows as $row) { $key = (string) $row->key; if ( SuperAdminProgramSettingsOverlay::isLocalOwned($key) || SuperAdminProgramSettingsOverlay::isRuntime($key) ) { $byKey[$key] = $this->normalizeTenantOwnedRow($row); } } return collect(array_values($byKey))->sortBy(fn ($row) => (string) ($row->key ?? ''))->values(); } /** * تنظیمات عادی پنل — can_edit=1 در DB + کلیدهای runtime (کد tenant می‌نویسد). */ private function composeEditableSettings($localRows) { $rows = collect($localRows)->filter(function ($row) { $key = (string) ($row->key ?? ''); return $this->isEditableCanEdit($row->can_edit ?? null) || SuperAdminProgramSettingsOverlay::isRuntime($key); }); return $rows ->map(fn ($row) => $this->normalizeTenantOwnedRow($row)) ->sortBy(fn ($row) => (string) ($row->key ?? '')) ->values(); } /** * get_settings: کلیدهای سوپرادمین (can_edit=0) + ردیف‌های جدول لوکال با can_edit=1. * اگر سوپرادمین در دسترس نباشد کل جدول لوکال برمی‌گردد. * کلیدهای runtime / متعلق به پنل خانگی همیشه از جدول لوکال می‌آیند. */ private function composeGetSettings($localRows) { $overlay = app(SuperAdminProgramSettingsOverlay::class); $saMap = $overlay->map(); if ($saMap === null) { return $localRows; } $byKey = []; foreach ($localRows as $row) { if ($this->isEditableCanEdit($row->can_edit ?? null)) { $byKey[(string) $row->key] = $this->normalizeTenantOwnedRow($row); } } foreach ($saMap as $key => $item) { if (! is_array($item)) { continue; } if ( SuperAdminProgramSettingsOverlay::isRuntime((string) $key) || SuperAdminProgramSettingsOverlay::isLocalOwned((string) $key) ) { continue; } $byKey[(string) $key] = $this->overlayRowToSetting($item); } foreach ($localRows as $row) { $key = (string) $row->key; if ( SuperAdminProgramSettingsOverlay::isLocalOwned($key) || SuperAdminProgramSettingsOverlay::isRuntime($key) ) { $byKey[$key] = $this->normalizeTenantOwnedRow($row); } } return collect(array_values($byKey))->sortBy(fn ($row) => (string) ($row->key ?? ''))->values(); } private function isEditableCanEdit(mixed $canEdit): bool { return $canEdit === 1 || $canEdit === true || $canEdit === '1'; } private function mergeOverlayIntoLocal($rows) { return $this->composeGetSettings($rows); } /** * @param array> $overlay */ private function overlayToRows(array $overlay) { return collect($overlay) ->filter(fn ($item) => is_array($item) && trim((string) ($item['key'] ?? '')) !== '') ->map(fn ($item) => $this->overlayRowToSetting($item)) ->values(); } private function overlayRowToSetting(array $item): Setting { $setting = new Setting(); $setting->id = isset($item['id']) && is_numeric($item['id']) ? (int) $item['id'] : null; $setting->key = (string) ($item['key'] ?? ''); $setting->value = $item['value'] ?? ''; $setting->title_fa = $item['title_fa'] ?? ''; $setting->description = $item['description'] ?? ''; $setting->type = $item['type'] ?? ''; $setting->is_active = $item['is_active'] ?? 1; $setting->can_edit = 0; return $setting; } /** کلیدهای tenant-only — can_edit=1 در پاسخ API حتی اگر DB قدیمی 0 باشد */ private function normalizeTenantOwnedRow(Setting $row): Setting { $key = (string) ($row->key ?? ''); if ( SuperAdminProgramSettingsOverlay::isRuntime($key) || SuperAdminProgramSettingsOverlay::isLocalOwned($key) ) { $row->can_edit = 1; } return $row; } /** * اپ کاربر (بدون auth ادمین) هرگز secret/credential نمی‌بیند. * پنل ادمین با auth:user همه کلیدها را می‌بیند. * * @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Collection|iterable $rows * @return \Illuminate\Support\Collection */ private function authorizeSettingsPayload($rows) { if (auth('user')->check()) { return collect($rows)->values(); } return Setting::forPublicApi($rows); } }