select(Setting::adminSelectColumns()) ->orderBy('key'); if ($request->boolean('system_only')) { $local = $query->get(); return response()->json($this->composeSystemSettings($local)); } if ($request->boolean('editable_only')) { $local = $query->get(); return response()->json($this->composeEditableSettings($local)); } $local = $query->get(); return response()->json($this->composeGetSettings($local)); } /** * اپ کاربر (khanegi_user / theme / ghatreh) — فقط ردیف‌های show_to_user=1، بدون credential. * پنل ادمین از admin/get_settings استفاده می‌کند. */ public function publicIndex() { if (! Setting::hasShowToUserColumn()) { return response()->json([]); } $clientFeatureKeys = SuperAdminProgramSettingsOverlay::CLIENT_FEATURE_KEYS; $local = Setting::query() ->select(Setting::adminSelectColumns()) ->where(function ($query) use ($clientFeatureKeys) { $query->where('show_to_user', 1) ->orWhereIn('key', $clientFeatureKeys); }) ->orderBy('key') ->get(); return response()->json(Setting::forClientApi($this->composeGetSettings($local))); } /** * جدول settings همین نسخه — بدون overlay سوپرادمین. * سوپرادمین برای ایمپورت از این مسیر can_edit=0 را برمی‌دارد. */ public function localTable() { $rows = Setting::query() ->select(Setting::adminSelectColumns()) ->orderBy('key') ->get(); return response()->json($rows); } /** * پاک کردن کش overlay و خواندن دوباره از سوپرادمین * (همهٔ کلیدهای can_edit=0 مالک سوپرادمین — دسترسی مشتری به قابلیت‌ها). */ public function refreshSuperAdminOverlay() { $result = app(SuperAdminProgramSettingsOverlay::class)->refreshFromSuperAdmin(); if (! ($result['ok'] ?? false)) { return response()->json([ 'message' => 'ارتباط با سوپرادمین برقرار نشد؛ در درخواست بعدی دوباره تلاش می‌شود', 'ok' => false, 'count' => 0, 'items' => [], 'meta' => $result['meta'] ?? [], ], 502); } $count = (int) ($result['count'] ?? 0); $domain = trim((string) (($result['meta']['domain'] ?? '') ?: '')); $csp = (string) (($result['items']['customer_special_pricing'] ?? '') ?: ''); $message = "همگام شد ({$count} کلید دسترسی از سوپرادمین)"; if ($domain !== '') { $message .= " — دامنه: {$domain}"; } if ($csp !== '') { $message .= " — قیمت ویژه: {$csp}"; } return response()->json([ 'message' => $message, 'ok' => true, 'count' => $count, 'items' => $result['items'] ?? [], 'source' => $result['source'] ?? 'superadmin', 'meta' => $result['meta'] ?? [], ]); } public function edit(Request $request) { $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 ($isSystemDeploy && !$isDeployAdmin) { return response()->json([ 'message' => 'افزودن تنظیم پیشرفته فقط برای ادمین دیپلوی مجاز است', ], 403); } $setting = new Setting(); $setting->key = $key; $setting->can_edit = $isSystemDeploy ? 0 : 1; } elseif ( SuperAdminProgramSettingsOverlay::isRuntime($key) && ! $this->isPanelManagedKey($key) ) { return response()->json([ 'message' => 'این مقدار توسط سیستم به‌روز می‌شود و از پنل قابل ویرایش نیست', ], 403); } elseif (!$setting->can_edit && !$isDeployAdmin && !$this->isPanelManagedKey($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 (app(ConfigService::class)->isTradingHoursSettingKey($key)) { try { app(ConfigService::class)->notifyTradingHoursChanged(); } catch (\Throwable $e) { report($e); } } 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) || app(ConfigService::class)->isTradingHoursSettingKey($key); } /** * تنظیمات پیشرفته: ۱۵ دسترسی از سوپرادمین + بقیهٔ can_edit=0 از DB لوکال. */ private function composeSystemSettings($localRows) { $overlay = app(SuperAdminProgramSettingsOverlay::class); $saMap = $overlay->catalogMap(); $byKey = []; $localByKey = []; foreach ($localRows as $row) { $localByKey[(string) $row->key] = $row; } if ($saMap !== null) { foreach ($saMap as $key => $item) { if (! is_array($item)) { continue; } if (! SuperAdminProgramSettingsOverlay::isSuperAdminOwned((string) $key)) { continue; } $value = $item['value'] ?? null; if ($value !== null && $value !== '') { $byKey[(string) $key] = $this->overlayRowToSetting($item, $localByKey[(string) $key] ?? null); } } foreach ($localRows as $row) { $key = (string) $row->key; if (isset($byKey[$key])) { continue; } if ( SuperAdminProgramSettingsOverlay::isRuntime($key) || SuperAdminProgramSettingsOverlay::isLocalOwned($key) ) { continue; } if (! $this->isEditableCanEdit($row->can_edit ?? null)) { $byKey[$key] = $row; } } } 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) || SuperAdminProgramSettingsOverlay::isLocalOwned($key); }); return $rows ->map(fn ($row) => $this->normalizeTenantOwnedRow($row)) ->sortBy(fn ($row) => (string) ($row->key ?? '')) ->values(); } /** * get_settings ادمین (و پایهٔ publicIndex قبل از فیلتر show_to_user): * can_edit=1 (+ runtime + local-owned) از DB لوکال * فقط ۱۵ دسترسی سوپرادمین از overlay؛ بقیهٔ can_edit=0 از کپی لوکال * اگر سوپرادمین در دسترس نباشد کل جدول لوکال برمی‌گردد. */ private function composeGetSettings($localRows) { $overlay = app(SuperAdminProgramSettingsOverlay::class); $saMap = $overlay->catalogMap(); if ($saMap === null) { return $localRows; } $localByKey = []; foreach ($localRows as $row) { $localByKey[(string) $row->key] = $row; } $byKey = []; foreach ($localRows as $row) { $key = (string) $row->key; if ( $this->isEditableCanEdit($row->can_edit ?? null) || SuperAdminProgramSettingsOverlay::isRuntime($key) || SuperAdminProgramSettingsOverlay::isLocalOwned($key) ) { $byKey[$key] = $this->normalizeTenantOwnedRow($row); } } foreach ($saMap as $key => $item) { if (! is_array($item)) { continue; } $key = (string) $key; if (! SuperAdminProgramSettingsOverlay::isSuperAdminOwned($key)) { continue; } $value = $item['value'] ?? null; if ($value !== null && $value !== '') { $byKey[$key] = $this->overlayRowToSetting($item, $localByKey[$key] ?? null); } } foreach ($localRows as $row) { $key = (string) $row->key; if (isset($byKey[$key])) { continue; } if ( SuperAdminProgramSettingsOverlay::isRuntime($key) || SuperAdminProgramSettingsOverlay::isLocalOwned($key) ) { $byKey[$key] = $this->normalizeTenantOwnedRow($row); continue; } if (! $this->isEditableCanEdit($row->can_edit ?? null)) { $byKey[$key] = $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 $local = null): 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; if ($local) { if (Setting::hasMenuColumn()) { $setting->menu = $local->menu; } if (Setting::hasShowToUserColumn()) { $setting->show_to_user = $local->show_to_user; } } // دسترسی‌های منوی اپ کاربر همیشه به کلاینت می‌رسند (حتی اگر local show_to_user=0 یا absent) if ( Setting::hasShowToUserColumn() && SuperAdminProgramSettingsOverlay::isClientFeatureKey((string) $setting->key) ) { $setting->show_to_user = 1; } 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; } }