feat(server): overlay admin get_settings from SuperAdmin program-settings
Fetch public program-settings with 3-origin retry and no Laravel cache so local DB settings stay in sync with the sold version. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Admin;
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Services\PriceChannelBroadcastService;
|
use App\Services\PriceChannelBroadcastService;
|
||||||
|
use App\Services\SuperAdminProgramSettingsOverlay;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class SettingsController extends Controller
|
class SettingsController extends Controller
|
||||||
@@ -19,8 +20,6 @@ class SettingsController extends Controller
|
|||||||
*/
|
*/
|
||||||
private const PANEL_MANAGED_KEYS = [
|
private const PANEL_MANAGED_KEYS = [
|
||||||
'tracking_systems',
|
'tracking_systems',
|
||||||
'tracking_systems_enabled',
|
|
||||||
'banner_enabled',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
@@ -30,6 +29,11 @@ class SettingsController extends Controller
|
|||||||
->orderBy('key');
|
->orderBy('key');
|
||||||
|
|
||||||
if ($request->boolean('system_only')) {
|
if ($request->boolean('system_only')) {
|
||||||
|
$overlay = app(SuperAdminProgramSettingsOverlay::class)->items();
|
||||||
|
if ($overlay) {
|
||||||
|
return response()->json($this->authorizeSettingsPayload($this->overlayToRows($overlay)));
|
||||||
|
}
|
||||||
|
|
||||||
$rows = $query
|
$rows = $query
|
||||||
->where(function ($q) {
|
->where(function ($q) {
|
||||||
$q->where('can_edit', 0)->orWhere('can_edit', false);
|
$q->where('can_edit', 0)->orWhere('can_edit', false);
|
||||||
@@ -48,26 +52,30 @@ class SettingsController extends Controller
|
|||||||
return response()->json($this->authorizeSettingsPayload($query->where('can_edit', 1)->get()));
|
return response()->json($this->authorizeSettingsPayload($query->where('can_edit', 1)->get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json($this->authorizeSettingsPayload($query->get()));
|
return response()->json($this->authorizeSettingsPayload(
|
||||||
|
$this->mergeOverlayIntoLocal($query->get())
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function edit(Request $request)
|
public function edit(Request $request)
|
||||||
{
|
{
|
||||||
$isDeployAdmin = $this->isDeployAdminUser();
|
|
||||||
$isSystemDeploy = $request->boolean('system_deploy');
|
|
||||||
$setting = Setting::where('key', $request->key)->first();
|
$setting = Setting::where('key', $request->key)->first();
|
||||||
$isNew = !$setting;
|
$isNew = !$setting;
|
||||||
|
|
||||||
if ($isNew) {
|
if ($isNew) {
|
||||||
if ($isSystemDeploy && !$isDeployAdmin) {
|
if ($request->boolean('system_deploy')) {
|
||||||
return response()->json(['message' => 'Unauthorized'], 403);
|
return response()->json([
|
||||||
|
'message' => 'تنظیمات پیشرفته فقط از سوپرادمین اضافه میشوند',
|
||||||
|
], 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
$setting = new Setting();
|
$setting = new Setting();
|
||||||
$setting->key = $request->key;
|
$setting->key = $request->key;
|
||||||
$setting->can_edit = $isSystemDeploy ? 0 : 1;
|
$setting->can_edit = 1;
|
||||||
} elseif (!$setting->can_edit && !$isDeployAdmin && !$this->isPanelManagedKey((string) $request->key)) {
|
} elseif (!$setting->can_edit && !$this->isPanelManagedKey((string) $request->key)) {
|
||||||
return response()->json(['message' => 'Forbidden'], 403);
|
return response()->json([
|
||||||
|
'message' => 'این تنظیم از سوپرادمین کنترل میشود و در پنل خانگی قابل ویرایش نیست',
|
||||||
|
], 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
$previousValue = $setting->exists ? (string) ($setting->value ?? '') : null;
|
$previousValue = $setting->exists ? (string) ($setting->value ?? '') : null;
|
||||||
@@ -94,7 +102,7 @@ class SettingsController extends Controller
|
|||||||
})->afterResponse();
|
})->afterResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($isNew && $isSystemDeploy) {
|
if ($isNew) {
|
||||||
$projectName = Setting::where('key', 'name_fa')->value('value') ?: 'پروژه';
|
$projectName = Setting::where('key', 'name_fa')->value('value') ?: 'پروژه';
|
||||||
$settingTitle = trim($request->title_fa ?? '') ?: $request->key;
|
$settingTitle = trim($request->title_fa ?? '') ?: $request->key;
|
||||||
$this->Send(
|
$this->Send(
|
||||||
@@ -135,6 +143,71 @@ class SettingsController extends Controller
|
|||||||
return in_array($key, self::PANEL_MANAGED_KEYS, true);
|
return in_array($key, self::PANEL_MANAGED_KEYS, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function mergeOverlayIntoLocal($rows)
|
||||||
|
{
|
||||||
|
$overlay = app(SuperAdminProgramSettingsOverlay::class)->items();
|
||||||
|
if (! $overlay) {
|
||||||
|
return $rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
$byKey = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$byKey[(string) $row->key] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($overlay as $item) {
|
||||||
|
$key = trim((string) ($item['key'] ?? ''));
|
||||||
|
if ($key === '' || SuperAdminProgramSettingsOverlay::isLocalOwned($key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isset($byKey[$key])) {
|
||||||
|
$byKey[$key]->value = $item['value'] ?? $byKey[$key]->value;
|
||||||
|
if (array_key_exists('title_fa', $item) && $item['title_fa'] !== null && $item['title_fa'] !== '') {
|
||||||
|
$byKey[$key]->title_fa = $item['title_fa'];
|
||||||
|
}
|
||||||
|
if (array_key_exists('description', $item) && $item['description'] !== null) {
|
||||||
|
$byKey[$key]->description = $item['description'];
|
||||||
|
}
|
||||||
|
if (array_key_exists('type', $item) && $item['type'] !== null && $item['type'] !== '') {
|
||||||
|
$byKey[$key]->type = $item['type'];
|
||||||
|
}
|
||||||
|
if (array_key_exists('is_active', $item)) {
|
||||||
|
$byKey[$key]->is_active = $item['is_active'];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$byKey[$key] = $this->overlayRowToSetting($item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return collect(array_values($byKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
{
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* اپ کاربر (بدون auth ادمین) هرگز secret/credential نمیبیند.
|
* اپ کاربر (بدون auth ادمین) هرگز secret/credential نمیبیند.
|
||||||
* پنل ادمین با auth:user همه کلیدها را میبیند.
|
* پنل ادمین با auth:user همه کلیدها را میبیند.
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ class ConfigService
|
|||||||
*/
|
*/
|
||||||
public function get(string $key, $default = null)
|
public function get(string $key, $default = null)
|
||||||
{
|
{
|
||||||
|
$fromSa = $this->valueFromSuperAdmin($key, $default);
|
||||||
|
if ($fromSa !== '__sa_miss__') {
|
||||||
|
return $fromSa;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$row = Setting::where('key', $key)->first();
|
$row = Setting::where('key', $key)->first();
|
||||||
|
|
||||||
@@ -39,6 +44,40 @@ class ConfigService
|
|||||||
return $fromFile !== null ? $fromFile : $default;
|
return $fromFile !== null ? $fromFile : $default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* مقدار سوپرادمین اگر کلید در overlay باشد؛ وگرنه سنتنل miss.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
private function valueFromSuperAdmin(string $key, $default)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$row = app(SuperAdminProgramSettingsOverlay::class)->row($key);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return '__sa_miss__';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($row === null) {
|
||||||
|
return '__sa_miss__';
|
||||||
|
}
|
||||||
|
|
||||||
|
$active = $row['is_active'] ?? 1;
|
||||||
|
if ($active === 0 || $active === false || $active === '0') {
|
||||||
|
$fromFile = config("price_providers.{$key}");
|
||||||
|
|
||||||
|
return $fromFile !== null ? $fromFile : $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = $row['value'] ?? null;
|
||||||
|
if ($value === null || $value === '') {
|
||||||
|
$fromFile = config("price_providers.{$key}");
|
||||||
|
|
||||||
|
return $fromFile !== null ? $fromFile : $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
public function getString(string $key, string $default = ''): string
|
public function getString(string $key, string $default = ''): string
|
||||||
{
|
{
|
||||||
$value = $this->get($key, $default);
|
$value = $this->get($key, $default);
|
||||||
|
|||||||
@@ -0,0 +1,245 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* خواندن تنظیمات پیشرفته از سوپرادمین (منبع حقیقت can_edit=0).
|
||||||
|
*
|
||||||
|
* Full (با HMAC/Bearer): GET {origin}/api/remote/program-settings
|
||||||
|
* Public: GET {origin}/api/public/program-settings?domain=
|
||||||
|
*/
|
||||||
|
class SuperAdminProgramSettingsClient
|
||||||
|
{
|
||||||
|
public const REMOTE_PATH = '/api/remote/program-settings';
|
||||||
|
|
||||||
|
public const PUBLIC_PATH = '/api/public/program-settings';
|
||||||
|
|
||||||
|
public const RETRY_ROUNDS = 3;
|
||||||
|
|
||||||
|
public function fetchItems(): ?array
|
||||||
|
{
|
||||||
|
if ($this->canAuth()) {
|
||||||
|
$items = $this->fetchRemoteFull();
|
||||||
|
if ($items !== null) {
|
||||||
|
return $items;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->fetchPublic();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function canAuth(): bool
|
||||||
|
{
|
||||||
|
return $this->sharedSecret() !== '' || $this->bearerToken() !== '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, array<string, mixed>>|null
|
||||||
|
*/
|
||||||
|
private function fetchRemoteFull(): ?array
|
||||||
|
{
|
||||||
|
foreach ($this->attemptOrigins() as $origin) {
|
||||||
|
try {
|
||||||
|
$response = Http::acceptJson()
|
||||||
|
->withHeaders(array_merge($this->authHeaders('GET', self::REMOTE_PATH), [
|
||||||
|
'Cache-Control' => 'no-store',
|
||||||
|
]))
|
||||||
|
->timeout(6)
|
||||||
|
->get($origin.self::REMOTE_PATH);
|
||||||
|
|
||||||
|
if (! $response->successful() || ! $this->isJsonResponse($response)) {
|
||||||
|
Log::warning('SuperAdmin program-settings remote failed', [
|
||||||
|
'origin' => $origin,
|
||||||
|
'status' => $response->status(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = $this->extractItems($response->json());
|
||||||
|
if ($items !== null) {
|
||||||
|
return $items;
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::warning('SuperAdmin program-settings remote exception', [
|
||||||
|
'origin' => $origin,
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, array<string, mixed>>|null
|
||||||
|
*/
|
||||||
|
private function fetchPublic(): ?array
|
||||||
|
{
|
||||||
|
$domain = $this->domain();
|
||||||
|
if ($domain === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->attemptOrigins() as $origin) {
|
||||||
|
try {
|
||||||
|
$response = Http::acceptJson()
|
||||||
|
->withHeaders(['Cache-Control' => 'no-store'])
|
||||||
|
->timeout(6)
|
||||||
|
->get($origin.self::PUBLIC_PATH, ['domain' => $domain]);
|
||||||
|
|
||||||
|
if (! $response->successful() || ! $this->isJsonResponse($response)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = $this->extractItems($response->json());
|
||||||
|
if ($items !== null) {
|
||||||
|
return $items;
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::warning('SuperAdmin public program-settings exception', [
|
||||||
|
'origin' => $origin,
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* سه دامنه × سه دور (مستر، بعد اسلیوها).
|
||||||
|
*
|
||||||
|
* @return iterable<int, string>
|
||||||
|
*/
|
||||||
|
private function attemptOrigins(): iterable
|
||||||
|
{
|
||||||
|
$origins = $this->origins();
|
||||||
|
for ($round = 1; $round <= self::RETRY_ROUNDS; $round++) {
|
||||||
|
foreach ($origins as $origin) {
|
||||||
|
yield $origin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<string>
|
||||||
|
*/
|
||||||
|
private function origins(): array
|
||||||
|
{
|
||||||
|
$defaults = [
|
||||||
|
'https://adm.montzar.ir',
|
||||||
|
'https://adm.montech.ir',
|
||||||
|
'https://adm.montest.ir',
|
||||||
|
];
|
||||||
|
|
||||||
|
$extra = [];
|
||||||
|
$explicit = trim((string) config('services.superadmin.origin', ''));
|
||||||
|
if ($explicit !== '') {
|
||||||
|
$extra[] = rtrim($explicit, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
$sms = trim((string) config('services.superadmin.sms_base_url', ''));
|
||||||
|
if ($sms !== '') {
|
||||||
|
$parsed = parse_url($sms);
|
||||||
|
if (! empty($parsed['scheme']) && ! empty($parsed['host'])) {
|
||||||
|
$port = isset($parsed['port']) ? ':'.$parsed['port'] : '';
|
||||||
|
$extra[] = $parsed['scheme'].'://'.$parsed['host'].$port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values(array_unique(array_merge($extra, $defaults)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isJsonResponse($response): bool
|
||||||
|
{
|
||||||
|
$contentType = strtolower((string) $response->header('Content-Type'));
|
||||||
|
|
||||||
|
return str_contains($contentType, 'json');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $data
|
||||||
|
* @return array<int, array<string, mixed>>|null
|
||||||
|
*/
|
||||||
|
private function extractItems($data): ?array
|
||||||
|
{
|
||||||
|
if (! is_array($data)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (array_is_list($data)) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
foreach (['settings', 'items', 'data'] as $key) {
|
||||||
|
if (isset($data[$key]) && is_array($data[$key])) {
|
||||||
|
return $data[$key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
private function authHeaders(string $method, string $canonicalPath): array
|
||||||
|
{
|
||||||
|
$secret = $this->sharedSecret();
|
||||||
|
if ($secret !== '') {
|
||||||
|
$domain = $this->domain();
|
||||||
|
$timestamp = (string) time();
|
||||||
|
$payload = $timestamp.'.'.strtoupper($method).'.'.$canonicalPath.'.'.$domain;
|
||||||
|
$signature = hash_hmac('sha256', $payload, $secret);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'X-Abshode-Domain' => $domain,
|
||||||
|
'X-Abshode-Timestamp' => $timestamp,
|
||||||
|
'X-Abshode-Signature' => $signature,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $this->bearerToken();
|
||||||
|
if ($token !== '') {
|
||||||
|
return [
|
||||||
|
'Authorization' => 'Bearer '.$token,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sharedSecret(): string
|
||||||
|
{
|
||||||
|
return trim((string) config('services.superadmin.sms_shared_secret', ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function bearerToken(): string
|
||||||
|
{
|
||||||
|
return trim((string) config('services.superadmin.remote_token', ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function domain(): string
|
||||||
|
{
|
||||||
|
$override = trim((string) config('services.superadmin.sms_domain', ''));
|
||||||
|
if ($override !== '') {
|
||||||
|
return $this->normalizeHost($override);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fromApp = parse_url((string) config('app.url'), PHP_URL_HOST);
|
||||||
|
|
||||||
|
return $this->normalizeHost(is_string($fromApp) ? $fromApp : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeHost(string $value): string
|
||||||
|
{
|
||||||
|
$host = strtolower(trim($value));
|
||||||
|
$host = preg_replace('#^https?://#', '', $host) ?? $host;
|
||||||
|
$host = explode('/', $host)[0] ?? $host;
|
||||||
|
$host = explode(':', $host)[0] ?? $host;
|
||||||
|
|
||||||
|
return $host;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* تنظیمات پیشرفته سوپرادمین برای ConfigService.
|
||||||
|
* کش پایدار ندارد؛ فقط یک بار در همان درخواست PHP نگه داشته میشود.
|
||||||
|
*/
|
||||||
|
class SuperAdminProgramSettingsOverlay
|
||||||
|
{
|
||||||
|
/** کلیدهایی که هنوز صفحهٔ اختصاصی ادمین خانگی مالکشان است */
|
||||||
|
public const LOCAL_OWNED_KEYS = [
|
||||||
|
'tracking_systems',
|
||||||
|
];
|
||||||
|
|
||||||
|
/** لاگ/قیمت لحظهای همکار — مال دیتابیس نسخه است نه سوپرادمین */
|
||||||
|
public const RUNTIME_KEYS = [
|
||||||
|
'montzar_last_product_log',
|
||||||
|
'montzar_order_response',
|
||||||
|
'montzar_live_price',
|
||||||
|
'montzar_mazaneh',
|
||||||
|
'montzar_diff',
|
||||||
|
'montzar_truly_buy_mazaneh',
|
||||||
|
'montzar_truly_sell_mazaneh',
|
||||||
|
'montzar_token',
|
||||||
|
'montzar_closed',
|
||||||
|
'up_price',
|
||||||
|
'down_price',
|
||||||
|
'price_extreme_date',
|
||||||
|
'do_send_liman',
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @var array<string, array<string, mixed>>|null */
|
||||||
|
private static ?array $memo = null;
|
||||||
|
|
||||||
|
private static bool $loaded = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, array<string, mixed>>|null null = سوپرادمین در دسترس نبود
|
||||||
|
*/
|
||||||
|
public function map(): ?array
|
||||||
|
{
|
||||||
|
if (self::$loaded) {
|
||||||
|
return self::$memo;
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$loaded = true;
|
||||||
|
|
||||||
|
$items = app(SuperAdminProgramSettingsClient::class)->fetchItems();
|
||||||
|
if ($items === null) {
|
||||||
|
self::$memo = null;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$map = [];
|
||||||
|
foreach ($items as $row) {
|
||||||
|
if (! is_array($row)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$key = trim((string) ($row['key'] ?? ''));
|
||||||
|
if ($key === '' || in_array($key, self::LOCAL_OWNED_KEYS, true) || in_array($key, self::RUNTIME_KEYS, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$map[$key] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$memo = $map;
|
||||||
|
|
||||||
|
return $map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, array<string, mixed>>
|
||||||
|
*/
|
||||||
|
public function items(): array
|
||||||
|
{
|
||||||
|
$map = $this->map();
|
||||||
|
if ($map === null || $map === []) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values($map);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>|null
|
||||||
|
*/
|
||||||
|
public function row(string $key): ?array
|
||||||
|
{
|
||||||
|
$map = $this->map();
|
||||||
|
if ($map === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$key = trim($key);
|
||||||
|
if ($key === '' || ! array_key_exists($key, $map)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $map[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isLocalOwned(string $key): bool
|
||||||
|
{
|
||||||
|
return in_array($key, self::LOCAL_OWNED_KEYS, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,6 +50,8 @@ return [
|
|||||||
'sms_shared_secret' => env('SUPERADMIN_SMS_SHARED_SECRET'),
|
'sms_shared_secret' => env('SUPERADMIN_SMS_SHARED_SECRET'),
|
||||||
'sms_domain' => env('SUPERADMIN_SMS_DOMAIN'),
|
'sms_domain' => env('SUPERADMIN_SMS_DOMAIN'),
|
||||||
'remote_token' => env('SUPERADMIN_REMOTE_TOKEN'),
|
'remote_token' => env('SUPERADMIN_REMOTE_TOKEN'),
|
||||||
|
/** Origin سوپرادمین برای /api/public/program-settings و /api/remote/program-settings */
|
||||||
|
'origin' => env('SUPERADMIN_ORIGIN'),
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user