whereRaw('LOWER(username) = ?', [$normalized])->first(); if (! $user) { $redirect = DB::table('username_redirects') ->whereRaw('LOWER(old_username) = ?', [$normalized]) ->value('new_username'); if ($redirect) { return redirect()->route('profile.show', ['username' => strtolower((string) $redirect)], 301); } abort(404); } if ($username !== strtolower((string) $user->username)) { return redirect()->route('profile.show', ['username' => strtolower((string) $user->username)], 301); } return $this->renderUserProfile($request, $user); } public function legacyById(Request $request, int $id, ?string $username = null) { $user = User::query()->findOrFail($id); return redirect()->route('profile.show', ['username' => strtolower((string) $user->username)], 301); } public function legacyByUsername(Request $request, string $username) { return redirect()->route('profile.show', ['username' => UsernamePolicy::normalize($username)], 301); } public function edit(Request $request): View { return view('profile.edit', [ 'user' => $request->user(), ]); } public function update(ProfileUpdateRequest $request, \App\Services\AvatarService $avatarService): RedirectResponse { $user = $request->user(); $validated = $request->validated(); logger()->debug('Profile update validated data', $validated); if (isset($validated['name'])) { $user->name = $validated['name']; } if (array_key_exists('username', $validated)) { $incomingUsername = UsernamePolicy::normalize((string) $validated['username']); $currentUsername = UsernamePolicy::normalize((string) ($user->username ?? '')); if ($incomingUsername !== '' && $incomingUsername !== $currentUsername) { $similar = UsernamePolicy::similarReserved($incomingUsername); if ($similar !== null && ! UsernamePolicy::hasApprovedOverride($incomingUsername, (int) $user->id)) { $this->usernameApprovalService->submit($user, $incomingUsername, 'profile_update', [ 'current_username' => $currentUsername, ]); return Redirect::back()->withErrors([ 'username' => 'This username is too similar to a reserved name and requires manual approval.', ]); } $cooldownDays = (int) config('usernames.rename_cooldown_days', 90); $isAdmin = method_exists($user, 'isAdmin') ? $user->isAdmin() : false; if (! $isAdmin && $user->username_changed_at !== null && $user->username_changed_at->gt(now()->subDays($cooldownDays))) { return Redirect::back()->withErrors([ 'username' => "Username can only be changed once every {$cooldownDays} days.", ]); } $user->username = $incomingUsername; $user->username_changed_at = now(); DB::table('username_history')->insert([ 'user_id' => (int) $user->id, 'old_username' => $currentUsername, 'changed_at' => now(), 'created_at' => now(), 'updated_at' => now(), ]); if ($currentUsername !== '') { DB::table('username_redirects')->updateOrInsert( ['old_username' => $currentUsername], [ 'new_username' => $incomingUsername, 'user_id' => (int) $user->id, 'updated_at' => now(), 'created_at' => now(), ] ); } } } if (!empty($validated['email']) && empty($user->email)) { $user->email = $validated['email']; $user->email_verified_at = null; } $user->save(); $profileUpdates = []; if (!empty($validated['about'])) $profileUpdates['about'] = $validated['about']; if (!empty($validated['web'])) { $profileUpdates['website'] = $validated['web']; } elseif (!empty($validated['homepage'])) { $profileUpdates['website'] = $validated['homepage']; } $day = $validated['day'] ?? null; $month = $validated['month'] ?? null; $year = $validated['year'] ?? null; if ($year && $month && $day) { $profileUpdates['birthdate'] = sprintf('%04d-%02d-%02d', (int)$year, (int)$month, (int)$day); } if (!empty($validated['gender'])) { $g = strtolower($validated['gender']); $map = ['m' => 'M', 'f' => 'F', 'n' => 'X', 'x' => 'X']; $profileUpdates['gender'] = $map[$g] ?? strtoupper($validated['gender']); } if (!empty($validated['country'])) $profileUpdates['country_code'] = $validated['country']; if (array_key_exists('mailing', $validated)) { $profileUpdates['mlist'] = filter_var($validated['mailing'], FILTER_VALIDATE_BOOLEAN) ? 1 : 0; } if (array_key_exists('notify', $validated)) { $profileUpdates['friend_upload_notice'] = filter_var($validated['notify'], FILTER_VALIDATE_BOOLEAN) ? 1 : 0; } if (isset($validated['signature'])) $profileUpdates['signature'] = $validated['signature']; if (isset($validated['description'])) $profileUpdates['description'] = $validated['description']; if (isset($validated['about'])) $profileUpdates['about'] = $validated['about']; if ($request->hasFile('avatar')) { try { $avatarService->storeFromUploadedFile($user->id, $request->file('avatar')); } catch (\Exception $e) { return Redirect::back()->with('error', 'Avatar processing failed: ' . $e->getMessage()); } } if ($request->hasFile('emoticon')) { $file = $request->file('emoticon'); $fname = $file->getClientOriginalName(); $path = \Illuminate\Support\Facades\Storage::disk('public')->putFileAs('user-emoticons/'.$user->id, $file, $fname); try { \Illuminate\Support\Facades\DB::table('users')->where('id', $user->id)->update(['eicon' => $fname]); } catch (\Exception $e) {} } if ($request->hasFile('photo')) { $file = $request->file('photo'); $fname = $file->getClientOriginalName(); $path = \Illuminate\Support\Facades\Storage::disk('public')->putFileAs('user-picture/'.$user->id, $file, $fname); if (\Illuminate\Support\Facades\Schema::hasTable('user_profiles')) { $profileUpdates['cover_image'] = $fname; } else { try { \Illuminate\Support\Facades\DB::table('users')->where('id', $user->id)->update(['picture' => $fname]); } catch (\Exception $e) {} } } try { if (\Illuminate\Support\Facades\Schema::hasTable('user_profiles')) { if (!empty($profileUpdates)) { \Illuminate\Support\Facades\DB::table('user_profiles')->updateOrInsert(['user_id' => $user->id], $profileUpdates); } } else { if (!empty($profileUpdates)) { \Illuminate\Support\Facades\DB::table('users')->where('id', $user->id)->update($profileUpdates); } } } catch (\Exception $e) { logger()->error('Profile update error: '.$e->getMessage()); } return Redirect::route('dashboard.profile')->with('status', 'profile-updated'); } public function destroy(Request $request): RedirectResponse { $request->validateWithBag('userDeletion', [ 'password' => ['required', 'current_password'], ]); $user = $request->user(); Auth::logout(); $user->delete(); $request->session()->invalidate(); $request->session()->regenerateToken(); return Redirect::to('/'); } public function password(Request $request): RedirectResponse { $request->validate([ 'current_password' => ['required', 'current_password'], 'password' => ['required', 'confirmed', PasswordRule::min(8)], ]); $user = $request->user(); $user->password = Hash::make($request->input('password')); $user->save(); return Redirect::route('dashboard.profile')->with('status', 'password-updated'); } private function renderUserProfile(Request $request, User $user) { $isOwner = Auth::check() && Auth::id() === $user->id; $perPage = 24; $artworks = $this->artworkService->getArtworksByUser($user->id, $isOwner, $perPage) ->through(function (Artwork $art) { $present = \App\Services\ThumbnailPresenter::present($art, 'md'); return (object) [ 'id' => $art->id, 'name' => $art->title, 'picture' => $art->file_name, 'datum' => $art->published_at, 'thumb' => $present['url'], 'thumb_srcset' => $present['srcset'] ?? $present['url'], 'uname' => $art->user->name ?? 'Skinbase', ]; }); $legacyUser = (object) [ 'user_id' => $user->id, 'uname' => $user->username ?? $user->name, 'name' => $user->name, 'real_name' => $user->name, 'icon' => DB::table('user_profiles')->where('user_id', $user->id)->value('avatar_hash'), 'about_me' => $user->bio ?? null, ]; return response()->view('legacy.profile', [ 'user' => $legacyUser, 'artworks' => $artworks, 'page_title' => 'Profile: ' . ($legacyUser->uname ?? ''), 'page_canonical' => url('/@' . strtolower((string) ($user->username ?? ''))), ]); } }