input->get('tab',true)?:$tab;if(!in_array($tab,array('settings','devices'),true))$tab='settings'; $user=$this->db->get_where('users',array('id'=>$this->uid()))->row();if(!$user)show_404(); $data=array('active_menu'=>'profile','profile_tab'=>$tab,'user'=>$user,'sessions'=>$this->usersessionservice->activeSessions($user->id),'attempts'=>$this->usersessionservice->attempts($user->id),'active_count'=>$this->usersessionservice->activeCount($user->id)); $this->load->view('partials/header',$data);$this->load->view('profile/index',$data);$this->load->view('partials/footer'); } public function update_profile() { $this->postOnly();$user=$this->user();$newPhoto=null; try{ $name=trim((string)$this->input->post('nama',true));$email=strtolower(trim((string)$this->input->post('email',true))); if($name===''||mb_strlen($name)>255)throw new RuntimeException('Nama wajib diisi dan maksimal 255 karakter.'); if($email!==''&&!filter_var($email,FILTER_VALIDATE_EMAIL))throw new RuntimeException('Format email tidak valid.'); if($email!==''&&$this->db->where('email',$email)->where('id !=',$user->id)->get('users')->row())throw new RuntimeException('Email sudah digunakan pengguna lain.'); $newPhoto=$this->uploadPhoto();$update=array('nama'=>$name,'email'=>$email?:null,'profile_updated_at'=>date('Y-m-d H:i:s'));if($newPhoto)$update['profile_photo']=$newPhoto; $this->db->where('id',$user->id)->update('users',$update);if(!$this->db->trans_status())throw new RuntimeException('Profil gagal disimpan.'); if($newPhoto&&!empty($user->profile_photo))$this->removeOldPhoto($user->profile_photo); $this->session->set_userdata(array('nama'=>$name,'email'=>$email?:null,'profile_photo'=>$newPhoto?:$user->profile_photo)); log_activity('Profile','update','Memperbarui profil pengguna sendiri','success');$this->session->set_flashdata('success','Profil berhasil diperbarui.'); }catch(Throwable$e){if($newPhoto&&is_file(FCPATH.$newPhoto))@unlink(FCPATH.$newPhoto);$this->session->set_flashdata('error',$e->getMessage());} redirect('profile'); } public function update_security() { $this->postOnly();$hours=(int)$this->input->post('session_duration_hours');$limit=(int)$this->input->post('max_active_devices'); try{ if(!in_array($hours,array(24,72,168),true))throw new RuntimeException('Durasi session tidak valid.'); if($limit<1||$limit>10)throw new RuntimeException('Batas perangkat harus antara 1 sampai 10.'); $this->db->where('id',$this->uid())->update('users',array('session_duration_hours'=>$hours,'max_active_devices'=>$limit,'profile_updated_at'=>date('Y-m-d H:i:s'))); log_activity('Profile','security_setting','Mengubah durasi session menjadi '.$hours.' jam dan batas perangkat menjadi '.$limit,'success'); $this->session->set_flashdata('success','Pengaturan login berhasil disimpan dan berlaku mulai login berikutnya. Session yang sedang aktif tetap memakai batas waktu awalnya.'); }catch(Throwable$e){$this->session->set_flashdata('error',$e->getMessage());} redirect('profile'); } public function update_password() { $this->postOnly();$user=$this->user(); try{ $current=(string)$this->input->post('current_password');$new=(string)$this->input->post('new_password');$confirm=(string)$this->input->post('confirm_password'); if(!password_verify($current,$user->password))throw new RuntimeException('Password saat ini tidak sesuai.'); if(strlen($new)<8||!preg_match('/[A-Za-z]/',$new)||!preg_match('/\d/',$new))throw new RuntimeException('Password baru minimal 8 karakter dan harus mengandung huruf serta angka.'); if($new!==$confirm)throw new RuntimeException('Konfirmasi password baru tidak sama.'); if(password_verify($new,$user->password))throw new RuntimeException('Password baru harus berbeda dari password saat ini.'); $this->db->where('id',$user->id)->update('users',array('password'=>password_hash($new,PASSWORD_DEFAULT),'profile_updated_at'=>date('Y-m-d H:i:s'))); $this->usersessionservice->revokeOthers($user->id);log_activity('Profile','password','Mengubah password dan mengakhiri session perangkat lain','success'); $this->session->set_flashdata('success','Password berhasil diubah. Session pada perangkat lain telah diakhiri.'); }catch(Throwable$e){$this->session->set_flashdata('error',$e->getMessage());} redirect('profile'); } public function revoke_session($id) { $this->postOnly(); try{ $result=$this->usersessionservice->revoke((int)$id,$this->uid());log_activity('Profile','revoke_session','Mengakhiri session perangkat ID '.(int)$id,'success'); if($result['current']){$this->session->sess_destroy();redirect('auth?notice=revoked');return;} $this->session->set_flashdata('success','Session perangkat berhasil diakhiri.'); }catch(Throwable$e){$this->session->set_flashdata('error',$e->getMessage());} redirect('profile?tab=devices'); } private function uploadPhoto() { if(empty($_FILES['profile_photo'])||$_FILES['profile_photo']['error']===UPLOAD_ERR_NO_FILE)return null; $file=$_FILES['profile_photo'];if($file['error']!==UPLOAD_ERR_OK)throw new RuntimeException('Foto profil gagal diunggah.');if((int)$file['size']>2*1024*1024)throw new RuntimeException('Ukuran foto profil maksimal 2 MB.'); $mime=(new finfo(FILEINFO_MIME_TYPE))->file($file['tmp_name']);$allowed=array('image/jpeg'=>'jpg','image/png'=>'png','image/webp'=>'webp');if(!isset($allowed[$mime]))throw new RuntimeException('Foto profil hanya menerima JPG, PNG, atau WEBP.'); $dir=FCPATH.'uploads/accounting/profiles/';if(!is_dir($dir)&&!mkdir($dir,0755,true))throw new RuntimeException('Folder foto profil tidak dapat dibuat.'); $relative='uploads/accounting/profiles/'.bin2hex(random_bytes(20)).'.'.$allowed[$mime];if(!move_uploaded_file($file['tmp_name'],FCPATH.$relative))throw new RuntimeException('Foto profil tidak dapat disimpan.');return$relative; } private function removeOldPhoto($path){$prefix='uploads/accounting/profiles/';if(strpos((string)$path,$prefix)===0&&is_file(FCPATH.$path))@unlink(FCPATH.$path);} private function user(){$row=$this->db->get_where('users',array('id'=>$this->uid()))->row();if(!$row)throw new RuntimeException('Pengguna tidak ditemukan.');return$row;} private function uid(){return(int)$this->session->userdata('user_id');} private function postOnly(){if(strtoupper($this->input->method())!=='POST')show_404();} }