user(); if (!$webUser) { abort(403, 'Unauthorized action.'); } $authorized = ($activeProfile instanceof \App\Models\User && ($webUser->can('manage users') || $webUser->id === $activeProfile->id)) || ($activeProfile instanceof \App\Models\Organization && ($webUser->can('manage organizations') || $webUser->hasRole('Organization\\' . $activeProfile->id . '\\organization-manager') || $webUser->organizations()->where('organization_user.organization_id', $activeProfile->id)->exists())) || ($activeProfile instanceof \App\Models\Bank && ($webUser->can('manage banks') || $webUser->hasRole('Bank\\' . $activeProfile->id . '\\bank-manager') || $webUser->banksManaged()->where('bank_user.bank_id', $activeProfile->id)->exists())) || ($activeProfile instanceof \App\Models\Admin && ($webUser->can('manage admins') || $webUser->hasRole('Admin\\' . $activeProfile->id . '\\admin') || $webUser->admins()->where('admin_user.admin_id', $activeProfile->id)->exists())); if (!$authorized) { abort(403, 'Unauthorized action.'); } $this->state = array_merge([ 'email' => $activeProfile->email, 'full_name' => $activeProfile->full_name, ], $activeProfile->withoutRelations()->toArray()); } /** * Update the active profile's profile information. * * @param \Laravel\Fortify\Contracts\UpdatesUserProfileInformation $updater * @return void */ public function updateProfileInformation() { $this->resetErrorBag(); $activeProfile = getActiveProfile(); // CRITICAL SECURITY: Validate user has ownership/access to this profile \App\Helpers\ProfileAuthorizationHelper::authorize($activeProfile); // Determine the profile type and table $profileType = get_class($activeProfile); $modelKey = match ($profileType) { 'App\Models\User' => 'user', 'App\Models\Organization' => 'organization', 'App\Models\Bank' => 'bank', 'App\Models\Admin' => 'admin', default => 'user', }; $tableName = (new $profileType())->getTable(); // Get validation rules from platform config $emailRules = timebank_config("rules.profile_{$modelKey}.email"); $fullNameRules = timebank_config("rules.profile_{$modelKey}.full_name"); $photoRules = timebank_config("rules.profile_{$modelKey}.profile_photo"); // Convert string rules to arrays if needed if (is_string($emailRules)) { $emailRules = explode('|', $emailRules); } if (is_string($fullNameRules)) { $fullNameRules = explode('|', $fullNameRules); } if (is_string($photoRules)) { $photoRules = explode('|', $photoRules); } // Process email rules to handle unique constraint for current profile $processedEmailRules = []; foreach ($emailRules as $rule) { if (is_string($rule) && \Illuminate\Support\Str::startsWith(trim($rule), 'unique:')) { // Check if this is the unique rule for the current table if (preg_match("/^unique:{$tableName},email(,|$)/", trim($rule))) { // Replace with a Rule object that ignores current profile $processedEmailRules[] = \Illuminate\Validation\Rule::unique($tableName, 'email')->ignore($activeProfile->id); } else { // Keep unique rules for other tables $processedEmailRules[] = $rule; } } else { $processedEmailRules[] = $rule; } } // Process full_name rules to handle unique constraint for current profile $processedFullNameRules = []; if ($fullNameRules) { foreach ($fullNameRules as $rule) { if (is_string($rule) && \Illuminate\Support\Str::startsWith(trim($rule), 'unique:')) { // Check if this is a unique rule for the current table (any column) if (preg_match("/^unique:{$tableName},(\w+)(,|$)/", trim($rule), $matches)) { $column = $matches[1]; // Replace with a Rule object that ignores current profile $processedFullNameRules[] = \Illuminate\Validation\Rule::unique($tableName, $column)->ignore($activeProfile->id); } else { // Keep unique rules for other tables $processedFullNameRules[] = $rule; } } else { $processedFullNameRules[] = $rule; } } } // Prepare validation rules $rules = [ 'state.email' => $processedEmailRules, ]; // Add full_name validation for non-User profiles if (!($activeProfile instanceof \App\Models\User) && $processedFullNameRules) { $rules['state.full_name'] = $processedFullNameRules; } // Add photo validation if a photo is being uploaded if ($this->photo && $photoRules) { $rules['photo'] = $photoRules; } // Validate the input $this->validate($rules, [ 'state.email.required' => __('The email field is required.'), 'state.email.email' => __('Please enter a valid email address.'), 'state.email.unique' => __('This email address is already in use.'), 'state.full_name.required' => __('The full name field is required.'), 'state.full_name.max' => __('The full name must not exceed the maximum length.'), 'photo.image' => __('The file must be an image.'), 'photo.max' => __('The image size exceeds the maximum allowed.'), ]); // Check if the email has changed $emailChanged = $this->state['email'] !== $activeProfile->email; if ($this->photo) { // Delete old file if it doesn't start with "app-images/" (as those are default images) if ($activeProfile->profile_photo_path && !Str::startsWith($activeProfile->profile_photo_path, 'app-images/')) { Storage::disk('public')->delete($activeProfile->profile_photo_path); } // Store the new file $photoPath = $this->photo->store('profile-photos', 'public'); $this->state['profile_photo_path'] = $photoPath; } // Remove protected fields from state to prevent changes $updateData = $this->state; unset($updateData['name']); // Username is always read-only // Full name is read-only for Users, but editable for Organizations, Banks, and Admins if ($activeProfile instanceof \App\Models\User) { unset($updateData['full_name']); } // Update records of active profile $activeProfile->update($updateData); // Refresh the component state with the updated model data $activeProfile = $activeProfile->fresh(); $this->state = $activeProfile->toArray(); $this->state['email'] = $activeProfile->email; $this->state['full_name'] = $activeProfile->full_name; // Update the session variable so the Blade view can display the new photo session(['activeProfilePhoto' => $this->state['profile_photo_path']]); // Send email verification if the email has changed if ($emailChanged) { $activeProfile->forceFill(['email_verified_at' => null])->save(); $activeProfile->sendEmailVerificationNotification(); // Refresh state after email verification changes $activeProfile = $activeProfile->fresh(); $this->state = $activeProfile->toArray(); $this->state['email'] = $activeProfile->email; $this->state['full_name'] = $activeProfile->full_name; } if (isset($this->photo)) { return redirect()->route('profile.settings'); } $this->dispatch('saved'); } /** * Delete active profile's profile photo. * * @return void */ public function deleteProfilePhoto() { $activeProfile = getActiveProfile(); // CRITICAL SECURITY: Validate user has ownership/access to this profile \App\Helpers\ProfileAuthorizationHelper::authorize($activeProfile); // If the existing photo path is not one of the default images, delete it if ($activeProfile->profile_photo_path && !Str::startsWith($activeProfile->profile_photo_path, 'app-images/')) { Storage::disk('public')->delete($activeProfile->profile_photo_path); } // Set the profile photo path to the configured default in your config file $defaultPath = timebank_config('profiles.' . strtolower(getActiveProfileType()) . '.profile_photo_path_default'); $this->state['profile_photo_path'] = $defaultPath; // Update the active profile’s record $activeProfile->update(['profile_photo_path' => $defaultPath]); // Refresh the component state with the updated model data $this->state = $activeProfile->fresh()->toArray(); // Update the session variable so the Blade view can display the new photo session(['activeProfilePhoto' => $defaultPath]); redirect()->route('profile.settings'); // Dispatch any events if desired, for example: $this->dispatch('saved'); $this->dispatch('refresh-navigation-menu'); } /** * Send the email verification. * * @return void */ public function sendEmailVerification() { $activeProfile = getActiveProfile(); // CRITICAL SECURITY: Validate user has ownership/access to this profile \App\Helpers\ProfileAuthorizationHelper::authorize($activeProfile); $activeProfile->sendEmailVerificationNotification(); $this->verificationLinkSent = true; } /** * Get the current active profile of the application. * * @return mixed */ public function getUserProperty() { return getActiveProfile(); } public function render() { return view('livewire.profile.update-settings-form'); } }