Refactoring : sécurité (XSS), découpage en modules inc/* et js/admin/*, IDs résolus par slug, perf (caches, cron Gravatar, assets auto-hébergés), tests

This commit is contained in:
2026-06-10 21:30:25 +02:00
parent e6b73df516
commit 9280c3b9ce
44 changed files with 3209 additions and 2907 deletions

149
js/admin/admin-profile.js Normal file
View File

@@ -0,0 +1,149 @@
/**
* Pages profil / utilisateur (profile.php, user-edit.php, user-new.php) :
* réordonnancement des sections natives, popovers d'aide, mode visuel forcé
* sur les WYSIWYG Pods.
* Dépend de admin-base.js (window.ThalimAdmin) — enqueue conditionnel.
*/
(function($) {
'use strict';
var TA = window.ThalimAdmin;
var safeRun = TA.safeRun;
// Only native WP field sections — never touch Pods tables (they may contain TinyMCE editors)
var PROFILE_SECTION_KEYS = [
'user-language-wrap',
'user-first-name-wrap',
'user-email-wrap',
'user-pass1-wrap',
'upload-avatar-row',
];
// Desired order. Groups of 2 are wrapped in a flex row and displayed side by side.
var PROFILE_ORDER = [
['user-first-name-wrap', 'upload-avatar-row'],
['user-email-wrap'],
['user-language-wrap', 'user-pass1-wrap'],
];
function reorderProfileSections() {
var form = TA.getProfileForm();
if (!form) return;
var pairMap = {};
form.querySelectorAll('table.form-table').forEach(function(table) {
PROFILE_SECTION_KEYS.forEach(function(key) {
if (pairMap[key] || !table.querySelector('.' + key)) return;
// Find the associated heading: first try preceding sibling in same parent,
// then look for an h2/h3 inside the same wrapper element.
var h2 = null;
var el = table.previousElementSibling;
while (el) {
if (el.tagName === 'H2' || el.tagName === 'H3') { h2 = el; break; }
if (el.tagName === 'TABLE') break;
el = el.previousElementSibling;
}
if (!h2 && table.parentElement !== form) {
h2 = table.parentElement.querySelector('h2, h3');
}
// The unit to move: if h2 and table share a non-form wrapper, move the wrapper.
var wrapper = null;
if (h2 && h2.parentElement !== form && h2.parentElement === table.parentElement) {
wrapper = h2.parentElement;
}
pairMap[key] = { h2: h2, table: table, wrapper: wrapper };
});
});
// Remove all matched units from DOM (dedup by actual element)
var removed = new Set();
function removeEl(el) {
if (el && !removed.has(el)) { removed.add(el); el.remove(); }
}
Object.values(pairMap).forEach(function(unit) {
if (unit.wrapper) { removeEl(unit.wrapper); }
else { removeEl(unit.h2); removeEl(unit.table); }
});
// Re-insert in declared order before the submit button
var submitAnchor = form.querySelector('p.submit');
function append(el) {
if (submitAnchor && submitAnchor.parentNode) form.insertBefore(el, submitAnchor);
else form.appendChild(el);
}
function appendUnit(unit) {
if (unit.wrapper) { append(unit.wrapper); }
else { if (unit.h2) append(unit.h2); append(unit.table); }
}
PROFILE_ORDER.forEach(function(group) {
var available = group.filter(function(key) { return !!pairMap[key]; });
if (!available.length) return;
// Dedup: two keys may resolve to the same table/wrapper
var seen = new Set();
var units = [];
available.forEach(function(key) {
var unit = pairMap[key];
var id = unit.wrapper || unit.table;
if (!seen.has(id)) { seen.add(id); units.push(unit); }
});
if (units.length === 1) {
appendUnit(units[0]);
} else {
var row = document.createElement('div');
row.className = 'profile-section-row';
units.forEach(function(unit) {
var col = document.createElement('div');
col.className = 'profile-section-col';
if (unit.wrapper) { col.appendChild(unit.wrapper); }
else { if (unit.h2) col.appendChild(unit.h2); col.appendChild(unit.table); }
row.appendChild(col);
});
append(row);
}
});
}
function initProfileEditors() {
reorderProfileSections();
TA.initInfoPopovers('user');
// Hide the "À propos du compte" section heading
document.querySelectorAll('#your-profile h2, #adduser h2, #createuser h2').forEach(function(h2) {
if (h2.textContent.trim() === 'À propos du compte') {
h2.style.display = 'none';
}
});
// Rename "Rôle" label to "Rôle sur le site"
var roleLabel = document.querySelector('label[for="role"]');
if (roleLabel && roleLabel.textContent.trim() === 'Rôle') {
roleLabel.textContent = 'Rôle sur le site';
}
}
$(document).ready(function() {
if (!TA.isProfileEditPage()) return;
setTimeout(function() {
safeRun('initProfileEditors', initProfileEditors);
TA.markReady();
}, 100);
// Force visual mode on all Pods WYSIWYG fields once everything is loaded
$(window).on('load', function() {
var scope = TA.getProfileForm() || document;
scope.querySelectorAll('.pods-dfv-container-wysiwyg textarea').forEach(function(ta) {
if (!ta.id) return;
TA.ensureVisualMode(ta.id);
});
});
});
})(jQuery);