by checkbox value; preserve "add new" button
+ var liMap = {};
+ var addNewItem = null;
+ list.querySelectorAll('li').forEach(function(li) {
+ if (li.classList.contains('pods-pick-add-new')) { addNewItem = li; return; }
+ var cb = li.querySelector('input[type="checkbox"]');
+ if (cb) liMap[cb.value] = li;
+ });
+
+ // Rebuild list in group order
+ list.innerHTML = '';
+ thalimAxesGroups.forEach(function(group) {
+ var labelLi = document.createElement('li');
+ labelLi.className = 'axes-group-label';
+ labelLi.textContent = group.label;
+ list.appendChild(labelLi);
+ group.terms.forEach(function(term) {
+ var li = liMap[String(term.id)];
+ if (li) list.appendChild(li);
+ });
+ });
+
+ if (addNewItem) list.appendChild(addNewItem);
+ }
+
+ var REF_BIB_EDITOR_ID = CONFIG.editors.refBib;
+ var refBibReinited = false;
+
+ // Reinit the référence bibliographique TinyMCE editor.
+ // Called at page load (if the field is already visible) and by the
+ // MutationObserver (when the field becomes visible after a category change).
+ function initRefBibEditor() {
+ if (refBibReinited) return;
+ var row = document.querySelector('.' + CONFIG.rows.refBib);
+ if (!row || row.style.display === 'none') return;
+ refBibReinited = true;
+ TA.reinitEditor(REF_BIB_EDITOR_ID);
+ TA.ensureVisualMode(REF_BIB_EDITOR_ID);
+ }
+
+ function initAxesGroupObserver() {
+ // Pods shows/hides conditional rows by removing inline style="display:none"
+ // Watch the entire Pods meta form for style changes on the axes row
+ var podsForm = document.querySelector('.pods-pick-values, ' + CONFIG.boxes.champsContextuels + ', form#post');
+ if (!podsForm) podsForm = document.body;
+
+ var observer = new MutationObserver(function(mutations) {
+ for (var i = 0; i < mutations.length; i++) {
+ var target = mutations[i].target;
+ if (target.classList && target.classList.contains(CONFIG.rows.axes)) {
+ if (target.style.display !== 'none') {
+ setTimeout(groupAxesCheckboxes, 50);
+ }
+ }
+ // Reinit TinyMCE on the référence bibliographique field when its
+ // row becomes visible — Pods hides it with display:none which breaks
+ // the TinyMCE iframe. Only reinit once per page load.
+ if (!refBibReinited && target.classList &&
+ target.classList.contains(CONFIG.rows.refBib)) {
+ if (target.style.display !== 'none') {
+ setTimeout(initRefBibEditor, 100);
+ }
+ }
+ }
+ });
+
+ observer.observe(podsForm, { attributes: true, attributeFilter: ['style'], subtree: true });
+ }
+
+ // Gutenberg's Popover component closes on outside click via focusout detection.
+ // But if focus never enters the popover, focusout never fires and clicking outside
+ // does nothing. Fix: focus the popover container as soon as it appears in the DOM.
+ function initDatePickerPopoverFix() {
+ var observer = new MutationObserver(function(mutations) {
+ for (var i = 0; i < mutations.length; i++) {
+ var added = mutations[i].addedNodes;
+ for (var j = 0; j < added.length; j++) {
+ var node = added[j];
+ if (node.nodeType !== 1) continue;
+ var content = node.classList.contains('components-popover__content')
+ ? node
+ : node.querySelector && node.querySelector('.components-popover__content');
+ if (content) {
+ var c = content;
+ requestAnimationFrame(function() {
+ if (!c.hasAttribute('tabindex')) c.setAttribute('tabindex', '-1');
+ c.focus();
+ });
+ }
+ }
+ }
+ });
+ observer.observe(document.body, { childList: true, subtree: true });
+ }
+
+ function updateMembresGridSeparator() {
+ var sep = document.querySelector(CONFIG.boxes.membres + ' .membres-grid-separator');
+ if (!sep) return;
+ var autreRows = document.querySelectorAll(CONFIG.boxes.membres + ' [class*="pods-form-ui-row-name-autre-"]');
+ var anyVisible = Array.from(autreRows).some(function(row) {
+ return row.style.display !== 'none';
+ });
+ sep.style.display = anyVisible ? '' : 'none';
+ }
+
+ function initPostEditPage() {
+ // Disable category options (CSS handles the color)
+ const categorieSelect = document.querySelector(CONFIG.categorySelect);
+ if (categorieSelect) {
+ categorieSelect.querySelectorAll('option').forEach(option => {
+ if (CONFIG.disabledCategoryIds.includes(option.value)) {
+ option.disabled = true;
+ }
+ });
+ }
+
+ // Reorder meta boxes
+ const sideSortables = document.querySelector('#side-sortables');
+ if (sideSortables) {
+ const typeDannonce = document.querySelector(CONFIG.boxes.typeDannonce);
+ const affichageAccueil = document.querySelector(CONFIG.boxes.affichageAccueil);
+ const thematique = document.querySelector(CONFIG.boxes.thematique);
+ if (typeDannonce) sideSortables.prepend(typeDannonce);
+ if (affichageAccueil) sideSortables.appendChild(affichageAccueil);
+ if (thematique) sideSortables.appendChild(thematique);
+ }
+
+ const submitDiv = document.querySelector('#submitdiv');
+ if (submitDiv && submitDiv.parentNode) {
+ submitDiv.parentNode.appendChild(submitDiv);
+ }
+
+ const champsContextuels = document.querySelector(CONFIG.boxes.champsContextuels);
+ if (champsContextuels && champsContextuels.parentNode) {
+ champsContextuels.parentNode.prepend(champsContextuels);
+ }
+
+ // Chaque sous-module est isolé : une exception dans l'un
+ // n'empêche pas les suivants de s'initialiser.
+ safeRun('initBodyLanguageTabs', initBodyLanguageTabs);
+ safeRun('initRefBibEditor', initRefBibEditor);
+ safeRun('groupAxesCheckboxes', groupAxesCheckboxes);
+ safeRun('initAxesGroupObserver', initAxesGroupObserver);
+ safeRun('updatePostboxVisibility', TA.updatePostboxVisibility);
+ safeRun('initDatePickerPopoverFix', initDatePickerPopoverFix);
+ safeRun('initInfoPopovers', TA.initInfoPopovers);
+
+ // Place #pods-meta-documents-joints in #normal-sortables, right after
+ // #pods-meta-champs-contextuels. This keeps it out of #post-body-content
+ // (the body editor section) regardless of whether champsContextuels is
+ // currently visible. When champsContextuels is hidden it takes no space,
+ // so documentsJoints simply appears first in #normal-sortables.
+ const documentsJoints = document.querySelector(CONFIG.boxes.documentsJoints);
+ if (documentsJoints) {
+ if (champsContextuels && champsContextuels.parentNode) {
+ champsContextuels.parentNode.insertBefore(documentsJoints, champsContextuels.nextSibling);
+ } else {
+ const normalSortables = document.querySelector('#normal-sortables');
+ if (normalSortables) normalSortables.prepend(documentsJoints);
+ }
+ }
+
+ // Inject separator row for the Membres grid layout
+ var membresTbody = document.querySelector(CONFIG.boxes.membres + ' .form-table tbody');
+ if (membresTbody && !membresTbody.querySelector('.membres-grid-separator')) {
+ var sep = document.createElement('tr');
+ sep.className = 'membres-grid-separator';
+ membresTbody.appendChild(sep);
+ }
+ updateMembresGridSeparator();
+ }
+
+ $(document).ready(function() {
+ if (!TA.isPostEditPage()) return;
+
+ safeRun('setupBodyTabsDom', setupBodyTabsDom);
+
+ setTimeout(function() {
+ safeRun('initPostEditPage', initPostEditPage);
+ TA.markReady();
+ }, 100);
+
+ $(CONFIG.categorySelect).change(function() {
+ setTimeout(function() {
+ safeRun('updatePostboxVisibility', TA.updatePostboxVisibility);
+ safeRun('updateMembresGridSeparator', updateMembresGridSeparator);
+ }, 10);
+ });
+ });
+
+})(jQuery);
diff --git a/js/admin/admin-profile.js b/js/admin/admin-profile.js
new file mode 100644
index 0000000..f20e40b
--- /dev/null
+++ b/js/admin/admin-profile.js
@@ -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);
diff --git a/js/admin/admin-rename.js b/js/admin/admin-rename.js
new file mode 100644
index 0000000..144f3c4
--- /dev/null
+++ b/js/admin/admin-rename.js
@@ -0,0 +1,77 @@
+/**
+ * Renomme « Article » en « Annonce » dans l'interface admin (toutes pages).
+ * Dépend de admin-base.js (window.ThalimAdmin).
+ */
+(function() {
+ 'use strict';
+
+ var TA = window.ThalimAdmin;
+
+ function renameArticlesToAnnonces() {
+ const replacements = [
+ [/Tous les articles/g, 'Toutes les annonces'],
+ [/Ajouter un article/g, 'Ajouter une annonce'],
+ [/Modifier l.article/g, "Modifier l'annonce"],
+ [/Prévisualiser l.article/g, "Prévisualiser l'annonce"],
+ [/Afficher l.article/g, "Afficher l'annonce"],
+ [/Voir l.article/g, "Voir l'annonce"],
+ [/Article publié/g, 'Annonce publiée'],
+ [/Article mis à jour/g, 'Annonce mise à jour'],
+ [/Article planifié/g, 'Annonce planifiée'],
+ [/Articles par page/g, 'Annonces par page'],
+ [/Articles/g, 'Annonces'],
+ [/Article/g, 'Annonce'],
+ [/Rechercher des articles/g, 'Rechercher des annonces'],
+ ];
+
+ function applyReplacements(text) {
+ return replacements.reduce((t, [s, r]) => t.replace(s, r), text);
+ }
+
+ function replaceInTextNodes(el) {
+ if (!el) return;
+ const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
+ const nodes = [];
+ while (walker.nextNode()) nodes.push(walker.currentNode);
+ nodes.forEach(function(node) {
+ const replaced = applyReplacements(node.textContent);
+ if (replaced !== node.textContent) node.textContent = replaced;
+ });
+ }
+
+ // Menu latéral
+ replaceInTextNodes(document.querySelector('#menu-posts'));
+
+ // Titre de page (h1) et bouton d'ajout
+ document.querySelectorAll('.wp-heading-inline, .page-title-action').forEach(replaceInTextNodes);
+
+ // Notifications après sauvegarde (Article publié, mis à jour…)
+ document.querySelectorAll('#message, .notice').forEach(replaceInTextNodes);
+
+ // Boîte de publication — lien "Voir l'article"
+ replaceInTextNodes(document.querySelector('.submitbox'));
+
+ // Options d'écran — "Articles par page"
+ replaceInTextNodes(document.querySelector('#screen-options-wrap'));
+
+ // Bouton de recherche (attribut value + aria-label)
+ var searchSubmit = document.querySelector('#search-submit');
+ if (searchSubmit) {
+ if (searchSubmit.value) {
+ searchSubmit.value = applyReplacements(searchSubmit.value);
+ }
+ var ariaLabel = searchSubmit.getAttribute('aria-label');
+ if (ariaLabel) {
+ searchSubmit.setAttribute('aria-label', applyReplacements(ariaLabel));
+ }
+ }
+
+ // Titre de l'onglet du navigateur
+ document.title = applyReplacements(document.title);
+ }
+
+ document.addEventListener('DOMContentLoaded', function() {
+ TA.safeRun('renameArticlesToAnnonces', renameArticlesToAnnonces);
+ });
+
+})();
diff --git a/js/admin/admin-taxonomy-list.js b/js/admin/admin-taxonomy-list.js
new file mode 100644
index 0000000..3c0efc9
--- /dev/null
+++ b/js/admin/admin-taxonomy-list.js
@@ -0,0 +1,80 @@
+/**
+ * Pages listes/édition de taxonomies (edit-tags.php, term.php) :
+ * info-bulles « FR // EN », filtre « Type de programme », mode visuel forcé
+ * sur les WYSIWYG Pods des pages term.
+ * Dépend de admin-base.js (window.ThalimAdmin) — enqueue conditionnel.
+ */
+(function($) {
+ 'use strict';
+
+ var TA = window.ThalimAdmin;
+ var CONFIG = TA.CONFIG;
+ var safeRun = TA.safeRun;
+
+ // Inject a "Type de programme" filter select into the taxonomy search form.
+ // The form already has hidden taxonomy/post_type fields so the select value
+ // is submitted with them and picked up by pre_get_terms server-side.
+ function initProgrammeFilter() {
+ var form = document.querySelector('form.search-form');
+ if (!form) return;
+
+ var types = [
+ 'Programme subventionné',
+ 'Autre programme',
+ 'Ancien programme'
+ ];
+
+ // Read current filter value from the URL.
+ var params = new URLSearchParams(window.location.search);
+ var current = params.get('type_de_programme') || '';
+
+ var select = document.createElement('select');
+ select.name = 'type_de_programme';
+ select.id = 'filter-type-de-programme';
+ select.style.cssText = 'margin-right:6px;';
+
+ var blank = document.createElement('option');
+ blank.value = '';
+ blank.textContent = 'Tous les types';
+ select.appendChild(blank);
+
+ types.forEach(function(type) {
+ var opt = document.createElement('option');
+ opt.value = type;
+ opt.textContent = type;
+ if (type === current) opt.selected = true;
+ select.appendChild(opt);
+ });
+
+ // Insert before the first (search-box) inside the form.
+ var searchBox = form.querySelector('p.search-box');
+ form.insertBefore(select, searchBox || null);
+ }
+
+ $(document).ready(function() {
+ setTimeout(function() {
+ safeRun('taxonomyPopovers', function() {
+ var isTranslateTaxonomy = CONFIG.translateTaxonomies.some(function(tax) {
+ return window.location.search.indexOf('taxonomy=' + tax) !== -1;
+ });
+ if (isTranslateTaxonomy) {
+ TA.initInfoPopovers('taxonomy');
+ }
+ });
+ safeRun('programmeFilter', function() {
+ if (window.location.search.indexOf('taxonomy=programme_de_recherche') !== -1) {
+ initProgrammeFilter();
+ }
+ });
+ }, 100);
+
+ // term.php / edit-tags.php : force visual mode on Pods WYSIWYG fields
+ $(window).on('load', function() {
+ document.querySelectorAll('.pods-dfv-container-wysiwyg textarea').forEach(function(ta) {
+ if (!ta.id) return;
+ TA.ensureVisualMode(ta.id);
+ });
+ });
+ });
+
+})(jQuery);
diff --git a/js/adminDashboardMods.js b/js/adminDashboardMods.js
deleted file mode 100644
index 1cdc44f..0000000
--- a/js/adminDashboardMods.js
+++ /dev/null
@@ -1,892 +0,0 @@
-(function($) {
- 'use strict';
-
- function isPostEditPage() {
- return window.pagenow === 'post'
- || window.pagenow === 'post-new'
- // On CPTs, pagenow is the post_type slug — also catch them via the
- // body classes WP sets for any post.php / post-new.php screen.
- || document.body.classList.contains('post-php')
- || document.body.classList.contains('post-new-php');
- }
-
- function isProfileEditPage() {
- return window.pagenow === 'profile' || window.pagenow === 'user-edit' || window.pagenow === 'user-new';
- }
-
- function getProfileForm() {
- return document.querySelector('#your-profile, #createuser');
- }
-
- function isPodsModal() {
- return new URLSearchParams(window.location.search).has('pods_modal');
- }
-
- function renameArticlesToAnnonces() {
- const replacements = [
- [/Tous les articles/g, 'Toutes les annonces'],
- [/Ajouter un article/g, 'Ajouter une annonce'],
- [/Modifier l.article/g, "Modifier l'annonce"],
- [/Pr\u00e9visualiser l.article/g, "Pr\u00e9visualiser l'annonce"],
- [/Afficher l.article/g, "Afficher l'annonce"],
- [/Voir l.article/g, "Voir l'annonce"],
- [/Article publi\u00e9/g, 'Annonce publi\u00e9e'],
- [/Article mis \u00e0 jour/g, 'Annonce mise \u00e0 jour'],
- [/Article planifi\u00e9/g, 'Annonce planifi\u00e9e'],
- [/Articles par page/g, 'Annonces par page'],
- [/Articles/g, 'Annonces'],
- [/Article/g, 'Annonce'],
- [/Rechercher des articles/g, 'Rechercher des annonces'],
- ];
-
- function applyReplacements(text) {
- return replacements.reduce((t, [s, r]) => t.replace(s, r), text);
- }
-
- function replaceInTextNodes(el) {
- if (!el) return;
- const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
- const nodes = [];
- while (walker.nextNode()) nodes.push(walker.currentNode);
- nodes.forEach(function(node) {
- const replaced = applyReplacements(node.textContent);
- if (replaced !== node.textContent) node.textContent = replaced;
- });
- }
-
- // Menu latéral
- replaceInTextNodes(document.querySelector('#menu-posts'));
-
- // Titre de page (h1) et bouton d'ajout
- document.querySelectorAll('.wp-heading-inline, .page-title-action').forEach(replaceInTextNodes);
-
- // Notifications après sauvegarde (Article publié, mis à jour…)
- document.querySelectorAll('#message, .notice').forEach(replaceInTextNodes);
-
- // Boîte de publication — lien "Voir l'article"
- replaceInTextNodes(document.querySelector('.submitbox'));
-
- // Options d'écran — "Articles par page"
- replaceInTextNodes(document.querySelector('#screen-options-wrap'));
-
- // Bouton de recherche (attribut value + aria-label)
- var searchSubmit = document.querySelector('#search-submit');
- if (searchSubmit) {
- if (searchSubmit.value) {
- searchSubmit.value = applyReplacements(searchSubmit.value);
- }
- var ariaLabel = searchSubmit.getAttribute('aria-label');
- if (ariaLabel) {
- searchSubmit.setAttribute('aria-label', applyReplacements(ariaLabel));
- }
- }
-
- // Titre de l'onglet du navigateur
- document.title = applyReplacements(document.title);
- }
-
- function updatePostboxVisibility() {
- document.querySelectorAll('.postbox').forEach((postBox) => {
- if (postBox.id.startsWith('pods')) {
- // body-en is controlled by language tabs — never auto-hide it
- if (postBox.id === 'pods-meta-body-en') return;
- const fields = postBox.querySelectorAll('tr');
- const hasVisibleFields = Array.from(fields).some(field => field.style.display !== 'none');
- postBox.style.display = hasVisibleFields ? 'block' : 'none';
- }
- });
- }
-
- // Force Visual (TinyMCE) mode on page load.
- // WP stores the last-used editor mode in localStorage and restores it at document.ready.
- // When Code mode is restored, TinyMCE is never initialised — tinymce.get() returns null.
- // Instead, check the wrapper's CSS class:
- // tmce-active = Visual mode (fine)
- // html-active = Code mode (switch to Visual)
- function ensureVisualMode(editorId, attempt) {
- attempt = attempt || 0;
- if (attempt > 15) return;
- var wrap = document.getElementById('wp-' + editorId + '-wrap');
- if (!wrap) {
- setTimeout(function() { ensureVisualMode(editorId, attempt + 1); }, 200);
- return;
- }
- if (wrap.classList.contains('html-active')) {
- var ed = window.tinymce && tinymce.get(editorId);
- if (!ed || !ed.initialized) {
- // TinyMCE not ready yet — retry rather than calling switchEditors.go() prematurely
- setTimeout(function() { ensureVisualMode(editorId, attempt + 1); }, 200);
- return;
- }
- if (typeof switchEditors !== 'undefined') {
- switchEditors.go(editorId, 'tmce');
- }
- return;
- }
- if (!wrap.classList.contains('tmce-active')) {
- // Mode not yet determined — retry
- setTimeout(function() { ensureVisualMode(editorId, attempt + 1); }, 200);
- }
- }
-
- // Phase 1: insert the tab bar and relocate #pods-meta-body-en.
- // The DOM move breaks TinyMCE's iframe (browsers reset iframe content on detach),
- // so we leave the container visible here and let Pods/TinyMCE initialise normally.
- // The broken iframe is repaired by reinitEditor() on first EN tab open.
- function setupBodyTabsDom() {
- var nativeEditor = document.getElementById('postdivrich') || document.getElementById('postdiv');
- var bodyEnBox = document.getElementById('pods-meta-body-en');
- if (!nativeEditor || !bodyEnBox) return;
-
- var tabBar = document.createElement('div');
- tabBar.className = 'body-lang-tabs';
- tabBar.innerHTML =
- '' +
- '';
- nativeEditor.parentNode.insertBefore(tabBar, nativeEditor);
-
- // Move EN metabox to sit right after the native editor for correct visual layout.
- // Do NOT hide it yet — Pods must init TinyMCE with the container visible so the
- // iframe can measure its dimensions. Page is still opacity:0 so no flash.
- nativeEditor.parentNode.insertBefore(bodyEnBox, nativeEditor.nextSibling);
- }
-
- // Rebuild a TinyMCE editor whose iframe is broken (empty/non-interactive).
- // This happens when TinyMCE is initialised on a hidden (display:none) element:
- // the iframe can't measure dimensions and its document body stays empty.
- //
- // We reinit from tinyMCEPreInit.mceInit — first trying the editor's own config
- // (registered by Pods server-side), falling back to 'content' (the native WP editor).
- //
- // Inline toolbar positioning fix:
- // TinyMCE's 'wordpress' plugin captures document.getElementById(id+'_ifr') during
- // 'preinit' — before the iframe is created — so mceIframe is always null.
- // Fix: intercept getElementById during preinit so the 'wordpress' plugin captures
- // a proxyIframe instead of null. After init, proxy delegates to the real iframe.
- function reinitEditor(editorId) {
- var ed = window.tinymce && tinymce.get(editorId);
- // Preserve existing content before destroying the instance
- var savedContent = '';
- if (ed) {
- try { savedContent = ed.getContent(); } catch (e) {}
- ed.remove();
- }
- if (!savedContent) {
- var ta = document.getElementById(editorId);
- if (ta) savedContent = ta.value || '';
- }
-
- if (!window.tinyMCEPreInit || !window.tinymce) return;
-
- // Use the editor's own server-side config if available, else clone from 'content'
- var baseInit = (tinyMCEPreInit.mceInit && tinyMCEPreInit.mceInit[editorId])
- || (tinyMCEPreInit.mceInit && tinyMCEPreInit.mceInit['content']);
- if (!baseInit) return;
-
- // Proxy iframe: getBoundingClientRect() falls back to the editor wrap
- var wrapId = 'wp-' + editorId + '-wrap';
- var proxyIframe = {
- getBoundingClientRect: function() {
- var el = document.getElementById(wrapId);
- return el ? el.getBoundingClientRect()
- : { top: 0, left: 0, right: window.innerWidth,
- bottom: window.innerHeight, width: window.innerWidth,
- height: window.innerHeight };
- }
- };
-
- var savedGetById = document.getElementById;
- var origSetup = baseInit.setup;
- var content = savedContent;
-
- tinymce.init($.extend({}, baseInit, {
- selector: '#' + editorId,
- setup: function(editor) {
- if (typeof origSetup === 'function') origSetup(editor);
-
- editor.on('focus', function() {
- window.wpActiveEditor = editorId;
- });
-
- editor.on('preinit', function() {
- document.getElementById = function(id) {
- if (id === editorId + '_ifr') return proxyIframe;
- return savedGetById.call(document, id);
- };
- setTimeout(function() {
- document.getElementById = savedGetById;
- }, 0);
- });
-
- editor.on('init', function() {
- // Point proxy to real iframe
- var realIframe = savedGetById.call(document, editorId + '_ifr');
- if (realIframe) {
- proxyIframe.getBoundingClientRect = function() {
- return realIframe.getBoundingClientRect();
- };
- }
- // Restore content that was in the textarea
- if (content) {
- editor.setContent(content);
- }
- });
- }
- }));
- }
-
- // Phase 2: wire tab click handlers — runs at t=100ms after metabox reordering.
- function initBodyLanguageTabs() {
- var nativeEditor = document.getElementById('postdivrich') || document.getElementById('postdiv');
- var bodyEnBox = document.getElementById('pods-meta-body-en');
- var tabBar = document.querySelector('.body-lang-tabs');
- if (!nativeEditor || !bodyEnBox || !tabBar) {
- // body_en not available (e.g. contributor role) — still force visual mode on main editor
- if (nativeEditor) ensureVisualMode('content');
- return;
- }
-
- var enEditorId = 'pods-form-ui-pods-meta-body-en';
- var enTmceReady = false;
-
- // Hide EN panel — page is still opacity:0, user won't see the switch
- bodyEnBox.style.display = 'none';
-
- tabBar.querySelectorAll('.body-lang-tab').forEach(function(btn) {
- btn.addEventListener('click', function() {
- tabBar.querySelectorAll('.body-lang-tab').forEach(function(b) {
- b.classList.remove('is-active');
- });
- btn.classList.add('is-active');
-
- var revealedPanel;
-
- if (btn.dataset.panel === 'fr') {
- bodyEnBox.style.display = 'none';
- nativeEditor.style.opacity = '0';
- nativeEditor.style.display = '';
- revealedPanel = nativeEditor;
- } else {
- nativeEditor.style.display = 'none';
- bodyEnBox.style.opacity = '0';
- bodyEnBox.style.display = 'block';
- revealedPanel = bodyEnBox;
-
- if (!enTmceReady) {
- enTmceReady = true;
- // Reinit while container is visible so TinyMCE can measure dimensions
- reinitEditor(enEditorId);
- }
- }
-
- // Notify TinyMCE to reflow, then fade in once layout is correct
- setTimeout(function() {
- window.dispatchEvent(new Event('resize'));
- requestAnimationFrame(function() {
- requestAnimationFrame(function() {
- revealedPanel.style.opacity = '';
- });
- });
- }, 50);
- });
- });
-
- // Ensure both editors start in Visual (not Code) mode
- ensureVisualMode('content');
- ensureVisualMode(enEditorId);
- }
-
- function groupAxesCheckboxes() {
- if (!window.thalimAxesGroups || !thalimAxesGroups.length) return;
-
- var row = document.querySelector('.pods-form-ui-row-name-axes-thematiques');
- if (!row) return;
-
- var list = row.querySelector('ul');
- if (!list) return;
-
- // Already grouped — nothing to do
- if (list.querySelector('.axes-group-label')) return;
-
- // Map existing
by checkbox value; preserve "add new" button
- var liMap = {};
- var addNewItem = null;
- list.querySelectorAll('li').forEach(function(li) {
- if (li.classList.contains('pods-pick-add-new')) { addNewItem = li; return; }
- var cb = li.querySelector('input[type="checkbox"]');
- if (cb) liMap[cb.value] = li;
- });
-
- // Rebuild list in group order
- list.innerHTML = '';
- thalimAxesGroups.forEach(function(group) {
- var labelLi = document.createElement('li');
- labelLi.className = 'axes-group-label';
- labelLi.textContent = group.label;
- list.appendChild(labelLi);
- group.terms.forEach(function(term) {
- var li = liMap[String(term.id)];
- if (li) list.appendChild(li);
- });
- });
-
- if (addNewItem) list.appendChild(addNewItem);
- }
-
- var REF_BIB_EDITOR_ID = 'pods-form-ui-pods-meta-reference-bibliographique';
- var refBibReinited = false;
-
- // Reinit the référence bibliographique TinyMCE editor.
- // Called at page load (if the field is already visible) and by the
- // MutationObserver (when the field becomes visible after a category change).
- function initRefBibEditor() {
- if (refBibReinited) return;
- var row = document.querySelector('.pods-form-ui-row-name-reference-bibliographique');
- if (!row || row.style.display === 'none') return;
- refBibReinited = true;
- reinitEditor(REF_BIB_EDITOR_ID);
- ensureVisualMode(REF_BIB_EDITOR_ID);
- }
-
- function initAxesGroupObserver() {
- // Pods shows/hides conditional rows by removing inline style="display:none"
- // Watch the entire Pods meta form for style changes on the axes row
- var podsForm = document.querySelector('.pods-pick-values, #pods-meta-champs-contextuels, form#post');
- if (!podsForm) podsForm = document.body;
-
- var observer = new MutationObserver(function(mutations) {
- for (var i = 0; i < mutations.length; i++) {
- var target = mutations[i].target;
- if (target.classList && target.classList.contains('pods-form-ui-row-name-axes-thematiques')) {
- if (target.style.display !== 'none') {
- setTimeout(groupAxesCheckboxes, 50);
- }
- }
- // Reinit TinyMCE on the référence bibliographique field when its
- // row becomes visible — Pods hides it with display:none which breaks
- // the TinyMCE iframe. Only reinit once per page load.
- if (!refBibReinited && target.classList &&
- target.classList.contains('pods-form-ui-row-name-reference-bibliographique')) {
- if (target.style.display !== 'none') {
- setTimeout(initRefBibEditor, 100);
- }
- }
- }
- });
-
- observer.observe(podsForm, { attributes: true, attributeFilter: ['style'], subtree: true });
- }
-
- function initPostEditPage() {
- // Disable category options (CSS handles the color)
- const categorieSelect = document.querySelector('#pods-form-ui-pods-meta-categorie');
- if (categorieSelect) {
- const categoriesToDisable = ['1', '12', '5', '20'];
- categorieSelect.querySelectorAll('option').forEach(option => {
- if (categoriesToDisable.includes(option.value)) {
- option.disabled = true;
- }
- });
- }
-
- // Reorder meta boxes
- const sideSortables = document.querySelector('#side-sortables');
- if (sideSortables) {
- const typeDannonce = document.querySelector('#pods-meta-type-dannonce');
- const affichageAccueil = document.querySelector('#pods-meta-affichage-sur-laccueil');
- const thematique = document.querySelector('#pods-meta-thematique');
- if (typeDannonce) sideSortables.prepend(typeDannonce);
- if (affichageAccueil) sideSortables.appendChild(affichageAccueil);
- if (thematique) sideSortables.appendChild(thematique);
- }
-
- const submitDiv = document.querySelector('#submitdiv');
- if (submitDiv && submitDiv.parentNode) {
- submitDiv.parentNode.appendChild(submitDiv);
- }
-
- const champsContextuels = document.querySelector('#pods-meta-champs-contextuels');
- if (champsContextuels && champsContextuels.parentNode) {
- champsContextuels.parentNode.prepend(champsContextuels);
- }
-
- initBodyLanguageTabs();
- initRefBibEditor();
- groupAxesCheckboxes();
- initAxesGroupObserver();
- updatePostboxVisibility();
- initDatePickerPopoverFix();
- initInfoPopovers();
-
- // Place #pods-meta-documents-joints in #normal-sortables, right after
- // #pods-meta-champs-contextuels. This keeps it out of #post-body-content
- // (the body editor section) regardless of whether champsContextuels is
- // currently visible. When champsContextuels is hidden it takes no space,
- // so documentsJoints simply appears first in #normal-sortables.
- const documentsJoints = document.querySelector('#pods-meta-documents-joints');
- if (documentsJoints) {
- if (champsContextuels && champsContextuels.parentNode) {
- champsContextuels.parentNode.insertBefore(documentsJoints, champsContextuels.nextSibling);
- } else {
- const normalSortables = document.querySelector('#normal-sortables');
- if (normalSortables) normalSortables.prepend(documentsJoints);
- }
- }
-
- // Inject separator row for the Membres grid layout
- var membresTbody = document.querySelector('#pods-meta-membres .form-table tbody');
- if (membresTbody && !membresTbody.querySelector('.membres-grid-separator')) {
- var sep = document.createElement('tr');
- sep.className = 'membres-grid-separator';
- membresTbody.appendChild(sep);
- }
- updateMembresGridSeparator();
- }
-
- var INFO_ICON = '';
- var TRANSLATE_ICON = '';
-
- var TRANSLATE_LINES = [
- 'Traduction en anglais apr\u00e8s //',
- 'ex\u00a0: Texte en fran\u00e7ais // English text'
- ];
-
- // Tips without a `page` key default to 'post'.
- // type: 'translate' uses the globe icon + green button style.
- var INFO_TIPS = [
- // --- post edit page: info ---
- {
- selector: '.wp-heading-inline',
- lines: [
- 'Saisir le titre anglais apr\u00e8s //',
- 'ex\u00a0: Titre de l\u2019annonce // Title of the announcement'
- ]
- },
- {
- selector: '#pods-meta-documents-joints .postbox-header h2',
- lines: [
- 'Ajouter les images dans les documents.',
- 'Ajouter les l\u00e9gendes comme titre du document.'
- ]
- },
- {
- selector: '#pods-meta-membres .postbox-header h2',
- lines: [
- 'Le champ fonction change le libell\u00e9 de la liste de personnes cit\u00e9es.',
- 'Le champ membre permet de lister les membres de Thalim li\u00e9s \u00e0 l\u2019annonce.',
- 'Le champ autre personnes permet de lister des personnes ext\u00e9rieures \u00e0 Thalim.'
- ]
- },
- {
- selector: '#pods-meta-dates .postbox-header h2',
- lines: [
- 'Pour entrer une date sans l\u2019heure, r\u00e9gler l\u2019heure sur 00\u202f:00.'
- ]
- },
- {
- selector: '#pods-meta-affichage-sur-laccueil .postbox-header h2',
- lines: [
- '\u00c9pingler l\u2019annonce dans le diaporama la fait s\u2019afficher avant les autres.'
- ]
- },
- {
- selector: '#pods-meta-medias .postbox-header h2',
- lines: [
- 'Pour ajouter un m\u00e9dia Canal\u00a0U, copier le lien depuis \u00ab\u00a0Citer cette ressource\u00a0\u00bb.',
- 'ex\u00a0: https://www.canal-u.tv/166564'
- ]
- },
- // --- post edit page: translate ---
- { type: 'translate', selector: '#pods-meta-documents-joints .postbox-header h2', lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-sous-titre th', lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-lieu th', lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-titre-du-lien-externe-1 th', lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-titre-du-lien-externe-2 th', lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-titre-du-lien-externe-3 th', lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-fonction-organisation th', lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-fonction-intervention th', lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-fonction-candidat th', lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-fonction-realisation th', lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-fonction-dirige th', lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-fonction-redaction th', lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-fonction-auteur th', lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-fonction-responsable th', lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-autre th', lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-concerne th', lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-directeur th', lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-direction-d-ouvrage th',lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-intervenant th', lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-autre-fonction-participants th', lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-type-autre th', lines: TRANSLATE_LINES },
- // --- contenu_general edit page: translate ---
- { type: 'translate', selector: '.pods-form-ui-row-name-umr th', lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-thalim th', lines: TRANSLATE_LINES },
- { type: 'translate', selector: '.pods-form-ui-row-name-siecles th', lines: TRANSLATE_LINES },
- // --- user/profile edit page: translate ---
- { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-du-lien-1 th', lines: TRANSLATE_LINES },
- { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-du-lien-2 th', lines: TRANSLATE_LINES },
- { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-du-lien-3 th', lines: TRANSLATE_LINES },
- { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-du-lien-4 th', lines: TRANSLATE_LINES },
- { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-complement-de-role-1 th', lines: TRANSLATE_LINES },
- { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-complement-de-role-2 th', lines: TRANSLATE_LINES },
- { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-complement-de-role-3 th', lines: TRANSLATE_LINES },
- { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-affichage-du-statut-1 th', lines: TRANSLATE_LINES },
- { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-affichage-du-statut-2 th', lines: TRANSLATE_LINES },
- { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-affichage-du-statut-3 th', lines: TRANSLATE_LINES },
- { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-affiliation-autre th', lines: TRANSLATE_LINES },
- { type: 'translate', page: 'user', selector: '.pods-form-ui-row-name-titre-de-these th', lines: TRANSLATE_LINES },
- // --- taxonomy edit pages: translate ---
- { type: 'translate', page: 'taxonomy', selector: 'label[for="name"]', lines: TRANSLATE_LINES },
- // --- user/profile edit page: info ---
- {
- page: 'user',
- selector: '.pods-form-ui-label-pods-meta-identifiant-hal',
- lines: [
- 'Renseigner votre idHAL (en lettres), pas votre PersonId (en chiffres).'
- ]
- },
- {
- page: 'user',
- selector: '.pods-form-ui-label-pods-meta-affichage-du-statut-1',
- lines: [
- 'Texte de statut affiché sur le profil publique.'
- ]
- }
- ];
-
- var _popoverCloseHandlerRegistered = false;
-
- function initInfoPopovers(currentPage) {
- currentPage = currentPage || 'post';
-
- INFO_TIPS.forEach(function(tip) {
- if ((tip.page || 'post') !== currentPage) return;
-
- var el = document.querySelector(tip.selector);
- if (!el) return;
-
- var isTranslate = tip.type === 'translate';
-
- var btn = document.createElement('button');
- btn.type = 'button';
- btn.className = isTranslate ? 'thalim-translate-btn' : 'thalim-info-btn';
- btn.setAttribute('aria-label', isTranslate ? 'Traduction bilingue' : 'Informations');
- btn.innerHTML = isTranslate ? TRANSLATE_ICON : INFO_ICON;
-
- var popover = document.createElement('div');
- popover.className = 'thalim-info-popover' + (isTranslate ? ' thalim-translate-popover' : '');
- popover.innerHTML = tip.lines.map(function(line) {
- return '' + line + '
';
- }).join('');
-
- var wrapper = document.createElement('span');
- wrapper.className = 'thalim-info-wrapper';
- wrapper.appendChild(btn);
- wrapper.appendChild(popover);
-
- el.appendChild(wrapper);
-
- btn.addEventListener('click', function(e) {
- e.stopPropagation();
- var isOpen = popover.classList.contains('is-open');
- document.querySelectorAll('.thalim-info-popover.is-open').forEach(function(p) {
- p.classList.remove('is-open');
- });
- if (!isOpen) {
- var rect = btn.getBoundingClientRect();
- popover.style.top = (rect.bottom + 6) + 'px';
- popover.style.left = (rect.left + rect.width / 2) + 'px';
- popover.classList.add('is-open');
- }
- });
-
- popover.addEventListener('click', function(e) {
- e.stopPropagation();
- });
- });
-
- if (!_popoverCloseHandlerRegistered) {
- _popoverCloseHandlerRegistered = true;
- document.addEventListener('click', function() {
- document.querySelectorAll('.thalim-info-popover.is-open').forEach(function(p) {
- p.classList.remove('is-open');
- });
- });
- }
- }
-
- // 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 = 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();
- 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() === '\u00c0 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\u00f4le') {
- roleLabel.textContent = 'R\u00f4le sur le site';
- }
- }
-
- // Gutenberg's Popover component closes on outside click via focusout detection.
- // But if focus never enters the popover, focusout never fires and clicking outside
- // does nothing. Fix: focus the popover container as soon as it appears in the DOM.
- function initDatePickerPopoverFix() {
- var observer = new MutationObserver(function(mutations) {
- for (var i = 0; i < mutations.length; i++) {
- var added = mutations[i].addedNodes;
- for (var j = 0; j < added.length; j++) {
- var node = added[j];
- if (node.nodeType !== 1) continue;
- var content = node.classList.contains('components-popover__content')
- ? node
- : node.querySelector && node.querySelector('.components-popover__content');
- if (content) {
- var c = content;
- requestAnimationFrame(function() {
- if (!c.hasAttribute('tabindex')) c.setAttribute('tabindex', '-1');
- c.focus();
- });
- }
- }
- }
- });
- observer.observe(document.body, { childList: true, subtree: true });
- }
-
- function updateMembresGridSeparator() {
- var sep = document.querySelector('#pods-meta-membres .membres-grid-separator');
- if (!sep) return;
- var autreRows = document.querySelectorAll('#pods-meta-membres [class*="pods-form-ui-row-name-autre-"]');
- var anyVisible = Array.from(autreRows).some(function(row) {
- return row.style.display !== 'none';
- });
- sep.style.display = anyVisible ? '' : 'none';
- }
-
- // Inject a "Type de programme" filter select into the taxonomy search form.
- // The form already has hidden taxonomy/post_type fields so the select value
- // is submitted with them and picked up by pre_get_terms server-side.
- function initProgrammeFilter() {
- var form = document.querySelector('form.search-form');
- if (!form) return;
-
- var types = [
- 'Programme subventionné',
- 'Autre programme',
- 'Ancien programme'
- ];
-
- // Read current filter value from the URL.
- var params = new URLSearchParams(window.location.search);
- var current = params.get('type_de_programme') || '';
-
- var select = document.createElement('select');
- select.name = 'type_de_programme';
- select.id = 'filter-type-de-programme';
- select.style.cssText = 'margin-right:6px;';
-
- var blank = document.createElement('option');
- blank.value = '';
- blank.textContent = 'Tous les types';
- select.appendChild(blank);
-
- types.forEach(function(type) {
- var opt = document.createElement('option');
- opt.value = type;
- opt.textContent = type;
- if (type === current) opt.selected = true;
- select.appendChild(opt);
- });
-
- // Insert before the first (search-box) inside the form.
- var searchBox = form.querySelector('p.search-box');
- form.insertBefore(select, searchBox || null);
- }
-
- $(document).ready(function() {
- renameArticlesToAnnonces();
-
- if (isPostEditPage()) {
- setupBodyTabsDom();
- }
-
- setTimeout(() => {
- if (isPostEditPage()) {
- initPostEditPage();
- }
- if (isProfileEditPage()) {
- initProfileEditors();
- }
- var TRANSLATE_TAXONOMIES = ['axe_thematique', 'programme_de_recherche', 'post_tag'];
- var isTaxonomyListPage = TRANSLATE_TAXONOMIES.some(function(tax) {
- return window.location.search.indexOf('taxonomy=' + tax) !== -1;
- });
- if (isTaxonomyListPage) {
- initInfoPopovers('taxonomy');
- }
- if (window.location.search.indexOf('taxonomy=programme_de_recherche') !== -1) {
- initProgrammeFilter();
- }
- document.body.classList.add('admin-mods-ready');
- }, 100);
-
- // Fallback: force reveal after 2s in case the 100ms path failed (e.g. JS error mid-init)
- setTimeout(() => {
- document.body.classList.add('admin-mods-ready');
- }, 2000);
-
- $('#pods-form-ui-pods-meta-categorie').change(function() {
- setTimeout(function() {
- updatePostboxVisibility();
- updateMembresGridSeparator();
- }, 10);
- });
-
- if (isProfileEditPage() || window.pagenow === 'edit-tags' || window.pagenow === 'term') {
- $(window).on('load', function() {
- var scope = getProfileForm() || document;
- scope.querySelectorAll('.pods-dfv-container-wysiwyg textarea').forEach(function(ta) {
- if (!ta.id) return;
- ensureVisualMode(ta.id);
- });
- });
- }
-
- if (isPodsModal()) {
- var lockSeanceCategory = function() {
- var itemId = $('#post_ID').val();
- if (window.PodsDFV && itemId) {
- window.PodsDFV.setFieldValue('post', itemId, 'categorie', '12', 0);
- }
-
- // Lock category select to 12 in iframe — delay to run after Pods React re-render
- setTimeout(function() {
- var $select = $('#pods-form-ui-pods-meta-categorie');
- if ($select.length) {
- $select.find('option').each(function() {
- this.disabled = this.value !== '12';
- });
- $select.val('12');
- }
- updatePostboxVisibility();
- }, 200);
- };
-
- // Dans l'iframe de la modale, window.load peut déjà être passé au moment
- // où ce code s'exécute : s'abonner à un load déjà émis ne rejoue rien.
- // On exécute donc tout de suite si la page est déjà chargée.
- if (document.readyState === 'complete') {
- lockSeanceCategory();
- } else {
- $(window).on('load', lockSeanceCategory);
- }
- }
- });
-
-})(jQuery);
diff --git a/js/adminFormRestore.js b/js/adminFormRestore.js
deleted file mode 100644
index ece0ae9..0000000
--- a/js/adminFormRestore.js
+++ /dev/null
@@ -1,119 +0,0 @@
-(function($) {
- 'use strict';
-
- var STORAGE_PREFIX = 'thalim_form_restore_';
- var MAX_AGE_MS = 10 * 60 * 1000; // 10 min
-
- function getPostId() {
- return $('#post_ID').val() || 'new';
- }
-
- function getStorageKey() {
- return STORAGE_PREFIX + getPostId();
- }
-
- function saveFormData() {
- var data = { timestamp: Date.now() };
-
- var $title = $('#title');
- if ($title.length) data.title = $title.val();
-
- var $content = $('#content');
- if ($content.length) {
- if (typeof tinyMCE !== 'undefined' && tinyMCE.get('content')) {
- data.content = tinyMCE.get('content').getContent();
- } else {
- data.content = $content.val();
- }
- }
-
- $('[name^="pods_meta_"]').each(function() {
- data[this.name] = $(this).val();
- });
-
- try {
- sessionStorage.setItem(getStorageKey(), JSON.stringify(data));
- } catch (e) {}
- }
-
- function restoreFormData() {
- var navEntries = performance.getEntriesByType('navigation');
- if (!navEntries.length || navEntries[0].type !== 'back_forward') return;
-
- var key = getStorageKey();
- var stored, data;
- try {
- stored = sessionStorage.getItem(key);
- if (!stored) return;
- data = JSON.parse(stored);
- } catch (e) { return; }
-
- if (!data.timestamp || Date.now() - data.timestamp > MAX_AGE_MS) {
- try { sessionStorage.removeItem(key); } catch (e) {}
- return;
- }
-
- // Restaurer titre
- if (data.title !== undefined) $('#title').val(data.title);
-
- // Restaurer contenu (TinyMCE ou textarea brut)
- if (data.content !== undefined) {
- $('#content').val(data.content);
- if (typeof tinyMCE !== 'undefined') {
- var ed = tinyMCE.get('content');
- if (ed) {
- ed.setContent(data.content);
- } else {
- tinyMCE.on('AddEditor', function(e) {
- if (e.editor.id === 'content') {
- e.editor.on('init', function() {
- e.editor.setContent(data.content);
- });
- }
- });
- }
- }
- }
-
- // Restaurer champs Pods après rendu React
- var restorePods = function() {
- setTimeout(function() {
- var postId = getPostId();
- Object.keys(data).forEach(function(name) {
- if (!name.startsWith('pods_meta_')) return;
- var fieldName = name.replace(/^pods_meta_/, '');
- var value = data[name];
- var $el = $('[name="' + name + '"]');
- if ($el.length) $el.val(value).trigger('change');
- if (window.PodsDFV && postId) {
- try { window.PodsDFV.setFieldValue('post', postId, fieldName, value, 0); } catch (e) {}
- }
- });
- }, 300);
- };
-
- if (document.readyState === 'complete') {
- restorePods();
- } else {
- $(window).on('load', restorePods);
- }
-
- // Notice informative
- var $notice = $('
Votre contenu a \u00e9t\u00e9 restaur\u00e9 suite \u00e0 une erreur de validation. V\u00e9rifiez les champs obligatoires avant de publier.
');
- $('#wpbody-content').find('.wrap').first().find('h1').after($notice);
- }
-
- $(document).ready(function() {
- if (window.pagenow !== 'post' && window.pagenow !== 'post-new') return;
-
- // Nettoyage si sauvegarde réussie
- if ($('.notice-success, #message.updated').length) {
- try { sessionStorage.removeItem(getStorageKey()); } catch (e) {}
- return;
- }
-
- $('#post').on('submit', saveFormData);
- restoreFormData();
- });
-
-})(jQuery);
diff --git a/js/membresFilters.js b/js/membresFilters.js
index 93b0d3c..ed49358 100644
--- a/js/membresFilters.js
+++ b/js/membresFilters.js
@@ -1,4 +1,15 @@
document.addEventListener('DOMContentLoaded', function () {
+ // ── Row navigation (replaces inline onclick) ──────────────
+ // Delegated listener: rows carry their target in data-url.
+ document.querySelectorAll('.membres-table tbody').forEach(function (tbody) {
+ tbody.addEventListener('click', function (e) {
+ var row = e.target.closest('tr[data-url]');
+ if (row && row.dataset.url) {
+ window.location.href = row.dataset.url;
+ }
+ });
+ });
+
// ── Filters toggle ────────────────────────────────────────
var membresToggleBtn = document.getElementById('membres-filters-toggle');
var membresFiltersEl = document.getElementById('membres-filters');
diff --git a/page-annonces.php b/page-annonces.php
index 419af66..bcd5093 100644
--- a/page-annonces.php
+++ b/page-annonces.php
@@ -1,15 +1,16 @@
parent)
- ? $active_cat_obj->parent
- : $active_cat_id;
-}
+$active_rubrique_id = thalim_archive_active_rubrique($active_cat_id);
$context['active_rubrique'] = $active_rubrique_id;
// Base filter params preserved across all filter links
@@ -76,7 +71,6 @@ $query_args = [
'posts_per_page' => 12,
'orderby' => 'date',
'order' => 'DESC',
- 'lang' => '',
'tax_query' => $tax_query,
'thalim_event_date_order' => true,
];
@@ -100,61 +94,20 @@ $context['filter_axes'] = $current_axes;
$page_url = get_permalink();
$all_cats = get_categories(['taxonomy' => 'category', 'hide_empty' => false, 'exclude' => $excluded_cat_ids]);
-$filter_parents = [];
-foreach ($all_cats as $cat) {
- if ($cat->parent == 0) {
- $filter_parents[] = [
- 'id' => $cat->term_id,
- 'name' => thalim_cat_name($cat),
- 'slug' => $cat->slug,
- 'link' => $base_filter_params
- ? add_query_arg($base_filter_params, get_category_link($cat->term_id))
- : get_category_link($cat->term_id),
- ];
- }
-}
-$context['filter_parents'] = $filter_parents;
+// Liens de filtre : navigation vers la page de catégorie, en conservant axe/dates
+$make_cat_link = function ($cat) use ($base_filter_params) {
+ return $base_filter_params
+ ? add_query_arg($base_filter_params, get_category_link($cat->term_id))
+ : get_category_link($cat->term_id);
+};
+$context['filter_parents'] = thalim_archive_filter_parents($all_cats, $make_cat_link);
+
+$filter_categories = thalim_archive_filter_children($all_cats, $active_rubrique_id, $make_cat_link);
-$filter_categories = [];
-if ($active_rubrique_id) {
- foreach ($all_cats as $cat) {
- if ($cat->parent == $active_rubrique_id) {
- $filter_categories[] = [
- 'id' => $cat->term_id,
- 'name' => thalim_cat_name($cat),
- 'slug' => $cat->slug,
- 'link' => $base_filter_params
- ? add_query_arg($base_filter_params, get_category_link($cat->term_id))
- : get_category_link($cat->term_id),
- ];
- }
- }
-}
// Add "Autres" entry if active rubrique has posts directly assigned to it
-if ($active_rubrique_id && !empty($filter_categories)) {
- $lang = thalim_current_language();
- $direct_check = new WP_Query([
- 'post_type' => 'post',
- 'posts_per_page' => 1,
- 'fields' => 'ids',
- 'no_found_rows' => true,
- 'lang' => '',
- 'tax_query' => [[
- 'taxonomy' => 'category',
- 'field' => 'term_id',
- 'terms' => [$active_rubrique_id],
- 'include_children' => false,
- ]],
- ]);
- if ($direct_check->have_posts()) {
- $params = array_filter(array_merge($base_filter_params, ['filter_cat' => $active_rubrique_id, 'filter_autres' => 1]));
- $filter_categories[] = [
- 'id' => 'autres',
- 'name' => $lang === 'en' ? 'Other' : 'Autres',
- 'slug' => 'autres',
- 'link' => add_query_arg($params, $page_url),
- ];
- }
+if ($active_rubrique_id && !empty($filter_categories) && thalim_rubrique_has_direct_posts($active_rubrique_id)) {
+ $params = array_filter(array_merge($base_filter_params, ['filter_cat' => $active_rubrique_id, 'filter_autres' => 1]));
+ $filter_categories[] = thalim_archive_autres_entry(add_query_arg($params, $page_url));
}
$context['filter_categories'] = $filter_categories;
diff --git a/page-le-laboratoire.php b/page-le-laboratoire.php
index f04723e..22105e2 100644
--- a/page-le-laboratoire.php
+++ b/page-le-laboratoire.php
@@ -91,18 +91,19 @@ unset( $group );
$context['axes_groups'] = array_values( $axes_map );
// ── Body (English override) ──────────────────────────────────
-$context['body_en'] = apply_filters( 'the_content', get_post_meta( $page_id, 'body_en', true ) ?: '' );
+$context['body_en'] = apply_filters( 'the_content', wp_kses_post( get_post_meta( $page_id, 'body_en', true ) ?: '' ) );
// ── WYSIWYG fields ────────────────────────────────────────────
-$context['partenaires_internationaux'] = wpautop( ( $labo_lang === 'en' && get_post_meta( $page_id, 'partenaires_internationaux_en', true ) )
+// wp_kses_post: rendus en |raw dans le template (autoescape off)
+$context['partenaires_internationaux'] = wpautop( wp_kses_post( ( $labo_lang === 'en' && get_post_meta( $page_id, 'partenaires_internationaux_en', true ) )
? get_post_meta( $page_id, 'partenaires_internationaux_en', true )
- : ( get_post_meta( $page_id, 'partenaires_internationaux', true ) ?: '' ) );
-$context['partenaires_nationaux'] = wpautop( ( $labo_lang === 'en' && get_post_meta( $page_id, 'partenaires_nationaux_en', true ) )
+ : ( get_post_meta( $page_id, 'partenaires_internationaux', true ) ?: '' ) ) );
+$context['partenaires_nationaux'] = wpautop( wp_kses_post( ( $labo_lang === 'en' && get_post_meta( $page_id, 'partenaires_nationaux_en', true ) )
? get_post_meta( $page_id, 'partenaires_nationaux_en', true )
- : ( get_post_meta( $page_id, 'partenaires_nationaux', true ) ?: '' ) );
-$context['bibliotheques'] = wpautop( ( $labo_lang === 'en' && get_post_meta( $page_id, 'bibliotheques_en', true ) )
+ : ( get_post_meta( $page_id, 'partenaires_nationaux', true ) ?: '' ) ) );
+$context['bibliotheques'] = wpautop( wp_kses_post( ( $labo_lang === 'en' && get_post_meta( $page_id, 'bibliotheques_en', true ) )
? get_post_meta( $page_id, 'bibliotheques_en', true )
- : ( get_post_meta( $page_id, 'bibliotheques', true ) ?: '' ) );
+ : ( get_post_meta( $page_id, 'bibliotheques', true ) ?: '' ) ) );
// ── Edit link ─────────────────────────────────────────────────
$context['page_edit_link'] = current_user_can( 'edit_page', $page_id ) ? get_edit_post_link( $page_id ) : '';
diff --git a/page-programmes-de-recherche.php b/page-programmes-de-recherche.php
index b581081..a950af5 100644
--- a/page-programmes-de-recherche.php
+++ b/page-programmes-de-recherche.php
@@ -16,7 +16,7 @@ foreach ( $terms as $term ) {
$item = [
'name' => $term->name,
- 'description' => wpautop( $term->description ),
+ 'description' => wpautop( wp_kses_post( $term->description ) ),
'url' => get_term_link( $term ),
'annee_fin' => (int) get_term_meta( $term->term_id, 'annee_fin', true ),
];
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
new file mode 100644
index 0000000..fe37bc6
--- /dev/null
+++ b/phpcs.xml.dist
@@ -0,0 +1,26 @@
+
+
+ WordPress Coding Standards pour le thème THALIM.
+
+
+
+ .
+ vendor/*
+ node_modules/*
+ assets/vendor/*
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/search.php b/search.php
index a772475..2fb5b6c 100644
--- a/search.php
+++ b/search.php
@@ -1,18 +1,18 @@
parent)
- ? $active_cat_obj->parent
- : $active_cat_id;
-}
+$active_rubrique_id = thalim_archive_active_rubrique($active_cat_id);
$context['active_rubrique'] = $active_rubrique_id;
// Base URL for search filter links (language-aware)
@@ -72,7 +66,6 @@ $query_args = [
'posts_per_page' => 12,
'orderby' => 'relevance',
'order' => 'DESC',
- 'lang' => '',
'tax_query' => $tax_query,
];
if ($active_axe) {
@@ -98,59 +91,19 @@ $context['axe_stay_on_page'] = true;
// Rubrique/catégorie filter links (all preserve search term)
$all_cats = get_categories(['taxonomy' => 'category', 'hide_empty' => false, 'exclude' => $excluded_cat_ids]);
-$filter_parents = [];
-foreach ($all_cats as $cat) {
- if ($cat->parent == 0) {
- $params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id]));
- $filter_parents[] = [
- 'id' => $cat->term_id,
- 'name' => thalim_cat_name($cat),
- 'slug' => $cat->slug,
- 'link' => add_query_arg($params, $search_base),
- ];
- }
-}
-$context['filter_parents'] = $filter_parents;
+// Liens de filtre : on reste sur la recherche avec un paramètre filter_cat
+$make_filter_link = function ($cat) use ($base_filter_params, $search_base) {
+ $params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id]));
+ return add_query_arg($params, $search_base);
+};
+$context['filter_parents'] = thalim_archive_filter_parents($all_cats, $make_filter_link);
+
+$filter_categories = thalim_archive_filter_children($all_cats, $active_rubrique_id, $make_filter_link);
-$filter_categories = [];
-if ($active_rubrique_id) {
- foreach ($all_cats as $cat) {
- if ($cat->parent == $active_rubrique_id) {
- $params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id]));
- $filter_categories[] = [
- 'id' => $cat->term_id,
- 'name' => thalim_cat_name($cat),
- 'slug' => $cat->slug,
- 'link' => add_query_arg($params, $search_base),
- ];
- }
- }
-}
// Add "Autres" entry if active rubrique has posts directly assigned to it
-if ($active_rubrique_id && !empty($filter_categories)) {
- $lang = thalim_current_language();
- $direct_check = new WP_Query([
- 'post_type' => 'post',
- 'posts_per_page' => 1,
- 'fields' => 'ids',
- 'no_found_rows' => true,
- 'lang' => '',
- 'tax_query' => [[
- 'taxonomy' => 'category',
- 'field' => 'term_id',
- 'terms' => [$active_rubrique_id],
- 'include_children' => false,
- ]],
- ]);
- if ($direct_check->have_posts()) {
- $params = array_filter(array_merge($base_filter_params, ['filter_cat' => $active_rubrique_id, 'filter_autres' => 1]));
- $filter_categories[] = [
- 'id' => 'autres',
- 'name' => $lang === 'en' ? 'Other' : 'Autres',
- 'slug' => 'autres',
- 'link' => add_query_arg($params, $search_base),
- ];
- }
+if ($active_rubrique_id && !empty($filter_categories) && thalim_rubrique_has_direct_posts($active_rubrique_id)) {
+ $params = array_filter(array_merge($base_filter_params, ['filter_cat' => $active_rubrique_id, 'filter_autres' => 1]));
+ $filter_categories[] = thalim_archive_autres_entry(add_query_arg($params, $search_base));
}
$context['filter_categories'] = $filter_categories;
@@ -161,21 +114,24 @@ $context['posts'] = $posts;
// Search users (members) by display_name
$author_cards = [];
if ( $search_query ) {
- $excluded_role_ids = [ 600, 598 ]; // "À ranger", "Archive"
- $user_query = new WP_User_Query([
+ $excluded_role_ids = thalim_excluded_role_ids(); // « À ranger », « Archive » (résolus par slug)
+ $user_query_args = [
'search' => '*' . $search_query . '*',
'search_columns' => ['display_name'],
'number' => 6,
'orderby' => 'display_name',
'order' => 'ASC',
- 'meta_query' => [
+ ];
+ if ( $excluded_role_ids ) {
+ $user_query_args['meta_query'] = [
[
'key' => 'role_1',
'value' => $excluded_role_ids,
'compare' => 'NOT IN',
],
- ],
- ]);
+ ];
+ }
+ $user_query = new WP_User_Query( $user_query_args );
$lang = thalim_current_language();
// Direction IDs (same source as membres page and author page)
diff --git a/taxonomy.php b/taxonomy.php
index e6892e6..129ad42 100644
--- a/taxonomy.php
+++ b/taxonomy.php
@@ -10,15 +10,16 @@ $context['parent_slug'] = '';
$tax_object = get_taxonomy($taxonomy);
$context['taxonomy_label'] = $tax_object ? $tax_object->labels->singular_name : $taxonomy;
-$excluded_ids = [12, 31]; // Séance de séminaire, Non classé
-if ( ! is_user_logged_in() ) $excluded_ids[] = 9; // Vie du labo
+// Séance de séminaire, Non classé (+ Vie du labo pour les non-connectés)
+$excluded_ids = thalim_archive_excluded_cat_ids();
// Read filter query params
-$active_axe = isset($_GET['axe']) ? intval($_GET['axe']) : 0;
-$active_date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : '';
-$active_date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : '';
-$active_cat_id = isset($_GET['filter_cat']) ? intval($_GET['filter_cat']) : 0;
-$filter_autres = isset($_GET['filter_autres']) ? 1 : 0;
+$f = thalim_archive_read_filters();
+$active_axe = $f['axe'];
+$active_date_from = $f['date_from'];
+$active_date_to = $f['date_to'];
+$active_cat_id = $f['cat_id'];
+$filter_autres = $f['filter_autres'];
$context['active_axe'] = $active_axe;
$context['active_date_from'] = $active_date_from;
@@ -28,13 +29,7 @@ $context['active_cat_id'] = $active_cat_id;
$context['filter_autres'] = $filter_autres;
// Determine active rubrique from active category (parent if subcategory, itself if top-level)
-$active_rubrique_id = 0;
-if ($active_cat_id) {
- $active_cat_obj = get_category($active_cat_id);
- $active_rubrique_id = ($active_cat_obj && $active_cat_obj->parent)
- ? $active_cat_obj->parent
- : $active_cat_id;
-}
+$active_rubrique_id = thalim_archive_active_rubrique($active_cat_id);
$context['active_rubrique'] = $active_rubrique_id;
// Base params shared across all filter links (preserves active filters when navigating)
@@ -53,11 +48,11 @@ $tax_query = [
'field' => 'term_id',
'terms' => [$term->term_id],
],
- // Exclure les séances de séminaire (catégorie 12)
+ // Exclure les séances de séminaire
[
'taxonomy' => 'category',
'field' => 'term_id',
- 'terms' => [12],
+ 'terms' => [ thalim_cat_id('seance') ],
'operator' => 'NOT IN',
],
];
@@ -101,66 +96,28 @@ $context['axe_stay_on_page'] = !$axe_taxonomy_mode;
$current_term_url = get_term_link($term);
$all_cats = get_categories(['taxonomy' => 'category', 'hide_empty' => false, 'exclude' => $excluded_ids]);
-$filter_parents = [];
-foreach ($all_cats as $cat) {
- if ($cat->parent == 0) {
- $params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id]));
- $filter_parents[] = [
- 'id' => $cat->term_id,
- 'name' => thalim_cat_name($cat),
- 'slug' => $cat->slug,
- 'link' => add_query_arg($params, $current_term_url),
- ];
- }
-}
-$context['filter_parents'] = $filter_parents;
+// Liens de filtre : on reste sur l'URL du terme courant avec un paramètre filter_cat
+$make_filter_link = function ($cat) use ($base_filter_params, $current_term_url) {
+ $params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id]));
+ return add_query_arg($params, $current_term_url);
+};
+$context['filter_parents'] = thalim_archive_filter_parents($all_cats, $make_filter_link);
+
+$filter_categories = thalim_archive_filter_children($all_cats, $active_rubrique_id, $make_filter_link);
-$filter_categories = [];
-if ($active_rubrique_id) {
- foreach ($all_cats as $cat) {
- if ($cat->parent == $active_rubrique_id) {
- $params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id]));
- $filter_categories[] = [
- 'id' => $cat->term_id,
- 'name' => thalim_cat_name($cat),
- 'slug' => $cat->slug,
- 'link' => add_query_arg($params, $current_term_url),
- ];
- }
- }
-}
// Add "Autres" entry if active rubrique has posts directly assigned to it
+// (contraints au terme de taxonomie courant)
if ($active_rubrique_id && !empty($filter_categories)) {
- $lang = thalim_current_language();
- $direct_check = new WP_Query([
- 'post_type' => 'post',
- 'posts_per_page' => 1,
- 'fields' => 'ids',
- 'no_found_rows' => true,
- 'lang' => '',
- 'tax_query' => [
- 'relation' => 'AND',
- [
- 'taxonomy' => $taxonomy,
- 'field' => 'term_id',
- 'terms' => [$term->term_id],
- ],
- [
- 'taxonomy' => 'category',
- 'field' => 'term_id',
- 'terms' => [$active_rubrique_id],
- 'include_children' => false,
- ],
+ $has_direct = thalim_rubrique_has_direct_posts($active_rubrique_id, [
+ [
+ 'taxonomy' => $taxonomy,
+ 'field' => 'term_id',
+ 'terms' => [$term->term_id],
],
]);
- if ($direct_check->have_posts()) {
+ if ($has_direct) {
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $active_rubrique_id, 'filter_autres' => 1]));
- $filter_categories[] = [
- 'id' => 'autres',
- 'name' => $lang === 'en' ? 'Other' : 'Autres',
- 'slug' => 'autres',
- 'link' => add_query_arg($params, $current_term_url),
- ];
+ $filter_categories[] = thalim_archive_autres_entry(add_query_arg($params, $current_term_url));
}
}
$context['filter_categories'] = $filter_categories;
@@ -171,7 +128,6 @@ $posts = Timber::get_posts(array_merge([
'posts_per_page' => 12,
'orderby' => 'date',
'order' => 'DESC',
- 'lang' => '',
'thalim_event_date_order' => true,
], $extra_query_args));
$context['cards'] = thalim_get_cards_data($posts);
@@ -181,6 +137,6 @@ $context['posts'] = $posts;
$tax_lang = thalim_current_language();
$pres_fr = get_term_meta($term->term_id, 'presentation', true) ?: '';
$pres_en = get_term_meta($term->term_id, 'presentation_en', true) ?: '';
-$context['term_presentation'] = wpautop( ( $tax_lang === 'en' && $pres_en ) ? $pres_en : $pres_fr );
+$context['term_presentation'] = wpautop( wp_kses_post( ( $tax_lang === 'en' && $pres_en ) ? $pres_en : $pres_fr ) );
Timber::render('taxonomy.twig', $context);
diff --git a/templates/author.twig b/templates/author.twig
index a5ab085..7a450ad 100644
--- a/templates/author.twig
+++ b/templates/author.twig
@@ -22,13 +22,13 @@
{% if author.avatar_url %}