xgit simple git

nexus

nexus

clone git clone https://kb.hax.al/nexus

users/edit.php

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'; ?>