Initial commit

This commit is contained in:
Ronald Huynen
2026-03-23 21:37:59 +01:00
commit 2547717edb
2193 changed files with 972171 additions and 0 deletions

View File

@@ -0,0 +1,525 @@
<?php
namespace App\Http\Controllers;
use App\Models\Bank;
use App\Models\Organization;
use App\Models\User;
use App\Traits\ActiveStatesTrait;
use App\Traits\LocationTrait;
use App\Traits\ProfilePermissionTrait;
use Illuminate\Http\Request;
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;
class ProfileController extends Controller
{
use ActiveStatesTrait;
use LocationTrait;
use ProfilePermissionTrait;
public function show($type, $id)
{
if (strtolower($type) === strtolower(__('user'))) {
return $this->showUser($id);
}
if (strtolower($type) === strtolower(__('organization'))) {
return $this->showOrganization($id);
}
if (strtolower($type) === strtolower(__('bank'))) {
return $this->showBank($id);
}
return view('profile.not_found');
}
public function showActive()
{
$profile = getActiveProfile();
return $this->show(__(class_basename($profile)), $profile->id);
}
public function showUser($id)
{
$user = User::select([
'id',
'name',
'full_name',
'profile_photo_path',
'about',
'about_short',
'motivation',
'date_of_birth',
'website',
'phone_public',
'phone',
'cyclos_skills',
'lang_preference',
'email_verified_at',
'created_at',
'last_login_at',
'love_reactant_id',
'inactive_at',
'deleted_at'
])
->with([
'organizations:id,name,profile_photo_path',
'accounts:id,name,accountable_type,accountable_id',
'languages:id,name,lang_code,flag',
'tags:tag_id',
'socials:id,name,icon,urL_structure',
'locations.district.translations:district_id,name',
'locations.city.translations:city_id,name',
'locations.division.translations:division_id,name',
'locations.country.translations:country_id,name',
'loveReactant.reactions.reacter.reacterable',
'loveReactant.reactionCounters',
])
->find($id);
if (!$user) {
return view('profile.not_found');
}
$states = $this->getActiveStates($user);
$canManageProfiles = $this->getCanManageProfiles();
$canViewIncompleteProfiles = $this->canViewIncompleteProfiles();
$isViewingOwnProfile = getActiveProfile() &&
get_class(getActiveProfile()) === User::class &&
getActiveProfile()->id === $user->id;
// For admins/banks: Always show incomplete label if profile is incomplete (ignore config)
if ($canManageProfiles) {
$states['hidden'] = false;
$states['inactiveLabel'] = !$user->isActive();
$states['inactiveSince'] = $user->inactive_at
? \Illuminate\Support\Carbon::parse($user->inactive_at)->diffForHumans()
: '';
$states['emailUnverifiedLabel'] = !$user->isEmailVerified();
$states['incompleteLabel'] = $user->hasIncompleteProfile($user);
}
// When viewing own profile, never hide but show all labels
if ($isViewingOwnProfile) {
$states['hidden'] = false;
$states['inactiveLabel'] = !$user->isActive();
$states['inactiveSince'] = $user->inactive_at
? \Illuminate\Support\Carbon::parse($user->inactive_at)->diffForHumans()
: '';
$states['emailUnverifiedLabel'] = !$user->isEmailVerified();
$states['incompleteLabel'] = $user->hasIncompleteProfile($user);
// Show incomplete warning modal when viewing own profile
$states['showIncompleteWarning'] = timebank_config('profile_incomplete.show_warning_modal') &&
$user->hasIncompleteProfile($user);
}
// Check if profile should be hidden
if ($states['hidden'] && !$canManageProfiles && !$isViewingOwnProfile) {
// Only allow Admin and Bank profiles to view incomplete-only profiles
// Inactive profiles should be hidden from everyone except admins/banks
$isIncompleteOnly = ($states['isIncomplete'] ?? false) &&
!($states['inactive'] ?? false) &&
!($states['emailUnverifiedLabel'] ?? false);
// Show profile only if it's incomplete-only AND viewer is admin/bank
if (!($isIncompleteOnly && $canViewIncompleteProfiles)) {
return view('profile.not_found');
}
}
$user->reactionCounter = $user->loveReactant->reactionCounters->first() ? (int)$user->loveReactant->reactionCounters->first()->weight : null;
$profile = $user;
$header = __('Personal profile');
return $profile != null
? view('profile.show', array_merge(compact('profile', 'header'), $states))
: abort(403);
}
public function showOrganization($id)
{
$organization = Organization::select([
'id',
'name',
'profile_photo_path',
'about',
'motivation',
'website',
'phone_public',
'phone',
'cyclos_skills',
'lang_preference',
'email_verified_at',
'created_at',
'last_login_at',
'love_reactant_id',
'inactive_at',
'deleted_at'
])
->with([
'managers:id,name,profile_photo_path',
'accounts:id,name,accountable_type,accountable_id',
'languages:id,name,lang_code,flag',
'tags:tag_id',
'socials:id,name,icon,urL_structure',
'locations.district.translations:district_id,name',
'locations.city.translations:city_id,name',
'locations.division.translations:division_id,name',
'locations.country.translations:country_id,name',
'loveReactant.reactions.reacter.reacterable',
// 'loveReactant.reactions.type',
'loveReactant.reactionCounters',
// 'loveReactant.reactionTotal',
])
->find($id);
if (!$organization) {
return view('profile.not_found');
}
$states = $this->getActiveStates($organization);
$canManageProfiles = $this->getCanManageProfiles();
$canViewIncompleteProfiles = $this->canViewIncompleteProfiles();
$isViewingOwnProfile = getActiveProfile() &&
get_class(getActiveProfile()) === Organization::class &&
getActiveProfile()->id === $organization->id;
// For admins/banks: Always show incomplete label if profile is incomplete (ignore config)
if ($canManageProfiles) {
$states['hidden'] = false;
$states['inactiveLabel'] = !$organization->isActive();
$states['inactiveSince'] = $organization->inactive_at
? \Illuminate\Support\Carbon::parse($organization->inactive_at)->diffForHumans()
: '';
$states['emailUnverifiedLabel'] = !$organization->isEmailVerified();
$states['incompleteLabel'] = $organization->hasIncompleteProfile($organization);
}
// When viewing own profile, never hide but show all labels
if ($isViewingOwnProfile) {
$states['hidden'] = false;
$states['inactiveLabel'] = !$organization->isActive();
$states['inactiveSince'] = $organization->inactive_at
? \Illuminate\Support\Carbon::parse($organization->inactive_at)->diffForHumans()
: '';
$states['emailUnverifiedLabel'] = !$organization->isEmailVerified();
$states['incompleteLabel'] = $organization->hasIncompleteProfile($organization);
// Show incomplete warning modal when viewing own profile
$states['showIncompleteWarning'] = timebank_config('profile_incomplete.show_warning_modal') &&
$organization->hasIncompleteProfile($organization);
}
// Check if profile should be hidden
if ($states['hidden'] && !$canManageProfiles && !$isViewingOwnProfile) {
// Only allow Admin and Bank profiles to view incomplete-only profiles
// Inactive profiles should be hidden from everyone except admins/banks
$isIncompleteOnly = ($states['isIncomplete'] ?? false) &&
!($states['inactive'] ?? false) &&
!($states['emailUnverifiedLabel'] ?? false);
// Show profile only if it's incomplete-only AND viewer is admin/bank
if (!($isIncompleteOnly && $canViewIncompleteProfiles)) {
return view('profile.not_found');
}
}
$organization->reactionCounter = $organization->loveReactant->reactionCounters->first() ? (int)$organization->loveReactant->reactionCounters->first()->weight : null;
$profile = $organization;
$header = __('Organization profile');
return $profile != null
? view('profile.show', array_merge(compact('profile', 'header'), $states))
: abort(403);
}
public function showBank($id)
{
$bank = Bank::select([
'id',
'name',
'profile_photo_path',
'about',
'motivation',
'website',
'phone_public',
'phone',
'cyclos_skills',
'lang_preference',
'email_verified_at',
'created_at',
'last_login_at',
'love_reactant_id',
'inactive_at',
'deleted_at'
])
->with([
'managers:id,name,profile_photo_path',
'accounts:id,name,accountable_type,accountable_id',
'languages:id,name,lang_code,flag',
'tags:tag_id',
'socials:id,name,icon,urL_structure',
'locations.district.translations:district_id,name',
'locations.city.translations:city_id,name',
'locations.division.translations:division_id,name',
'locations.country.translations:country_id,name',
'loveReactant.reactions.reacter.reacterable',
// 'loveReactant.reactions.type',
'loveReactant.reactionCounters',
// 'loveReactant.reactionTotal',
])
->find($id);
if (!$bank) {
return view('profile.not_found');
}
$states = $this->getActiveStates($bank);
$canManageProfiles = $this->getCanManageProfiles();
$canViewIncompleteProfiles = $this->canViewIncompleteProfiles();
$isViewingOwnProfile = getActiveProfile() &&
get_class(getActiveProfile()) === Bank::class &&
getActiveProfile()->id === $bank->id;
// For admins/banks: Always show incomplete label if profile is incomplete (ignore config)
if ($canManageProfiles) {
$states['hidden'] = false;
$states['inactiveLabel'] = !$bank->isActive();
$states['inactiveSince'] = $bank->inactive_at
? \Illuminate\Support\Carbon::parse($bank->inactive_at)->diffForHumans()
: '';
$states['emailUnverifiedLabel'] = !$bank->isEmailVerified();
$states['incompleteLabel'] = $bank->hasIncompleteProfile($bank);
}
// When viewing own profile, never hide but show all labels
if ($isViewingOwnProfile) {
$states['hidden'] = false;
$states['inactiveLabel'] = !$bank->isActive();
$states['inactiveSince'] = $bank->inactive_at
? \Illuminate\Support\Carbon::parse($bank->inactive_at)->diffForHumans()
: '';
$states['emailUnverifiedLabel'] = !$bank->isEmailVerified();
$states['incompleteLabel'] = $bank->hasIncompleteProfile($bank);
// Show incomplete warning modal when viewing own profile
$states['showIncompleteWarning'] = timebank_config('profile_incomplete.show_warning_modal') &&
$bank->hasIncompleteProfile($bank);
}
// Check if profile should be hidden
if ($states['hidden'] && !$canManageProfiles && !$isViewingOwnProfile) {
// Only allow Admin and Bank profiles to view incomplete-only profiles
// Inactive profiles should be hidden from everyone except admins/banks
$isIncompleteOnly = ($states['isIncomplete'] ?? false) &&
!($states['inactive'] ?? false) &&
!($states['emailUnverifiedLabel'] ?? false);
// Show profile only if it's incomplete-only AND viewer is admin/bank
if (!($isIncompleteOnly && $canViewIncompleteProfiles)) {
return view('profile.not_found');
}
}
$bank->reactionCounter = $bank->loveReactant->reactionCounters->first() ? (int)$bank->loveReactant->reactionCounters->first()->weight : null;
$profile = $bank;
$header = __('Bank profile');
return $profile != null
? view('profile.show', array_merge(compact('profile', 'header'), $states))
: abort(403);
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit()
{
$type = strtolower(class_basename(getActiveProfile()));
$profile = getActiveProfile();
// Check if profile is incomplete - show warning if config allows
$showIncompleteWarning = false;
if (timebank_config('profile_incomplete.show_warning_modal') &&
method_exists($profile, 'hasIncompleteProfile') &&
$profile->hasIncompleteProfile($profile)) {
$showIncompleteWarning = true;
}
return view('profile-' . $type . '.edit', compact('showIncompleteWarning'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
/**
* Redirects the authenticated user to their profile settings page with locale-specific routing.
*
* This method is used by the route profile.settings.no_locale.
* The email footer template uses this route because when sending the locale of the
* recipient is unknown.
*
* @return \Illuminate\Http\RedirectResponse Redirects to the localized profile settings page.
*/
public function settingsNoLocale()
{
$user = auth()->user();
$locale = $user->lang_preference ?? config('app.fallback_locale');
// Load the route translations for the specific locale
$routeTranslations = include resource_path("lang/{$locale}/routes.php");
$translatedRoute = $routeTranslations['profile.settings'] ?? 'profile/settings';
$localizedUrl = url("/{$locale}/{$translatedRoute}");
return redirect($localizedUrl . '#message_settings');
}
/**
* Show the user settings screen.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function settings(Request $request)
{
$type = strtolower(class_basename(getActiveProfile()));
if ($type === 'user') {
return view('profile.settings', [
'request' => $request,
'user' => $request->user(),
]);
} else {
return view('profile-' . $type . '.settings');
}
}
public function manage()
{
return view('profiles.manage');
}
/**
* Determines the inactive status flags for a given profile.
*
* @param mixed $profile The profile object to check for inactivity.
* @return array An associative array containing:
* - 'inactive' (bool): Whether the profile is inactive.
* - 'hidden' (bool): Whether the profile should be hidden.
* - 'inactiveLabel' (bool): Whether the profile should be labeled as inactive.
* - 'inactiveSince' (string): Human-readable duration since the profile became inactive.
*/
private function getActiveStates($profile)
{
$inactive = false;
$hidden = false;
$inactiveLabel = false;
$inactiveSince = '';
$emailUnverifiedLabel = false;
$isIncomplete = false;
$incompleteLabel = false;
$noExchangesYetLabel = false;
$removedSince = '';
if (method_exists($profile, 'isActive') && !$profile->isActive()) {
$inactive = true;
if (timebank_config('profile_inactive.profile_hidden')) {
$hidden = true;
}
if (timebank_config('profile_inactive.profile_labeled')) {
$inactiveLabel = true;
$inactiveSince = $profile->inactive_at
? \Illuminate\Support\Carbon::parse($profile->inactive_at)->diffForHumans()
: '';
}
}
if (method_exists($profile, 'isEmailVerified') && !$profile->isEmailVerified()) {
$emailUnverifiedLabel = false;
if (timebank_config('profile_email_unverified.profile_hidden')) {
$hidden = true;
}
if (timebank_config('profile_email_unverified.profile_labeled')) {
$emailUnverifiedLabel = true;
}
}
if (method_exists($profile, 'hasIncompleteProfile') && $profile->hasIncompleteProfile($profile)) {
$isIncomplete = true;
if (timebank_config('profile_incomplete.profile_hidden')) {
$hidden = true;
}
if (timebank_config('profile_incomplete.profile_labeled')) {
$incompleteLabel = true;
}
}
if (
timebank_config('profile_incomplete.no_exchanges_yet_label') &&
method_exists($profile, 'hasNeverReceivedTransaction') &&
$profile->hasNeverReceivedTransaction($profile)
) {
$noExchangesYetLabel = true;
}
if (
(method_exists($profile, 'isRemoved') && $profile->isRemoved()) ||
(!empty($profile->deleted_at) && \Carbon\Carbon::parse($profile->deleted_at)->isPast())
) {
$hidden = true;
$removedSince = !empty($profile->deleted_at)
? \Carbon\Carbon::parse($profile->deleted_at)->diffForHumans()
: '';
}
return compact('inactive', 'hidden', 'inactiveLabel', 'inactiveSince', 'emailUnverifiedLabel', 'isIncomplete', 'incompleteLabel', 'noExchangesYetLabel', 'removedSince');
}
}