Snapshot of main at 9f39a6e1e (2026-11-02)
This commit is contained in:
commit
6ccf1792d7
2580 files changed
+1040202
No files matched your search
@@ -0,0 +1,372 @@
|
||||
<?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;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user