xgit simple git

nexus

nexus

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

users/edit.php


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