1 <?php 2 require_once __DIR__ . '/../includes/bootstrap.php'; 3 must_login(); 4 $err = $ok = null; 5 6 if ($_SERVER['REQUEST_METHOD']==='POST' && csrf_ok()) { 7 $bio = sanitise(post('bio')); 8 $location = mb_substr(sanitise(post('location')), 0, 100); 9 $friends_hidden = isset($_POST['friends_hidden']) ? 1 : 0; 10 $avUrl = $USER['avatar']; 11 12 // Avatar upload 13 if (!empty($_FILES['avatar']['name'])) { 14 $f = $_FILES['avatar']; 15 if ($f['size'] > 2*1024*1024) { $err = 'Avatar must be under 2 MB.'; } 16 elseif (!in_array($f['type'],['image/jpeg','image/png','image/gif','image/webp'])) { $err='Image files only.'; } 17 else { 18 $ext = strtolower(pathinfo($f['name'],PATHINFO_EXTENSION)); 19 $name = 'av_'.$USER['id'].'_'.time().'.'.$ext; 20 $dir = UPLOADS.'/avatars/'; 21 if (!is_dir($dir)) mkdir($dir,0755,true); 22 if (move_uploaded_file($f['tmp_name'],$dir.$name)) { 23 $avUrl = BASE.'/public/uploads/avatars/'.$name; 24 } else { $err='Upload failed.'; } 25 } 26 } 27 28 if (!$err) { 29 DB::run('UPDATE users SET bio=?,avatar=?,friends_hidden=?,location=? WHERE id=?', 30 [$bio?:null, $avUrl, $friends_hidden, $location, $USER['id']]); 31 32 // Password change 33 $np = post('new_password'); 34 if ($np) { 35 if (!password_verify(post('cur_password'), $USER['password'])) { $err='Current password incorrect.'; } 36 elseif (strlen($np)<8) { $err='New password must be 8+ characters.'; } 37 elseif ($np!==post('new_password2')) { $err='New passwords do not match.'; } 38 else { 39 DB::run('UPDATE users SET password=? WHERE id=?', 40 [password_hash($np,PASSWORD_BCRYPT,['cost'=>12]),$USER['id']]); 41 } 42 } 43 if (!$err) { $ok='Profile updated!'; $USER=current_user(); } 44 } 45 } 46 47 $PAGE_TITLE = 'Edit Profile'; 48 include __DIR__ . '/../views/partials/layout.php'; 49 ?> 50 <nav class="bc"> 51 <a href="<?=u('/')?>">Home</a> › 52 <a href="<?=u('users/profile.php?u='.urlencode($USER['username']))?>">@<?=e($USER['username'])?></a> › 53 <span>Edit Profile</span> 54 </nav> 55 <div class="form-card"> 56 <h1>Edit Profile</h1> 57 <?php if($err):?><div class="alert err"><?=e($err)?></div><?php endif;?> 58 <?php if($ok): ?><div class="alert ok"><?=e($ok)?></div><?php endif;?> 59 <form method="POST" enctype="multipart/form-data"> 60 <?=csrf_input()?> 61 62 <div class="form-section"> 63 <h2>Profile Picture</h2> 64 <div class="av-upload"> 65 <?php if($USER['avatar']):?> 66 <img src="<?=e($USER['avatar'])?>" class="av-xl" id="avPrev" alt=""> 67 <?php else:?> 68 <span class="av-xl av-init" id="avPrev"><?=strtoupper($USER['username'][0])?></span> 69 <?php endif;?> 70 <div> 71 <label for="avatar" class="btn-ghost" style="cursor:pointer">Choose Image</label> 72 <input type="file" id="avatar" name="avatar" accept="image/*" style="display:none" onchange="prevAv(this)"> 73 <p class="hint">Max 2 MB · JPG, PNG, GIF, WebP</p> 74 </div> 75 </div> 76 </div> 77 78 <div class="form-section"> 79 <h2>About Me</h2> 80 <div class="fg"> 81 <label for="bio">Bio</label> 82 <textarea name="bio" id="bio" class="fi" rows="4" maxlength="500" 83 placeholder="Tell the community about yourself…"><?=e($USER['bio']??'')?></textarea> 84 <span class="hint"><span id="bioLen"><?=mb_strlen($USER['bio']??'')?></span>/500</span> 85 </div> 86 <div class="fg"> 87 <label for="location"> 88 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="14" height="14" style="vertical-align:middle;margin-right:4px"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg> 89 Location <small class="hint" style="display:inline">(optional)</small> 90 </label> 91 <input type="text" name="location" id="location" class="fi" maxlength="100" 92 value="<?=e($USER['location']??'')?>" placeholder="City, Country"> 93 <span class="hint">Shown on your posts and profile</span> 94 </div> 95 </div> 96 97 <div class="form-section"> 98 <h2>Privacy</h2> 99 <label class="toggle-label" style="cursor:pointer"> 100 <div> 101 <strong>Hide Friends List</strong> 102 <p class="hint">Others won't see your friends list on your profile</p> 103 </div> 104 <label class="toggle-sw"> 105 <input type="checkbox" name="friends_hidden" <?=$USER['friends_hidden']?'checked':''?>> 106 <span class="toggle-knob"></span> 107 </label> 108 </label> 109 </div> 110 111 <div class="form-section"> 112 <h2>Change Password</h2> 113 <p class="hint">Leave blank to keep your current password.</p> 114 <div class="fg"><label>Current Password</label><input type="password" name="cur_password" class="fi" placeholder="Current password"></div> 115 <div class="fg"><label>New Password</label><input type="password" name="new_password" class="fi" placeholder="Min. 8 characters"></div> 116 <div class="fg"><label>Confirm New Password</label><input type="password" name="new_password2" class="fi" placeholder="Repeat new password"></div> 117 </div> 118 119 <div class="form-actions"> 120 <a href="<?=u('users/profile.php?u='.urlencode($USER['username']))?>" class="btn-ghost">Cancel</a> 121 <button type="submit" class="btn-primary">Save Changes</button> 122 </div> 123 </form> 124 </div> 125 <script> 126 function prevAv(i){if(!i.files[0])return;var r=new FileReader();r.onload=function(e){var p=document.getElementById('avPrev');if(p.tagName==='IMG'){p.src=e.target.result;}else{var img=document.createElement('img');img.src=e.target.result;img.className='av-xl';img.id='avPrev';p.replaceWith(img);}};r.readAsDataURL(i.files[0]);} 127 document.getElementById('bio').addEventListener('input',function(){document.getElementById('bioLen').textContent=this.value.length;}); 128 </script> 129 130 <style> 131 .toggle-label { display:flex; align-items:center; justify-content:space-between; padding:8px 0; } 132 </style> 133 <?php include __DIR__ . '/../views/partials/layout_end.php'; ?>