372 lines
13 KiB
PHP
372 lines
13 KiB
PHP
<?php
|
|
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Session;
|
|
|
|
/**
|
|
* Retrieve the active profile based on the session data.
|
|
*
|
|
* This function checks if the session contains 'activeProfileType' and
|
|
* 'activeProfileId'. If both are present, it attempts to find and return
|
|
* the profile using the specified type and ID. If either is missing,
|
|
* it returns null.
|
|
*
|
|
* @return mixed|null The active profile object if found, otherwise null.
|
|
*/
|
|
if (!function_exists('getActiveProfile')) {
|
|
function getActiveProfile()
|
|
{
|
|
$profileType = Session::get('activeProfileType');
|
|
$profileId = Session::get('activeProfileId');
|
|
|
|
if ($profileType && $profileId) {
|
|
return $profileType::find($profileId);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if (!function_exists('getActiveProfileType')) {
|
|
function getActiveProfileType()
|
|
{
|
|
$profileType = Session::get('activeProfileType');
|
|
$profileTypeName = class_basename($profileType);
|
|
|
|
if ($profileType && $profileTypeName) {
|
|
return $profileTypeName;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the currently authenticated web user can create payments as the active profile.
|
|
*
|
|
* Users with the coordinator role (organization-coordinator / bank-coordinator) have
|
|
* full profile access except payment execution. Only manager roles may pay.
|
|
* User profiles are always allowed to pay.
|
|
*
|
|
* @return bool
|
|
*/
|
|
if (!function_exists('canActiveProfileCreatePayments')) {
|
|
function canActiveProfileCreatePayments(): bool
|
|
{
|
|
$activeType = Session::get('activeProfileType');
|
|
$activeId = Session::get('activeProfileId');
|
|
|
|
if (!$activeType || !$activeId) {
|
|
return false;
|
|
}
|
|
|
|
// User profiles can always pay
|
|
if ($activeType === 'App\Models\User') {
|
|
return true;
|
|
}
|
|
|
|
$user = Auth::guard('web')->user();
|
|
if (!$user) {
|
|
return false;
|
|
}
|
|
|
|
$managerRoles = [
|
|
'App\Models\Organization' => "Organization\\{$activeId}\\organization-manager",
|
|
'App\Models\Bank' => "Bank\\{$activeId}\\bank-manager",
|
|
];
|
|
|
|
if (!isset($managerRoles[$activeType])) {
|
|
return false;
|
|
}
|
|
|
|
return $user->hasRole($managerRoles[$activeType]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the human-readable role label (English DB key) for a profile + web user combination.
|
|
* Returns a string suitable for passing to __() at render time.
|
|
* Stored in session as 'activeProfileRoleLabel' at switch time.
|
|
*/
|
|
if (!function_exists('getProfileRoleLabel')) {
|
|
function getProfileRoleLabel($profile, ?\App\Models\User $webUser = null): string
|
|
{
|
|
if (!$profile) {
|
|
return '';
|
|
}
|
|
|
|
$type = get_class($profile);
|
|
|
|
if ($type === \App\Models\User::class) {
|
|
return 'User';
|
|
}
|
|
|
|
if ($type === \App\Models\Admin::class) {
|
|
// Look up the Admin role the web user holds for this admin profile
|
|
if ($webUser) {
|
|
$prefix = "Admin\\{$profile->id}\\";
|
|
$spatieRole = $webUser->roles->first(fn($r) => str_starts_with($r->name, $prefix));
|
|
if ($spatieRole) {
|
|
$roleName = str_replace($prefix, '', $spatieRole->name);
|
|
$adminRole = \App\Models\AdminRole::where('name', $roleName)->first();
|
|
if ($adminRole?->label) {
|
|
return $adminRole->label;
|
|
}
|
|
}
|
|
}
|
|
return 'Administrator';
|
|
}
|
|
|
|
if ($type === \App\Models\Organization::class) {
|
|
if ($webUser) {
|
|
$prefix = "Organization\\{$profile->id}\\";
|
|
$spatieRole = $webUser->roles->first(fn($r) => str_starts_with($r->name, $prefix));
|
|
if ($spatieRole) {
|
|
$roleName = str_replace($prefix, '', $spatieRole->name);
|
|
$orgRole = \App\Models\OrganizationRole::where('name', $roleName)->first();
|
|
if ($orgRole?->label) {
|
|
return $orgRole->label;
|
|
}
|
|
}
|
|
}
|
|
return 'Organization Manager';
|
|
}
|
|
|
|
if ($type === \App\Models\Bank::class) {
|
|
if ($webUser) {
|
|
$prefix = "Bank\\{$profile->id}\\";
|
|
$spatieRole = $webUser->roles->first(fn($r) => str_starts_with($r->name, $prefix));
|
|
if ($spatieRole) {
|
|
$roleName = str_replace($prefix, '', $spatieRole->name);
|
|
$orgRole = \App\Models\OrganizationRole::where('name', $roleName)->first();
|
|
if ($orgRole?->label) {
|
|
return $orgRole->label;
|
|
}
|
|
}
|
|
}
|
|
return 'Bank Manager';
|
|
}
|
|
|
|
return '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build the session key for the active profile's location hierarchy.
|
|
* Keyed by both profile type and ID so switching profiles never serves stale data.
|
|
*/
|
|
if (!function_exists('activeProfileLocationSessionKey')) {
|
|
function activeProfileLocationSessionKey(): ?string
|
|
{
|
|
$profileType = Session::get('activeProfileType');
|
|
$profileId = Session::get('activeProfileId');
|
|
|
|
if (!$profileType || !$profileId) {
|
|
return null;
|
|
}
|
|
|
|
return 'profile_location_hierarchy_' . class_basename($profileType) . '_' . $profileId;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the active profile's resolved location hierarchy, using the session as a cache.
|
|
* Returns [] if no location is set.
|
|
*/
|
|
if (!function_exists('getActiveProfileLocationHierarchy')) {
|
|
function getActiveProfileLocationHierarchy(): array
|
|
{
|
|
$key = activeProfileLocationSessionKey();
|
|
if (!$key) {
|
|
return [];
|
|
}
|
|
|
|
if (Session::has($key)) {
|
|
return Session::get($key);
|
|
}
|
|
|
|
try {
|
|
$activeProfile = getActiveProfile();
|
|
if (!$activeProfile) {
|
|
\Illuminate\Support\Facades\Log::debug('getActiveProfileLocationHierarchy: no active profile');
|
|
return [];
|
|
}
|
|
if (!$activeProfile->locations) {
|
|
\Illuminate\Support\Facades\Log::debug('getActiveProfileLocationHierarchy: profile has no locations relation');
|
|
return [];
|
|
}
|
|
|
|
$location = $activeProfile->locations->first();
|
|
if (!$location) {
|
|
\Illuminate\Support\Facades\Log::debug('getActiveProfileLocationHierarchy: no location record found', ['profile_id' => $activeProfile->id]);
|
|
return [];
|
|
}
|
|
|
|
// Resolve full hierarchy once to sync missing IDs, then extract only what's needed.
|
|
// We store a flat array of IDs and names — no model objects — to keep the session small.
|
|
$hierarchy = $location->getCompleteHierarchy();
|
|
$locale = app()->getLocale();
|
|
|
|
\Illuminate\Support\Facades\Log::debug('getActiveProfileLocationHierarchy: hierarchy resolved', [
|
|
'country_id' => $hierarchy['country']->id ?? null,
|
|
'division_id' => $hierarchy['division']->id ?? null,
|
|
'city_id' => $hierarchy['city']->id ?? null,
|
|
'district_id' => $hierarchy['district']->id ?? null,
|
|
'locale' => $locale,
|
|
]);
|
|
|
|
$resolveName = function ($model) use ($locale) {
|
|
if (!$model) return null;
|
|
if ($model->translations && $model->translations->count() > 0) {
|
|
$t = $model->translations->where('locale', $locale)->first();
|
|
if ($t && $t->name) return $t->name;
|
|
}
|
|
return $model->name ?? null;
|
|
};
|
|
|
|
$flat = [
|
|
'country_id' => $hierarchy['country']->id ?? null,
|
|
'division_id' => $hierarchy['division']->id ?? null,
|
|
'city_id' => $hierarchy['city']->id ?? null,
|
|
'district_id' => $hierarchy['district']->id ?? null,
|
|
'country_name' => $resolveName($hierarchy['country'] ?? null),
|
|
'division_name' => $resolveName($hierarchy['division'] ?? null),
|
|
'city_name' => $resolveName($hierarchy['city'] ?? null),
|
|
'district_name' => $resolveName($hierarchy['district'] ?? null),
|
|
];
|
|
|
|
\Illuminate\Support\Facades\Log::debug('getActiveProfileLocationHierarchy: storing in session', ['key' => $key, 'flat' => $flat]);
|
|
|
|
Session::put($key, $flat);
|
|
|
|
return $flat;
|
|
} catch (\Exception $e) {
|
|
\Illuminate\Support\Facades\Log::warning('getActiveProfileLocationHierarchy failed: ' . $e->getMessage());
|
|
return [];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invalidate the session-cached location hierarchy for the active profile.
|
|
* Call this immediately after saving a profile's location.
|
|
*/
|
|
if (!function_exists('invalidateActiveProfileLocationCache')) {
|
|
function invalidateActiveProfileLocationCache(): void
|
|
{
|
|
$key = activeProfileLocationSessionKey();
|
|
if ($key) {
|
|
Session::forget($key);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the active viewer can see a bank of a given level for a specific context.
|
|
*
|
|
* Reads `banks.visibility.{level}.{viewerType}.{visibilityKey}` from the platform config.
|
|
* Returns true (visible) when the config key is absent — opt-in hiding only.
|
|
*
|
|
* @param int $bankLevel The bank's level (0, 1, 2, …)
|
|
* @param string $visibilityKey One of: profile_visible, search_visible, messenger_visible, payment_visible
|
|
* @param string|null $viewerType Explicit viewer type override; defaults to active profile type or 'guest'
|
|
* @return bool
|
|
*/
|
|
if (!function_exists('canViewerSeeBankLevel')) {
|
|
function canViewerSeeBankLevel(?int $bankLevel, string $visibilityKey, ?string $viewerType = null): bool
|
|
{
|
|
if ($bankLevel === null) {
|
|
return true;
|
|
}
|
|
|
|
if ($viewerType === null) {
|
|
$viewerType = getActiveProfileType() ?? 'guest';
|
|
}
|
|
|
|
$configured = timebank_config("banks.visibility.{$bankLevel}.{$viewerType}.{$visibilityKey}");
|
|
|
|
// If the config key is absent, default to visible (safe fallback)
|
|
if ($configured === null) {
|
|
return true;
|
|
}
|
|
|
|
return (bool) $configured;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the authenticated web user has multiple profiles (organizations, banks, admins).
|
|
* Returns true if user has 2+ profiles total (User + any elevated profiles).
|
|
*
|
|
* @return bool
|
|
*/
|
|
if (!function_exists('userHasMultipleProfiles')) {
|
|
function userHasMultipleProfiles(): bool
|
|
{
|
|
$user = Auth::guard('web')->user();
|
|
if (!$user) {
|
|
return false;
|
|
}
|
|
|
|
$profileCount = 0;
|
|
|
|
// User profile counts as 1
|
|
$profileCount++;
|
|
|
|
// Add organizations
|
|
$organizations = $user->organizations()
|
|
->where(fn($q) => $q->whereNull('deleted_at')->orWhere('deleted_at', '>', now()))
|
|
->count();
|
|
$profileCount += $organizations;
|
|
|
|
// Add banks
|
|
$banks = $user->banksManaged()
|
|
->where(fn($q) => $q->whereNull('deleted_at')->orWhere('deleted_at', '>', now()))
|
|
->count();
|
|
$profileCount += $banks;
|
|
|
|
// Add admins
|
|
$admins = $user->admins()
|
|
->where(fn($q) => $q->whereNull('deleted_at')->orWhere('deleted_at', '>', now()))
|
|
->count();
|
|
$profileCount += $admins;
|
|
|
|
return $profileCount >= 2;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolve the correct translation key for the "I/we just transferred..."
|
|
* chat message sent after a payment, based on the SENDER's profile type.
|
|
* Organizations and Banks are plural entities ("we"); Users are singular ("I").
|
|
*
|
|
* @param mixed $sender The Eloquent model that sent the payment (User/Organization/Bank).
|
|
* @return string
|
|
*/
|
|
if (!function_exists('payChatMessageKey')) {
|
|
function payChatMessageKey($sender): string
|
|
{
|
|
if ($sender instanceof \App\Models\Organization || $sender instanceof \App\Models\Bank) {
|
|
return 'messages.pay_chat_message_plural';
|
|
}
|
|
|
|
return 'messages.pay_chat_message';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the system is in maintenance mode.
|
|
*
|
|
* @return bool
|
|
*/
|
|
if (!function_exists('isMaintenanceMode')) {
|
|
function isMaintenanceMode()
|
|
{
|
|
return \Illuminate\Support\Facades\Cache::remember('system_setting_maintenance_mode', 300, function () {
|
|
$setting = \Illuminate\Support\Facades\DB::table('system_settings')
|
|
->where('key', 'maintenance_mode')
|
|
->first();
|
|
return $setting ? $setting->value === 'true' : false;
|
|
});
|
|
}
|
|
} |