Files
boursecu_server/app/Http/Controllers/Admin/SettingsController.php
T
mohmdsdgandCursor 995f6984ed feat(automations): add run-now test endpoint and sends log table
Enable immediate automation test runs without waiting for the scheduled hour, expose send history for debugging, and return all SuperAdmin overlay keys on refresh instead of only enable_automation.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-24 21:46:13 +03:30

423 lines
14 KiB
PHP

<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Setting;
use App\Services\PriceChannelBroadcastService;
use App\Services\SuperAdminProgramSettingsOverlay;
use Illuminate\Http\Request;
class SettingsController extends Controller
{
private const DEPLOY_ADMIN_MOBILE_SUFFIX = '9128081392';
private const NEW_SETTING_NOTIFY_MOBILE = '09134469985';
/**
* کلیدهایی که پنل ادمین باید بتواند ویرایش کند حتی اگر در DB هنوز can_edit=0 باشد
* (بنرها / سایت‌های ری‌گیری).
*/
private const PANEL_MANAGED_KEYS = [
'tracking_systems',
];
public function index(Request $request)
{
$query = Setting::query()
->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([]);
}
$local = Setting::query()
->select(Setting::adminSelectColumns())
->where('show_to_user', 1)
->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' => [],
], 502);
}
$count = (int) ($result['count'] ?? 0);
return response()->json([
'message' => "همگام شد ({$count} کلید دسترسی از سوپرادمین)",
'ok' => true,
'count' => $count,
'items' => $result['items'] ?? [],
'source' => $result['source'] ?? 'superadmin',
]);
}
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 ($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->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::isRuntime((string) $key)
|| SuperAdminProgramSettingsOverlay::isLocalOwned((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);
});
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) از DB لوکال
* can_edit=0 از سوپرادمین؛ اگر کلید در SA نبود یا value خالی بود → کپی لوکال
* اگر سوپرادمین در دسترس نباشد کل جدول لوکال برمی‌گردد.
*/
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::isRuntime($key)
|| SuperAdminProgramSettingsOverlay::isLocalOwned($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<int, array<string, mixed>> $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;
}
}
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;
}
}