Fixed new issues with Undefined username in header on profile pic change & TOTP Enabled not checked
This commit is contained in:
22
CHANGELOG.md
22
CHANGELOG.md
@@ -77,6 +77,28 @@
|
|||||||
- `#viewSliderContainer` uses `inline-flex` and `align-items: center` so that label, slider, and value text are vertically aligned with the other toolbar elements.
|
- `#viewSliderContainer` uses `inline-flex` and `align-items: center` so that label, slider, and value text are vertically aligned with the other toolbar elements.
|
||||||
- Reset margins/padding on the label and value span within `#viewSliderContainer` to eliminate any vertical misalignment.
|
- Reset margins/padding on the label and value span within `#viewSliderContainer` to eliminate any vertical misalignment.
|
||||||
|
|
||||||
|
### 9. Fixed new issues with Undefined username in header on profile pic change & TOTP Enabled not checked
|
||||||
|
|
||||||
|
**openUserPanel**
|
||||||
|
|
||||||
|
- **Rewritten entirely with DOM APIs** instead of `innerHTML` for any user-supplied text to eliminates “DOM text reinterpreted as HTML” warnings.
|
||||||
|
- **Default avatar fallback**: now uses `'/assets/default-avatar.png'` whenever `profile_picture` is empty.
|
||||||
|
- **TOTP checkbox initial state** is now set from the `totp_enabled` value returned by the server.
|
||||||
|
- **Modal title sync** on reopen now updates the `(username)` correctly (no more “undefined” until refresh).
|
||||||
|
- **Re-sync on reopen**: background color, avatar, TOTP checkbox and language selector all update when reopen the panel.
|
||||||
|
|
||||||
|
**updateAuthenticatedUI**
|
||||||
|
|
||||||
|
- **Username fix**: dropdown toggle now always uses `data.username` so the name never becomes `undefined` after uploading a picture.
|
||||||
|
- **Profile URL update** via `fetchProfilePicture()` always writes into `localStorage` before rebuilding the header, ensuring avatar+name stay in sync instantly.
|
||||||
|
- **Dropdown rebuild logic** tweaked to update the toggle’s innerHTML with both avatar and username on every call.
|
||||||
|
|
||||||
|
**UserModel::getUser**
|
||||||
|
|
||||||
|
- Switched to `explode(':', $line, 4)` to the fourth “profile_picture” field without clobbering the TOTP secret.
|
||||||
|
- **Strip trailing colons** from the stored URL (`rtrim($parts[3], ':')`) so we never send `…png:` back to the client.
|
||||||
|
- Returns an array with both `'username'` and `'profile_picture'`, matching what `getCurrentUser.php` needs.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Changes 5/8/2025
|
## Changes 5/8/2025
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../../../config/config.php';
|
require_once __DIR__ . '/../../../config/config.php';
|
||||||
require_once PROJECT_ROOT . '/src/controllers/UserController.php';
|
require_once PROJECT_ROOT . '/src/models/UserModel.php';
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
if (empty($_SESSION['authenticated'])) {
|
if (empty($_SESSION['authenticated'])) {
|
||||||
http_response_code(401);
|
http_response_code(401);
|
||||||
echo json_encode(['error'=>'Unauthorized']);
|
echo json_encode(['error'=>'Unauthorized']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $_SESSION['username'];
|
$user = $_SESSION['username'];
|
||||||
|
|||||||
@@ -223,6 +223,9 @@ async function fetchProfilePicture() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function updateAuthenticatedUI(data) {
|
export async function updateAuthenticatedUI(data) {
|
||||||
|
// Save latest auth data for later reuse
|
||||||
|
window.__lastAuthData = data;
|
||||||
|
|
||||||
// 1) Remove loading overlay safely
|
// 1) Remove loading overlay safely
|
||||||
const loading = document.getElementById('loadingOverlay');
|
const loading = document.getElementById('loadingOverlay');
|
||||||
if (loading) loading.remove();
|
if (loading) loading.remove();
|
||||||
@@ -304,6 +307,11 @@ export async function updateAuthenticatedUI(data) {
|
|||||||
? `<img src="${profilePicUrl}" style="width:24px;height:24px;border-radius:50%;vertical-align:middle;">`
|
? `<img src="${profilePicUrl}" style="width:24px;height:24px;border-radius:50%;vertical-align:middle;">`
|
||||||
: `<i class="material-icons">account_circle</i>`;
|
: `<i class="material-icons">account_circle</i>`;
|
||||||
|
|
||||||
|
// fallback username if missing
|
||||||
|
const usernameText = data.username
|
||||||
|
|| localStorage.getItem("username")
|
||||||
|
|| "";
|
||||||
|
|
||||||
if (!dd) {
|
if (!dd) {
|
||||||
dd = document.createElement("div");
|
dd = document.createElement("div");
|
||||||
dd.id = "userDropdown";
|
dd.id = "userDropdown";
|
||||||
@@ -314,7 +322,11 @@ export async function updateAuthenticatedUI(data) {
|
|||||||
toggle.id = "userDropdownToggle";
|
toggle.id = "userDropdownToggle";
|
||||||
toggle.classList.add("btn","btn-user");
|
toggle.classList.add("btn","btn-user");
|
||||||
toggle.setAttribute("title", t("user_settings"));
|
toggle.setAttribute("title", t("user_settings"));
|
||||||
toggle.innerHTML = `${avatarHTML}<span class="dropdown-username">${data.username}</span><span class="dropdown-caret"></span>`;
|
toggle.innerHTML = `
|
||||||
|
${avatarHTML}
|
||||||
|
<span class="dropdown-username">${usernameText}</span>
|
||||||
|
<span class="dropdown-caret"></span>
|
||||||
|
`;
|
||||||
dd.append(toggle);
|
dd.append(toggle);
|
||||||
|
|
||||||
// menu
|
// menu
|
||||||
@@ -375,9 +387,13 @@ export async function updateAuthenticatedUI(data) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// update avatar only
|
// update avatar & username only
|
||||||
const tog = dd.querySelector("#userDropdownToggle");
|
const tog = dd.querySelector("#userDropdownToggle");
|
||||||
tog.innerHTML = `${avatarHTML}<span class="dropdown-username">${data.username}</span><span class="dropdown-caret"></span>`;
|
tog.innerHTML = `
|
||||||
|
${avatarHTML}
|
||||||
|
<span class="dropdown-username">${usernameText}</span>
|
||||||
|
<span class="dropdown-caret"></span>
|
||||||
|
`;
|
||||||
dd.style.display = "inline-block";
|
dd.style.display = "inline-block";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,105 +184,128 @@ function normalizePicUrl(raw) {
|
|||||||
export async function openUserPanel() {
|
export async function openUserPanel() {
|
||||||
// 1) load data
|
// 1) load data
|
||||||
const { username = 'User', profile_picture = '', totp_enabled = false } = await fetchCurrentUser();
|
const { username = 'User', profile_picture = '', totp_enabled = false } = await fetchCurrentUser();
|
||||||
const raw = profile_picture;
|
const raw = profile_picture;
|
||||||
const picUrl = normalizePicUrl(raw);
|
const picUrl = normalizePicUrl(raw) || '/assets/default-avatar.png';
|
||||||
|
|
||||||
// 2) dark‐mode helpers
|
// 2) dark‐mode helpers
|
||||||
const isDark = document.body.classList.contains('dark-mode');
|
const isDark = document.body.classList.contains('dark-mode');
|
||||||
const overlayBg = isDark ? 'rgba(0,0,0,0.7)' : 'rgba(0,0,0,0.3)';
|
const overlayBg = isDark ? 'rgba(0,0,0,0.7)' : 'rgba(0,0,0,0.3)';
|
||||||
const contentCss = `
|
const contentStyle = `
|
||||||
background: ${isDark ? '#2c2c2c' : '#fff'};
|
background: ${isDark ? '#2c2c2c' : '#fff'};
|
||||||
color: ${isDark ? '#e0e0e0' : '#000'};
|
color: ${isDark ? '#e0e0e0' : '#000'};
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
max-width: 600px;
|
max-width: 600px; width:90%;
|
||||||
width: 90%;
|
border-radius: 8px;
|
||||||
border-radius: 8px;
|
overflow-y: auto; max-height: 415px;
|
||||||
overflow-y: auto;
|
border: ${isDark ? '1px solid #444' : '1px solid #ccc'};
|
||||||
max-height: 415px;
|
box-sizing: border-box;
|
||||||
border: ${isDark ? '1px solid #444' : '1px solid #ccc'};
|
scrollbar-width: none;
|
||||||
box-sizing: border-box;
|
-ms-overflow-style: none;
|
||||||
|
`;
|
||||||
|
|
||||||
/* hide scrollbar in Firefox */
|
// 3) create or reuse modal
|
||||||
scrollbar-width: none;
|
|
||||||
/* hide scrollbar in IE 10+ */
|
|
||||||
-ms-overflow-style: none;
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 3) build or re-use modal
|
|
||||||
let modal = document.getElementById('userPanelModal');
|
let modal = document.getElementById('userPanelModal');
|
||||||
if (!modal) {
|
if (!modal) {
|
||||||
|
// overlay
|
||||||
modal = document.createElement('div');
|
modal = document.createElement('div');
|
||||||
modal.id = 'userPanelModal';
|
modal.id = 'userPanelModal';
|
||||||
modal.style.cssText = `
|
Object.assign(modal.style, {
|
||||||
position:fixed; top:0; left:0; right:0; bottom:0;
|
position: 'fixed',
|
||||||
background:${overlayBg};
|
top: '0',
|
||||||
display:flex; align-items:center; justify-content:center;
|
left: '0',
|
||||||
z-index:1000;
|
right: '0',
|
||||||
|
bottom: '0',
|
||||||
|
background: overlayBg,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
zIndex: '1000',
|
||||||
|
});
|
||||||
|
|
||||||
|
// content container
|
||||||
|
const content = document.createElement('div');
|
||||||
|
content.className = 'modal-content';
|
||||||
|
content.style.cssText = contentStyle;
|
||||||
|
|
||||||
|
// close button
|
||||||
|
const closeBtn = document.createElement('span');
|
||||||
|
closeBtn.id = 'closeUserPanel';
|
||||||
|
closeBtn.className = 'editor-close-btn';
|
||||||
|
closeBtn.textContent = '×';
|
||||||
|
closeBtn.addEventListener('click', () => modal.style.display = 'none');
|
||||||
|
content.appendChild(closeBtn);
|
||||||
|
|
||||||
|
// avatar + picker
|
||||||
|
const avatarWrapper = document.createElement('div');
|
||||||
|
avatarWrapper.style.cssText = 'text-align:center; margin-bottom:20px;';
|
||||||
|
const avatarInner = document.createElement('div');
|
||||||
|
avatarInner.style.cssText = 'position:relative; width:80px; height:80px; margin:0 auto;';
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.id = 'profilePicPreview';
|
||||||
|
img.src = picUrl;
|
||||||
|
img.alt = 'Profile Picture';
|
||||||
|
img.style.cssText = 'width:100%; height:100%; border-radius:50%; object-fit:cover;';
|
||||||
|
avatarInner.appendChild(img);
|
||||||
|
const label = document.createElement('label');
|
||||||
|
label.htmlFor = 'profilePicInput';
|
||||||
|
label.style.cssText = `
|
||||||
|
position:absolute; bottom:0; right:0;
|
||||||
|
width:24px; height:24px;
|
||||||
|
background:rgba(0,0,0,0.6);
|
||||||
|
border-radius:50%; display:flex;
|
||||||
|
align-items:center; justify-content:center;
|
||||||
|
cursor:pointer;
|
||||||
`;
|
`;
|
||||||
|
const editIcon = document.createElement('i');
|
||||||
|
editIcon.className = 'material-icons';
|
||||||
|
editIcon.style.cssText = 'color:#fff; font-size:16px;';
|
||||||
|
editIcon.textContent = 'edit';
|
||||||
|
label.appendChild(editIcon);
|
||||||
|
avatarInner.appendChild(label);
|
||||||
|
const fileInput = document.createElement('input');
|
||||||
|
fileInput.type = 'file';
|
||||||
|
fileInput.id = 'profilePicInput';
|
||||||
|
fileInput.accept = 'image/*';
|
||||||
|
fileInput.style.display = 'none';
|
||||||
|
avatarInner.appendChild(fileInput);
|
||||||
|
avatarWrapper.appendChild(avatarInner);
|
||||||
|
content.appendChild(avatarWrapper);
|
||||||
|
|
||||||
modal.innerHTML = `
|
// title
|
||||||
<div class="modal-content" style="${contentCss}">
|
const title = document.createElement('h3');
|
||||||
<span id="closeUserPanel" class="editor-close-btn">×</span>
|
title.style.cssText = 'text-align:center; margin-bottom:20px;';
|
||||||
<div style="text-align:center; margin-bottom:20px;">
|
title.textContent = `${t('user_panel')} (${username})`;
|
||||||
<div style="position:relative; width:80px; height:80px; margin:0 auto;">
|
content.appendChild(title);
|
||||||
<img id="profilePicPreview"
|
|
||||||
src="${picUrl || '/assets/default-avatar.png'}"
|
|
||||||
style="width:100%; height:100%; border-radius:50%; object-fit:cover;">
|
|
||||||
<label for="profilePicInput"
|
|
||||||
style="
|
|
||||||
position:absolute; bottom:0; right:0;
|
|
||||||
width:24px; height:24px; background:rgba(0,0,0,0.6);
|
|
||||||
border-radius:50%; display:flex; align-items:center;
|
|
||||||
justify-content:center; cursor:pointer;">
|
|
||||||
<i class="material-icons" style="color:#fff; font-size:16px;">edit</i>
|
|
||||||
</label>
|
|
||||||
<input type="file" id="profilePicInput" accept="image/*" style="display:none">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h3 style="text-align:center; margin-bottom:20px;">
|
|
||||||
${t('user_panel')} (${username})
|
|
||||||
</h3>
|
|
||||||
<button id="openChangePasswordModalBtn" class="btn btn-primary" style="margin-bottom:15px;">
|
|
||||||
${t('change_password')}
|
|
||||||
</button>
|
|
||||||
<fieldset style="margin-bottom:15px;">
|
|
||||||
<legend>${t('totp_settings')}</legend>
|
|
||||||
<label style="cursor:pointer;">
|
|
||||||
<input type="checkbox" id="userTOTPEnabled" style="vertical-align:middle;">
|
|
||||||
${t('enable_totp')}
|
|
||||||
</label>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset style="margin-bottom:15px;">
|
|
||||||
<legend>${t('language')}</legend>
|
|
||||||
<select id="languageSelector" class="form-select">
|
|
||||||
<option value="en">${t('english')}</option>
|
|
||||||
<option value="es">${t('spanish')}</option>
|
|
||||||
<option value="fr">${t('french')}</option>
|
|
||||||
<option value="de">${t('german')}</option>
|
|
||||||
</select>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
document.body.appendChild(modal);
|
|
||||||
|
|
||||||
// --- wire up handlers ---
|
// change password btn
|
||||||
|
const pwdBtn = document.createElement('button');
|
||||||
|
pwdBtn.id = 'openChangePasswordModalBtn';
|
||||||
|
pwdBtn.className = 'btn btn-primary';
|
||||||
|
pwdBtn.style.marginBottom = '15px';
|
||||||
|
pwdBtn.textContent = t('change_password');
|
||||||
|
pwdBtn.addEventListener('click', () => {
|
||||||
|
document.getElementById('changePasswordModal').style.display = 'block';
|
||||||
|
});
|
||||||
|
content.appendChild(pwdBtn);
|
||||||
|
|
||||||
modal.querySelector('#closeUserPanel')
|
// TOTP fieldset
|
||||||
.addEventListener('click', () => modal.style.display = 'none');
|
const totpFs = document.createElement('fieldset');
|
||||||
|
totpFs.style.marginBottom = '15px';
|
||||||
modal.querySelector('#openChangePasswordModalBtn')
|
const totpLegend = document.createElement('legend');
|
||||||
.addEventListener('click', () => {
|
totpLegend.textContent = t('totp_settings');
|
||||||
document.getElementById('changePasswordModal').style.display = 'block';
|
totpFs.appendChild(totpLegend);
|
||||||
});
|
const totpLabel = document.createElement('label');
|
||||||
|
totpLabel.style.cursor = 'pointer';
|
||||||
// TOTP
|
const totpCb = document.createElement('input');
|
||||||
const totpCb = modal.querySelector('#userTOTPEnabled');
|
totpCb.type = 'checkbox';
|
||||||
totpCb.addEventListener('change', async function () {
|
totpCb.id = 'userTOTPEnabled';
|
||||||
|
totpCb.style.verticalAlign = 'middle';
|
||||||
|
totpCb.checked = totp_enabled;
|
||||||
|
totpCb.addEventListener('change', async function() {
|
||||||
const resp = await fetch('/api/updateUserPanel.php', {
|
const resp = await fetch('/api/updateUserPanel.php', {
|
||||||
method: 'POST',
|
method: 'POST', credentials: 'include',
|
||||||
credentials: 'include',
|
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type':'application/json',
|
||||||
'X-CSRF-Token': window.csrfToken
|
'X-CSRF-Token': window.csrfToken
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ totp_enabled: this.checked })
|
body: JSON.stringify({ totp_enabled: this.checked })
|
||||||
@@ -291,37 +314,52 @@ export async function openUserPanel() {
|
|||||||
if (!js.success) showToast(js.error || t('error_updating_totp_setting'));
|
if (!js.success) showToast(js.error || t('error_updating_totp_setting'));
|
||||||
else if (this.checked) openTOTPModal();
|
else if (this.checked) openTOTPModal();
|
||||||
});
|
});
|
||||||
|
totpLabel.appendChild(totpCb);
|
||||||
|
totpLabel.append(` ${t('enable_totp')}`);
|
||||||
|
totpFs.appendChild(totpLabel);
|
||||||
|
content.appendChild(totpFs);
|
||||||
|
|
||||||
// Language
|
// language fieldset
|
||||||
const langSel = modal.querySelector('#languageSelector');
|
const langFs = document.createElement('fieldset');
|
||||||
langSel.addEventListener('change', function () {
|
langFs.style.marginBottom = '15px';
|
||||||
|
const langLegend = document.createElement('legend');
|
||||||
|
langLegend.textContent = t('language');
|
||||||
|
langFs.appendChild(langLegend);
|
||||||
|
const langSel = document.createElement('select');
|
||||||
|
langSel.id = 'languageSelector';
|
||||||
|
langSel.className = 'form-select';
|
||||||
|
['en','es','fr','de'].forEach(code => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = code;
|
||||||
|
opt.textContent = t(code === 'en'? 'english' : code === 'es'? 'spanish' : code === 'fr'? 'french' : 'german');
|
||||||
|
langSel.appendChild(opt);
|
||||||
|
});
|
||||||
|
langSel.value = localStorage.getItem('language') || 'en';
|
||||||
|
langSel.addEventListener('change', function() {
|
||||||
localStorage.setItem('language', this.value);
|
localStorage.setItem('language', this.value);
|
||||||
setLocale(this.value);
|
setLocale(this.value);
|
||||||
applyTranslations();
|
applyTranslations();
|
||||||
});
|
});
|
||||||
|
langFs.appendChild(langSel);
|
||||||
|
content.appendChild(langFs);
|
||||||
|
|
||||||
// Auto‐upload on file select
|
// wire up image‐input change
|
||||||
const fileInput = modal.querySelector('#profilePicInput');
|
fileInput.addEventListener('change', async function() {
|
||||||
fileInput.addEventListener('change', async function () {
|
const f = this.files[0];
|
||||||
const file = this.files[0];
|
if (!f) return;
|
||||||
if (!file) return;
|
|
||||||
|
|
||||||
// preview immediately
|
// preview immediately
|
||||||
const img = modal.querySelector('#profilePicPreview');
|
img.src = URL.createObjectURL(f);
|
||||||
img.src = URL.createObjectURL(file);
|
|
||||||
|
|
||||||
// upload
|
// upload
|
||||||
const fd = new FormData();
|
const fd = new FormData();
|
||||||
fd.append('profile_picture', file);
|
fd.append('profile_picture', f);
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/profile/uploadPicture.php', {
|
const res = await fetch('/api/profile/uploadPicture.php', {
|
||||||
method: 'POST',
|
method: 'POST', credentials: 'include',
|
||||||
credentials: 'include',
|
|
||||||
headers: { 'X-CSRF-Token': window.csrfToken },
|
headers: { 'X-CSRF-Token': window.csrfToken },
|
||||||
body: fd
|
body: fd
|
||||||
});
|
});
|
||||||
const text = await res.text();
|
const text = await res.text();
|
||||||
const js = JSON.parse(text || '{}');
|
const js = JSON.parse(text || '{}');
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
showToast(js.error || t('error_updating_picture'));
|
showToast(js.error || t('error_updating_picture'));
|
||||||
return;
|
return;
|
||||||
@@ -329,7 +367,6 @@ export async function openUserPanel() {
|
|||||||
const newUrl = normalizePicUrl(js.url);
|
const newUrl = normalizePicUrl(js.url);
|
||||||
img.src = newUrl;
|
img.src = newUrl;
|
||||||
localStorage.setItem('profilePicUrl', newUrl);
|
localStorage.setItem('profilePicUrl', newUrl);
|
||||||
// refresh the header immediately
|
|
||||||
updateAuthenticatedUI(window.__lastAuthData || {});
|
updateAuthenticatedUI(window.__lastAuthData || {});
|
||||||
showToast(t('profile_picture_updated'));
|
showToast(t('profile_picture_updated'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -338,15 +375,18 @@ export async function openUserPanel() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// finalize
|
||||||
|
modal.appendChild(content);
|
||||||
|
document.body.appendChild(modal);
|
||||||
} else {
|
} else {
|
||||||
|
// reuse on reopen
|
||||||
modal.style.background = overlayBg;
|
Object.assign(modal.style, { background: overlayBg });
|
||||||
const contentEl = modal.querySelector('.modal-content');
|
const content = modal.querySelector('.modal-content');
|
||||||
contentEl.style.cssText = contentCss;
|
content.style.cssText = contentStyle;
|
||||||
// re-open: sync current values
|
modal.querySelector('#profilePicPreview').src = picUrl || '/assets/default-avatar.png';
|
||||||
modal.querySelector('#profilePicPreview').src = picUrl || '/images/default-avatar.png';
|
modal.querySelector('#userTOTPEnabled').checked = totp_enabled;
|
||||||
modal.querySelector('#userTOTPEnabled').checked = totp_enabled;
|
modal.querySelector('#languageSelector').value = localStorage.getItem('language') || 'en';
|
||||||
modal.querySelector('#languageSelector').value = localStorage.getItem('language') || 'en';
|
modal.querySelector('h3').textContent = `${t('user_panel')} (${username})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// show
|
// show
|
||||||
|
|||||||
@@ -676,22 +676,29 @@ class userModel
|
|||||||
if (! file_exists($usersFile)) {
|
if (! file_exists($usersFile)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (file($usersFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
|
foreach (file($usersFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
|
||||||
// explode into at most 4 parts: [0]=username, [1]=hash, [2]=isAdmin, [3]=profileUrl (might include a trailing colon)
|
// split *all* the fields
|
||||||
$parts = explode(':', $line, 4);
|
$parts = explode(':', $line);
|
||||||
|
|
||||||
if ($parts[0] !== $username) {
|
if ($parts[0] !== $username) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// strip any trailing colon(s) from the URL field
|
|
||||||
$pic = isset($parts[3]) ? rtrim($parts[3], ':') : '';
|
// determine admin & totp
|
||||||
|
$isAdmin = (isset($parts[2]) && $parts[2] === '1');
|
||||||
|
$totpEnabled = !empty($parts[3]);
|
||||||
|
// profile_picture is the 5th field if present
|
||||||
|
$pic = isset($parts[4]) ? $parts[4] : '';
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'username' => $parts[0],
|
'username' => $parts[0],
|
||||||
|
'isAdmin' => $isAdmin,
|
||||||
|
'totp_enabled' => $totpEnabled,
|
||||||
'profile_picture' => $pic,
|
'profile_picture' => $pic,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return []; // user not found
|
return []; // user not found
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user