303 lines
9.8 KiB
PHP
303 lines
9.8 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\User;
|
|
|
|
class AdminPageAccess
|
|
{
|
|
public const DEPLOY_ADMIN_MOBILE_CORE = '9128081392';
|
|
|
|
/** کلید برگها؛ با ADMIN_PAGE_TREE در ادمین یکی بماند. */
|
|
public const LEAF_KEYS = [
|
|
'dashboardMain',
|
|
'customerMain_limitedOrder',
|
|
'customerMain_archive',
|
|
'customerMain_online',
|
|
'customerMain_specialPrice',
|
|
'customerMain_goldCredit',
|
|
'customerMain_digipayInstallment',
|
|
'productsMain_limitedOrder',
|
|
'productsMain_archive',
|
|
'productsMain_categories',
|
|
'ordersMain',
|
|
'copperConvertMain',
|
|
'transactionsMain',
|
|
'accountsMain',
|
|
'walletMain',
|
|
'physicalWithdrawMain',
|
|
'withdrawMain',
|
|
'pricingMain',
|
|
'priceTriggers',
|
|
'conditionalOrders',
|
|
'colleagueOrdersMain',
|
|
'accountingMain',
|
|
'supportMain',
|
|
'websiteLandingSite',
|
|
'websiteLandingHeader',
|
|
'websiteLandingHero',
|
|
'websiteLandingLivePrice',
|
|
'websiteLandingCalculator',
|
|
'websiteLandingFeature',
|
|
'websiteLandingSteps',
|
|
'websiteLandingTrust',
|
|
'websiteLandingFaq',
|
|
'websiteLandingBottomCta',
|
|
'websiteLandingFooter',
|
|
'configuration',
|
|
'captureSitesMain',
|
|
'advancedConfiguration',
|
|
'smsPanelMain',
|
|
'tradingHours',
|
|
'banners',
|
|
'appNotifications',
|
|
'automations',
|
|
'smsMain',
|
|
'giftCardMain',
|
|
'discounts',
|
|
'usersMain',
|
|
];
|
|
|
|
/** کلید عملیات ستون هر صفحه؛ با PAGE_TABLE_ACTIONS ادمین یکی بماند. */
|
|
public const ACTIONS = [
|
|
'customerMain_limitedOrder' => ['charge', 'tahesab', 'zarin', 'pargar', 'accountingCode', 'info', 'edit', 'referral', 'sms', 'approve', 'archive', 'active', 'trade'],
|
|
'customerMain_archive' => ['restore', 'info'],
|
|
'customerMain_specialPrice' => ['edit', 'toggle', 'delete'],
|
|
'customerMain_goldCredit' => ['detail', 'edit', 'approve', 'reject'],
|
|
'customerMain_digipayInstallment' => ['edit'],
|
|
'productsMain_limitedOrder' => ['buy', 'sell', 'list', 'tv', 'edit', 'archive'],
|
|
'productsMain_archive' => ['restore', 'info'],
|
|
'ordersMain' => ['info'],
|
|
'transactionsMain' => ['info'],
|
|
'accountsMain' => ['toggle', 'delete'],
|
|
'walletMain' => ['info'],
|
|
'physicalWithdrawMain' => ['amount', 'approve', 'status', 'factor'],
|
|
'withdrawMain' => ['confirm', 'factor', 'status', 'weight', 'tahesab', 'zarin'],
|
|
'pricingMain' => ['details'],
|
|
'priceTriggers' => ['edit', 'toggle', 'delete'],
|
|
'conditionalOrders' => ['edit', 'toggle', 'delete'],
|
|
'accountingMain' => ['doc', 'register', 'send'],
|
|
'appNotifications' => ['recipients', 'readers', 'edit', 'toggle', 'delete'],
|
|
'automations' => ['preview', 'log', 'edit', 'toggle', 'delete'],
|
|
'giftCardMain' => ['info', 'codes', 'edit', 'delete'],
|
|
'discounts' => ['edit'],
|
|
'usersMain' => ['info', 'edit', 'delete'],
|
|
];
|
|
|
|
private const ALIASES = [
|
|
'dashboard' => 'dashboardMain',
|
|
'main' => 'dashboardMain',
|
|
'orders' => 'ordersMain',
|
|
'copperConvert' => 'copperConvertMain',
|
|
'transactions' => 'transactionsMain',
|
|
'support' => 'supportMain',
|
|
'factor' => 'withdrawMain',
|
|
];
|
|
|
|
public static function hasFullAccess(?User $user): bool
|
|
{
|
|
if (!$user) {
|
|
return false;
|
|
}
|
|
if (self::isDeployAdmin($user)) {
|
|
return true;
|
|
}
|
|
$access = $user->page_access;
|
|
return $access === null;
|
|
}
|
|
|
|
public static function isDeployAdmin(?User $user): bool
|
|
{
|
|
if (!$user) {
|
|
return false;
|
|
}
|
|
return self::normalizeMobile($user->mobile ?? '') === self::DEPLOY_ADMIN_MOBILE_CORE;
|
|
}
|
|
|
|
/**
|
|
* null = دسترسی کامل. آرایه = فقط کلیدهای شناختهشده.
|
|
*/
|
|
public static function normalize($value): ?array
|
|
{
|
|
if ($value === null || $value === '' || $value === false) {
|
|
return null;
|
|
}
|
|
if (is_string($value)) {
|
|
$decoded = json_decode($value, true);
|
|
$value = is_array($decoded) ? $decoded : null;
|
|
if ($value === null) {
|
|
return null;
|
|
}
|
|
}
|
|
if (!is_array($value)) {
|
|
return null;
|
|
}
|
|
|
|
$out = [];
|
|
foreach (self::LEAF_KEYS as $key) {
|
|
$row = $value[$key] ?? null;
|
|
if (!is_array($row)) {
|
|
continue;
|
|
}
|
|
$view = self::toBool($row['view'] ?? false);
|
|
$edit = self::toBool($row['edit'] ?? false);
|
|
$delete = self::toBool($row['delete'] ?? false);
|
|
$actions = null;
|
|
if (is_array($row['actions'] ?? null)) {
|
|
$actions = [];
|
|
foreach (self::ACTIONS[$key] ?? [] as $actionKey) {
|
|
$actions[$actionKey] = self::toBool($row['actions'][$actionKey] ?? false);
|
|
}
|
|
if (array_key_exists('edit', $actions)) {
|
|
$edit = $actions['edit'];
|
|
}
|
|
if (array_key_exists('delete', $actions)) {
|
|
$delete = $actions['delete'];
|
|
}
|
|
}
|
|
if ($edit || $delete || ($actions && in_array(true, $actions, true))) {
|
|
$view = true;
|
|
}
|
|
if (!$view) {
|
|
$edit = false;
|
|
$delete = false;
|
|
if (is_array($actions)) {
|
|
foreach ($actions as $actionKey => $enabled) {
|
|
$actions[$actionKey] = false;
|
|
}
|
|
}
|
|
}
|
|
if (!$view && !$edit && !$delete && !($actions && in_array(true, $actions, true))) {
|
|
continue;
|
|
}
|
|
$stored = [
|
|
'view' => $view,
|
|
'edit' => $edit,
|
|
'delete' => $delete,
|
|
];
|
|
if (is_array($actions) && $actions) {
|
|
$stored['actions'] = $actions;
|
|
}
|
|
$out[$key] = $stored;
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
public static function hasAnyView(?array $access): bool
|
|
{
|
|
if ($access === null) {
|
|
return true;
|
|
}
|
|
foreach ($access as $row) {
|
|
if (!empty($row['view'])) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static function allows(?User $user, string $routeName, string $action): bool
|
|
{
|
|
if (self::hasFullAccess($user)) {
|
|
return true;
|
|
}
|
|
$leaf = self::ALIASES[$routeName] ?? $routeName;
|
|
if (!in_array($leaf, self::LEAF_KEYS, true)) {
|
|
return false;
|
|
}
|
|
$access = $user->page_access;
|
|
if (!is_array($access)) {
|
|
return false;
|
|
}
|
|
$row = $access[$leaf] ?? null;
|
|
if (!is_array($row)) {
|
|
return false;
|
|
}
|
|
if ($action === 'delete' || $action === 'edit') {
|
|
$actions = $row['actions'] ?? null;
|
|
if (is_array($actions) && array_key_exists($action, $actions)) {
|
|
return !empty($actions[$action]);
|
|
}
|
|
return !empty($row[$action]);
|
|
}
|
|
return !empty($row['view']);
|
|
}
|
|
|
|
public static function isReadPost(string $path): bool
|
|
{
|
|
$path = strtolower(trim($path, '/'));
|
|
if (str_contains($path, '/table') || substr($path, -5) === 'table') {
|
|
return true;
|
|
}
|
|
if (str_contains($path, 'preview')) {
|
|
return true;
|
|
}
|
|
if (str_contains($path, 'get_customer_list') || str_contains($path, 'products-by-categories')) {
|
|
return true;
|
|
}
|
|
if (str_contains($path, 'mande-by-codes')) {
|
|
return true;
|
|
}
|
|
$exact = [
|
|
'getbalance',
|
|
'prices',
|
|
'prices/chart',
|
|
'gateway',
|
|
'finance',
|
|
'check_card',
|
|
'dashboard',
|
|
'dashboard/finance',
|
|
'dashboard/customer/all_sell',
|
|
'dashboard/customer/single/all_sell',
|
|
'dashboard/customer/flow',
|
|
'dashboard/customer/order-peak-hours',
|
|
'dashboard/profit-conversions',
|
|
'dashboard/montzar-reconcile',
|
|
];
|
|
if (in_array($path, $exact, true)) {
|
|
return true;
|
|
}
|
|
if (preg_match('#^customers/\d+/(referrals|funds|piggy-funds)$#', $path)) {
|
|
return true;
|
|
}
|
|
if (preg_match('#^customers/\d+/assets/\d+/(piggy-funds|transactions)$#', $path)) {
|
|
return true;
|
|
}
|
|
if (preg_match('#^customers/\d+/piggy-funds/\d+/transactions$#', $path)) {
|
|
return true;
|
|
}
|
|
if (preg_match('#^customers/\d+/gold_credit/installments$#', $path)) {
|
|
return true;
|
|
}
|
|
if (preg_match('#^chat/(focus|unfocus|ensure-webhook)$#', $path)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static function toBool($value): bool
|
|
{
|
|
if (is_bool($value)) {
|
|
return $value;
|
|
}
|
|
if (is_numeric($value)) {
|
|
return (int) $value === 1;
|
|
}
|
|
$text = strtolower(trim((string) $value));
|
|
return in_array($text, ['1', 'true', 'yes', 'on'], true);
|
|
}
|
|
|
|
private static function normalizeMobile(?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;
|
|
}
|
|
}
|