validate([ 'email' => 'required|email', 'password' => 'required|string', ]); if (Auth::attempt($credentials)) { $request->session()->regenerate(); return redirect()->route('dashboard.index'); } return back()->withErrors([ 'email' => 'Email atau password salah.', ])->withInput(); } public function logout(Request $request) { Auth::logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); return redirect()->route('login'); } public function showForgotPassword() { return view('auth.forgot-password'); } public function sendResetLink(Request $request) { $request->validate([ 'email' => ['required', 'email'], ]); // Cek apakah email terdaftar $user = User::where('email', $request->email)->first(); if (!$user) { return back()->withErrors(['email' => 'Email tidak ditemukan.'])->withInput($request->only('email')); } // Generate token random $token = \Illuminate\Support\Str::random(60); // Simpan token ke database dengan hashing DB::table('password_reset_tokens')->updateOrInsert( ['email' => $request->email], [ 'token' => Hash::make($token), 'created_at' => now(), ] ); // Kirim email dengan link reset password dan token try { Mail::to($request->email)->send(new SendResetPasswordLink($request->email, $token)); } catch (\Exception $e) { return back()->withErrors(['email' => 'Gagal mengirim email. Silakan coba lagi.'])->withInput($request->only('email')); } return redirect()->route('password.verify.form', [ 'email' => $request->email, ])->with('status', 'Email reset password telah dikirim. Silakan cek email Anda untuk token. Token berlaku selama 60 menit.'); } public function showVerifyToken(Request $request) { if (!$request->filled('email')) { return redirect()->route('password.request'); } // Cek apakah ada token untuk email ini yang masih berlaku $resetToken = DB::table('password_reset_tokens') ->where('email', $request->email) ->where('created_at', '>=', now()->subMinutes(60)) ->first(); if (!$resetToken) { return redirect()->route('password.request') ->withErrors(['email' => 'Token tidak ditemukan atau sudah kadaluarsa.']); } $email = $request->email; return view('auth.verify-token', compact('email')); } public function verifyToken(Request $request) { $request->validate([ 'email' => ['required', 'email'], 'token' => ['required', 'string'], ]); // Cek token di database $resetToken = DB::table('password_reset_tokens') ->where('email', $request->email) ->where('created_at', '>=', now()->subMinutes(60)) ->first(); if (!$resetToken) { return back()->withErrors(['email' => 'Token tidak ditemukan atau sudah kadaluarsa.']); } // Verifikasi token if (!Hash::check($request->token, $resetToken->token)) { return back()->withErrors(['token' => 'Token tidak sesuai.'])->withInput(); } // Token valid, arahkan ke form confirm password return redirect()->route('password.confirm.form', [ 'email' => $request->email, ])->with('status', 'Token terverifikasi. Silakan masukkan password baru.'); } public function showConfirmPassword(Request $request) { if (!$request->filled('email')) { return redirect()->route('password.request') ->withErrors(['email' => 'Link konfirmasi password tidak valid.']); } $email = $request->email; return view('auth.confirm-password', compact('email')); } public function updatePassword(Request $request) { $request->validate([ 'email' => ['required', 'email'], 'password' => ['required', 'confirmed', 'min:8'], ], [ 'password.confirmed' => 'Konfirmasi password tidak sesuai.', 'password.min' => 'Password minimal 8 karakter.', ]); // Cek user $user = User::where('email', $request->email)->first(); if (!$user) { return back()->withErrors(['email' => 'Email tidak ditemukan.']); } // Jika user adalah admin (forgot password langsung ke confirm password) $isAdmin = $user->role === 'admin'; // Jika user bukan admin, cek token (dari verify token flow) if (!$isAdmin) { $resetToken = DB::table('password_reset_tokens') ->where('email', $request->email) ->where('created_at', '>=', now()->subMinutes(60)) ->first(); if (!$resetToken) { return back()->withErrors(['email' => 'Session reset password telah kadaluarsa. Silakan lakukan forgot password lagi.']); } } // Update password $user->password = Hash::make($request->password); $user->save(); // Hapus token dari database jika ada DB::table('password_reset_tokens') ->where('email', $request->email) ->delete(); $message = $isAdmin ? 'Password admin berhasil diperbarui. Silakan login.' : 'Password berhasil direset. Silakan login dengan password baru Anda.'; return redirect()->route('login')->with('success', $message); } public function showSetting() { $user = Auth::user(); return view('pages.setting.setting', compact('user')); } public function updateSetting(Request $request) { $user = Auth::user(); if (!$user) { return redirect()->route('login'); } if ($request->input('form_type') === 'profile') { return $this->updateSettingProfile($request, $user); } if ($request->input('form_type') === 'password') { return $this->updateSettingPassword($request, $user); } return back()->withErrors(['form' => 'Form tidak valid.']); } public function deleteProfilePicture() { $user = Auth::user(); if (!$user instanceof User) { return redirect()->route('login'); } if (empty($user->profile_picture)) { return back()->with('success', 'Foto profil sudah kosong.'); } $rolePath = $user->role ?? 'others'; if (!empty($user->profile_picture) && Storage::disk('public')->exists('profile_pictures/' . $rolePath . '/' . $user->profile_picture)) { Storage::disk('public')->delete('profile_pictures/' . $rolePath . '/' . $user->profile_picture); } $user->profile_picture = null; $user->save(); return back()->with('success', 'Foto profil berhasil dihapus.'); } private function updateSettingProfile(Request $request, User $user) { $request->validate([ 'email' => 'required|email|max:255|unique:users,email,' . $user->id, 'profile_picture' => 'nullable|image|mimes:jpeg,png,jpg,gif,webp|max:2048', ]); $user->email = $request->email; if ($request->hasFile('profile_picture')) { $rolePath = $user->role ?? 'others'; if (!empty($user->profile_picture) && Storage::disk('public')->exists('profile_pictures/' . $rolePath . '/' . $user->profile_picture)) { Storage::disk('public')->delete('profile_pictures/' . $rolePath . '/' . $user->profile_picture); } $image = $request->file('profile_picture'); $imageName = $image->hashName(); Storage::disk('public')->putFileAs('profile_pictures/' . $rolePath, $image, $imageName); $user->profile_picture = $imageName; } $user->save(); return back()->with('success', 'Email dan gambar profil berhasil diperbarui.'); } private function updateSettingPassword(Request $request, User $user) { $request->validate([ 'current_password' => 'required|string', 'new_password' => 'required|string|min:8|confirmed', ], [ 'new_password.confirmed' => 'Konfirmasi password baru tidak cocok.', ]); if (!Hash::check($request->current_password, $user->password)) { return back()->withErrors([ 'current_password' => 'Password saat ini tidak sesuai.', ]); } $user->password = Hash::make($request->new_password); $user->save(); return back()->with('success', 'Password berhasil diperbarui.'); } // start register student public function registerStudent(Request $request) { try { $request->validate([ 'name' => 'required|string|max:255', 'email' => 'required|string|email|max:255|unique:users', 'password' => 'required|string|min:6', 'nisn' => 'required|digits:10|unique:students,nisn', 'id_class' => 'required|integer', 'entry_year' => 'required|integer', 'profile_picture' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048', 'pictures' => 'nullable|string', ]); // Proses upload gambar jika ada $imageName = null; if ($request->hasFile('profile_picture')) { $image = $request->file('profile_picture'); $imageName = $image->hashName(); Storage::disk('public')->putFileAs('profile_pictures/student', $image, $imageName); } // Proses 3 foto webcam $picturesNames = []; if ($request->filled('pictures')) { $photosData = $request->pictures; $photoArray = explode('|||', $photosData); // Split dengan ||| foreach ($photoArray as $index => $photo) { if (!empty($photo)) { $imageData = $photo; if (strpos($imageData, ',') !== false) { list($type, $imageData) = explode(',', $imageData); $imageData = base64_decode($imageData); } else { $imageData = base64_decode($imageData); } if ($imageData !== false) { $picturesName = 'webcam_' . time() . '_' . ($index + 1) . '_' . uniqid() . '.png'; Storage::disk('public')->put('photo-webcam/' . $picturesName, $imageData); $picturesNames[] = $picturesName; } } } } $user = User::create([ 'email' => $request->email, 'password' => Hash::make($request->password), 'role' => 'student', 'profile_picture' => $imageName, ]); $user->student()->create([ 'id_user' => $user->id, 'name' => $request->name, 'nisn' => $request->nisn, 'id_class' => $request->id_class, 'entry_year' => $request->entry_year, 'pictures' => !empty($picturesNames) ? implode(',', $picturesNames) : null, ]); // Redirect kembali ke identitas siswa dengan pesan sukses return redirect()->route('akun.indentitas_siswa')->with('success', 'Data siswa berhasil ditambahkan.'); } catch (ValidationException $e) { return redirect()->back() ->withErrors($e->errors()) ->withInput() ->with('open_modal', 'tambah'); } catch (\Exception $e) { return redirect()->back() ->with('error', 'Registrasi siswa gagal. Silakan coba lagi.') ->withInput() ->with('open_modal', 'tambah'); } } public function identitasSiswa() { $students = Student::with(['user', 'class'])->get(); $classes = Classes::all(); return view('pages.akun.indentitas_siswa', compact('students', 'classes')); } public function destroyStudent($id) { $student = Student::findOrFail($id); // Hapus semua foto webcam jika ada if ($student->pictures) { $photos = explode(',', $student->pictures); foreach ($photos as $photo) { if (!empty($photo)) { if (Storage::disk('public')->exists('photo-webcam/' . $photo)) { Storage::disk('public')->delete('photo-webcam/' . $photo); } } } } // Hapus user terkait (akan otomatis hapus student jika foreign key cascade) if ($student->user) { $student->user->delete(); } else { $student->delete(); } return redirect()->route('akun.indentitas_siswa')->with('success', 'Data siswa berhasil dihapus.'); } public function updateStudent(Request $request, $id) { try { // Jika massal (hanya update kelas) if ($request->has('id_class') && !$request->has('name')) { $student = Student::findOrFail($id); $student->id_class = $request->id_class; $student->save(); return redirect()->route('akun.indentitas_siswa')->with('success', 'Kelas siswa berhasil diupdate.'); } // Update lengkap (dari modal edit) $student = Student::findOrFail($id); $user = $student->user; $request->validate([ 'name' => 'required|string|max:255', 'email' => 'required|string|email|max:255|unique:users,email,' . $user->id, 'nisn' => 'required|digits:10|unique:students,nisn,' . $student->id, 'id_class' => 'required|integer', 'entry_year' => 'required|integer', 'profile_picture' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048', 'pictures_edit' => 'nullable|string', 'delete_webcam_photo' => 'nullable|string', ]); $user->email = $request->email; if ($request->hasFile('profile_picture')) { // Hapus foto lama jika ada if ($user->profile_picture && Storage::disk('public')->exists('profile_pictures/student/' . $user->profile_picture)) { Storage::disk('public')->delete('profile_pictures/student/' . $user->profile_picture); } $image = $request->file('profile_picture'); $imageName = $image->hashName(); Storage::disk('public')->putFileAs('profile_pictures/student', $image, $imageName); $user->profile_picture = $imageName; } $user->save(); $student->name = $request->name; $student->nisn = $request->nisn; $student->id_class = $request->id_class; $student->entry_year = $request->entry_year; // Handle hapus foto webcam lama if ($request->filled('delete_webcam_photo') && $request->delete_webcam_photo == '1') { if ($student->pictures) { $photos = explode(',', $student->pictures); foreach ($photos as $photo) { if (!empty($photo) && Storage::disk('public')->exists('photo-webcam/' . $photo)) { Storage::disk('public')->delete('photo-webcam/' . $photo); } } } $student->pictures = null; } // Handle tambah foto webcam baru dari edit (3 foto) if ($request->filled('pictures_edit')) { $photosData = $request->pictures_edit; $photoArray = explode('|||', $photosData); // Split dengan ||| // Hapus foto lama dulu sebelum menyimpan yang baru if ($student->pictures) { $oldPhotos = explode(',', $student->pictures); foreach ($oldPhotos as $photo) { if (!empty($photo) && Storage::disk('public')->exists('photo-webcam/' . $photo)) { Storage::disk('public')->delete('photo-webcam/' . $photo); } } } $picturesNames = []; foreach ($photoArray as $index => $photo) { if (!empty($photo)) { $imageData = $photo; if (strpos($imageData, ',') !== false) { list($type, $imageData) = explode(',', $imageData); $imageData = base64_decode($imageData); } else { $imageData = base64_decode($imageData); } if ($imageData !== false) { $picturesName = 'webcam_' . time() . '_' . ($index + 1) . '_' . uniqid() . '.png'; Storage::disk('public')->put('photo-webcam/' . $picturesName, $imageData); $picturesNames[] = $picturesName; } } } if (!empty($picturesNames)) { $student->pictures = implode(',', $picturesNames); } } $student->save(); return redirect()->route('akun.indentitas_siswa')->with('success', 'Data siswa berhasil diupdate.'); } catch (ValidationException $e) { return redirect()->back() ->withErrors($e->errors()) ->withInput() ->with('open_modal', 'edit') ->with('edit_id', $id); } catch (\Exception $e) { return redirect()->back() ->with('error', 'Update siswa gagal. Silakan coba lagi.') ->withInput() ->with('open_modal', 'edit') ->with('edit_id', $id); } } public function updateStudentClass(Request $request, $id) { $request->validate([ 'id_class' => 'required|integer', ]); $student = Student::findOrFail($id); $student->id_class = $request->id_class; $student->save(); return redirect()->route('akun.indentitas_siswa')->with('success', 'Kelas siswa berhasil diupdate.'); } // end register student // start register teacher public function registerTeacher(Request $request) { try { $request->validate([ 'name' => 'required|string|max:255', 'email' => 'required|string|email|max:255|unique:users', 'password' => 'required|string|min:6', 'nip' => 'required|digits:18|unique:teachers,nip', 'subjects' => 'required|array|min:1', 'subjects.*' => 'integer|exists:subjects,id', 'profile_picture' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048', ]); $imageName = null; if ($request->hasFile('profile_picture')) { $image = $request->file('profile_picture'); $imageName = $image->hashName(); Storage::disk('public')->putFileAs('profile_pictures/teacher', $image, $imageName); } // Ambil nama semua subject yang dipilih $subjectNames = Subject::whereIn('id', $request->subjects)->pluck('code')->toArray(); $subjectString = implode(', ', $subjectNames); $user = User::create([ 'email' => $request->email, 'password' => Hash::make($request->password), 'role' => 'teacher', 'profile_picture' => $imageName, ]); $teacher = new Teacher(); $teacher->id_user = $user->id; $teacher->name = $request->name; $teacher->nip = $request->nip; $teacher->subject = $subjectString; $teacher->save(); return redirect()->route('akun.indentitas_guru')->with('success', 'Data guru berhasil ditambahkan.'); } catch (ValidationException $e) { return redirect()->back() ->withErrors($e->errors()) ->withInput() ->with('open_modal', 'tambah'); } catch (\Exception $e) { return redirect()->back() ->with('error', 'Registrasi guru gagal: ' . $e->getMessage()) ->withInput() ->with('open_modal', 'tambah'); } } public function identitasGuru() { $teachers = Teacher::with('user')->get(); // Hapus 'subjects' $subjects = Subject::all(); return view('pages.akun.indentitas_guru', compact('teachers', 'subjects')); } // Update teacher public function updateTeacher(Request $request, $id) { try { $teacher = Teacher::findOrFail($id); $user = $teacher->user; $request->validate([ 'name' => 'required|string|max:255', 'email' => 'required|string|email|max:255|unique:users,email,' . $user->id, 'nip' => 'required|string|max:50|unique:teachers,nip,' . $teacher->id, 'subjects' => 'required|array|min:1', 'subjects.*' => 'integer|exists:subjects,id', 'profile_picture' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048', ]); $user->email = $request->email; if ($request->hasFile('profile_picture')) { if ($user->profile_picture && Storage::disk('public')->exists('profile_pictures/teacher/' . $user->profile_picture)) { Storage::disk('public')->delete('profile_pictures/teacher/' . $user->profile_picture); } $image = $request->file('profile_picture'); $imageName = $image->hashName(); Storage::disk('public')->putFileAs('profile_pictures/teacher', $image, $imageName); $user->profile_picture = $imageName; } $user->save(); // Ambil nama semua subject yang dipilih $subjectNames = Subject::whereIn('id', $request->subjects)->pluck('code')->toArray(); $subjectString = implode(', ', $subjectNames); $teacher->name = $request->name; $teacher->nip = $request->nip; $teacher->subject = $subjectString; $teacher->save(); return redirect()->route('akun.indentitas_guru')->with('success', 'Data guru berhasil diupdate.'); } catch (\Illuminate\Validation\ValidationException $e) { return redirect()->back() ->withErrors($e->errors()) ->withInput() ->with('open_modal', 'edit') ->with('edit_id', $id); } catch (\Exception $e) { return redirect()->back() ->with('error', 'Update guru gagal. Silakan coba lagi.') ->withInput() ->with('open_modal', 'edit') ->with('edit_id', $id); } } public function destroyTeacher($id) { $teacher = Teacher::findOrFail($id); // Hapus user terkait (akan otomatis hapus teacher jika foreign key cascade) if ($teacher->user) { $teacher->user->delete(); } else { $teacher->delete(); } return redirect()->route('akun.indentitas_guru')->with('success', 'Data guru berhasil dihapus.'); } }