1<?php
2require_once __DIR__ . '/../includes/bootstrap.php';
3must_login();
4$err = $ok = null;
5
6if ($_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
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
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';
48include __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>
126function 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]);}
127document.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'; ?>