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:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,5 @@
|
|||||||
# --- Dépendances Composer ---
|
# --- Dépendances Composer ---
|
||||||
vendor/
|
/vendor/
|
||||||
|
|
||||||
# --- Artefacts SASS ---
|
# --- Artefacts SASS ---
|
||||||
.sass-cache/
|
.sass-cache/
|
||||||
|
|||||||
22
assets/vendor/iconoir/iconoir.css
vendored
Normal file
22
assets/vendor/iconoir/iconoir.css
vendored
Normal file
File diff suppressed because one or more lines are too long
13
assets/vendor/swiper/swiper-bundle.min.css
vendored
Normal file
13
assets/vendor/swiper/swiper-bundle.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
14
assets/vendor/swiper/swiper-bundle.min.js
vendored
Normal file
14
assets/vendor/swiper/swiper-bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
97
category.php
97
category.php
@@ -4,8 +4,8 @@ $category = get_queried_object();
|
|||||||
$context['category'] = Timber::get_term($category);
|
$context['category'] = Timber::get_term($category);
|
||||||
$context['cards'] = [];
|
$context['cards'] = [];
|
||||||
|
|
||||||
$excluded_ids = [12, 31]; // Séance de séminaire, Non classé
|
// Séance de séminaire, Non classé (+ Vie du labo pour les non-connectés)
|
||||||
if ( ! is_user_logged_in() ) $excluded_ids[] = 9; // Vie du labo
|
$excluded_ids = thalim_archive_excluded_cat_ids();
|
||||||
|
|
||||||
// Parent category slug for color theming
|
// Parent category slug for color theming
|
||||||
if ($category->parent) {
|
if ($category->parent) {
|
||||||
@@ -54,68 +54,25 @@ $all_cats = get_categories([
|
|||||||
'exclude' => $excluded_ids,
|
'exclude' => $excluded_ids,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$filter_parents = [];
|
// Liens de filtre : navigation vers la page de catégorie, en conservant axe/dates
|
||||||
foreach ($all_cats as $cat) {
|
$make_cat_link = function ($cat) use ($filter_query) {
|
||||||
if ($cat->parent == 0) {
|
$link = get_category_link($cat->term_id);
|
||||||
$link = get_category_link($cat->term_id);
|
return $filter_query ? $link . '?' . $filter_query : $link;
|
||||||
if ($filter_query) $link .= '?' . $filter_query;
|
};
|
||||||
$filter_parents[] = [
|
$context['filter_parents'] = thalim_archive_filter_parents($all_cats, $make_cat_link);
|
||||||
'id' => $cat->term_id,
|
|
||||||
'name' => thalim_cat_name($cat),
|
|
||||||
'slug' => $cat->slug,
|
|
||||||
'link' => $link,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$context['filter_parents'] = $filter_parents;
|
|
||||||
|
|
||||||
// Children of active rubrique for catégorie filter (with links)
|
// Children of active rubrique for catégorie filter (with links)
|
||||||
$active_rubrique_id = $context['active_rubrique'];
|
$active_rubrique_id = $context['active_rubrique'];
|
||||||
$is_direct = (bool) get_query_var('thalim_direct_posts');
|
$is_direct = (bool) get_query_var('thalim_direct_posts');
|
||||||
$lang = thalim_current_language();
|
|
||||||
|
|
||||||
$filter_categories = [];
|
$filter_categories = thalim_archive_filter_children($all_cats, $active_rubrique_id, $make_cat_link);
|
||||||
foreach ($all_cats as $cat) {
|
|
||||||
if ($cat->parent == $active_rubrique_id) {
|
|
||||||
$link = get_category_link($cat->term_id);
|
|
||||||
if ($filter_query) $link .= '?' . $filter_query;
|
|
||||||
$filter_categories[] = [
|
|
||||||
'id' => $cat->term_id,
|
|
||||||
'name' => thalim_cat_name($cat),
|
|
||||||
'slug' => $cat->slug,
|
|
||||||
'link' => $link,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add "Autres" entry if the active rubrique has posts directly assigned to it
|
// Add "Autres" entry if the active rubrique has posts directly assigned to it
|
||||||
if ($is_direct) {
|
$has_direct_posts = $is_direct ?: thalim_rubrique_has_direct_posts($active_rubrique_id);
|
||||||
$has_direct_posts = true;
|
|
||||||
} else {
|
|
||||||
$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,
|
|
||||||
]],
|
|
||||||
]);
|
|
||||||
$has_direct_posts = $direct_check->have_posts();
|
|
||||||
}
|
|
||||||
if ($has_direct_posts && !empty($filter_categories)) {
|
if ($has_direct_posts && !empty($filter_categories)) {
|
||||||
$autres_link = trailingslashit(get_category_link($active_rubrique_id)) . 'autres/';
|
$autres_link = trailingslashit(get_category_link($active_rubrique_id)) . 'autres/';
|
||||||
if ($filter_query) $autres_link .= '?' . $filter_query;
|
if ($filter_query) $autres_link .= '?' . $filter_query;
|
||||||
$filter_categories[] = [
|
$filter_categories[] = thalim_archive_autres_entry($autres_link);
|
||||||
'id' => 'autres',
|
|
||||||
'name' => $lang === 'en' ? 'Other' : 'Autres',
|
|
||||||
'slug' => 'autres',
|
|
||||||
'link' => $autres_link,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$context['filter_categories'] = $filter_categories;
|
$context['filter_categories'] = $filter_categories;
|
||||||
@@ -135,23 +92,23 @@ $children = get_categories([
|
|||||||
'exclude' => $excluded_ids,
|
'exclude' => $excluded_ids,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Ordre personnalisé des sous-catégories (term_id => position).
|
// Ordre personnalisé des sous-catégories (slug => position).
|
||||||
// Les termes absents du tableau sont placés en dernier (position 999).
|
// Les termes absents du tableau sont placés en dernier (position 999).
|
||||||
$subcategory_order = [
|
$subcategory_order = [
|
||||||
// Publications et productions (parent: 4)
|
// Publications et productions
|
||||||
15 => 0, // Ouvrages
|
'ouvrages' => 0,
|
||||||
16 => 1, // Articles
|
'articles' => 1,
|
||||||
65 => 2, // Revues et collections
|
'revues-et-collections' => 2,
|
||||||
17 => 3, // Multimédia
|
'multimedia' => 3,
|
||||||
// Activités (parent: 3)
|
// Manifestations scientifiques
|
||||||
11 => 0, // Séminaires
|
'seminaires' => 0,
|
||||||
10 => 1, // Colloques et journées d'études
|
'colloques-et-journees-detudes' => 1,
|
||||||
13 => 2, // Communications
|
'communications' => 2,
|
||||||
14 => 3, // Soutenances
|
'soutenances' => 3,
|
||||||
];
|
];
|
||||||
usort($children, function($a, $b) use ($subcategory_order) {
|
usort($children, function($a, $b) use ($subcategory_order) {
|
||||||
$pos_a = $subcategory_order[$a->term_id] ?? 999;
|
$pos_a = $subcategory_order[$a->slug] ?? 999;
|
||||||
$pos_b = $subcategory_order[$b->term_id] ?? 999;
|
$pos_b = $subcategory_order[$b->slug] ?? 999;
|
||||||
return $pos_a - $pos_b;
|
return $pos_a - $pos_b;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -188,7 +145,6 @@ if (!$is_direct && !empty($children)) {
|
|||||||
'posts_per_page' => 3,
|
'posts_per_page' => 3,
|
||||||
'orderby' => 'date',
|
'orderby' => 'date',
|
||||||
'order' => 'DESC',
|
'order' => 'DESC',
|
||||||
'lang' => '',
|
|
||||||
'thalim_event_date_order' => true,
|
'thalim_event_date_order' => true,
|
||||||
], $extra_query_args);
|
], $extra_query_args);
|
||||||
$posts = $sort_with_pinned( Timber::get_posts($query_args) );
|
$posts = $sort_with_pinned( Timber::get_posts($query_args) );
|
||||||
@@ -212,7 +168,6 @@ if (!$is_direct && !empty($children)) {
|
|||||||
'posts_per_page' => 3,
|
'posts_per_page' => 3,
|
||||||
'orderby' => 'date',
|
'orderby' => 'date',
|
||||||
'order' => 'DESC',
|
'order' => 'DESC',
|
||||||
'lang' => '',
|
|
||||||
'thalim_event_date_order' => true,
|
'thalim_event_date_order' => true,
|
||||||
], $extra_query_args);
|
], $extra_query_args);
|
||||||
$direct_posts = $sort_with_pinned( Timber::get_posts($direct_query_args) );
|
$direct_posts = $sort_with_pinned( Timber::get_posts($direct_query_args) );
|
||||||
@@ -244,7 +199,6 @@ if (!$is_direct && !empty($children)) {
|
|||||||
'posts_per_page' => 12,
|
'posts_per_page' => 12,
|
||||||
'orderby' => 'date',
|
'orderby' => 'date',
|
||||||
'order' => 'DESC',
|
'order' => 'DESC',
|
||||||
'lang' => '',
|
|
||||||
'thalim_event_date_order' => true,
|
'thalim_event_date_order' => true,
|
||||||
], $extra_query_args);
|
], $extra_query_args);
|
||||||
if ( $pinned_ids ) {
|
if ( $pinned_ids ) {
|
||||||
@@ -259,7 +213,6 @@ if (!$is_direct && !empty($children)) {
|
|||||||
'post__in' => $pinned_ids,
|
'post__in' => $pinned_ids,
|
||||||
'orderby' => 'post__in',
|
'orderby' => 'post__in',
|
||||||
'posts_per_page' => -1,
|
'posts_per_page' => -1,
|
||||||
'lang' => '',
|
|
||||||
], $extra_query_args ) ) : [];
|
], $extra_query_args ) ) : [];
|
||||||
|
|
||||||
$context['cards'] = thalim_get_cards_data($pinned_posts) + thalim_get_cards_data($posts);
|
$context['cards'] = thalim_get_cards_data($pinned_posts) + thalim_get_cards_data($posts);
|
||||||
@@ -288,6 +241,6 @@ $context['agenda_toggle_url'] = add_query_arg( $toggle_params, $toggle_base );
|
|||||||
$cat_lang = thalim_current_language();
|
$cat_lang = thalim_current_language();
|
||||||
$pres_fr = get_term_meta( $category->term_id, 'presentation', true ) ?: '';
|
$pres_fr = get_term_meta( $category->term_id, 'presentation', true ) ?: '';
|
||||||
$pres_en = get_term_meta( $category->term_id, 'presentation_en', true ) ?: '';
|
$pres_en = get_term_meta( $category->term_id, 'presentation_en', true ) ?: '';
|
||||||
$context['term_presentation'] = wpautop( ( $cat_lang === 'en' && $pres_en ) ? $pres_en : $pres_fr );
|
$context['term_presentation'] = wpautop( wp_kses_post( ( $cat_lang === 'en' && $pres_en ) ? $pres_en : $pres_fr ) );
|
||||||
|
|
||||||
Timber::render('category.twig', $context);
|
Timber::render('category.twig', $context);
|
||||||
|
|||||||
1430
functions.php
1430
functions.php
File diff suppressed because it is too large
Load Diff
233
inc/access-control.php
Normal file
233
inc/access-control.php
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Restrictions d'accès aux contenus et capacités des contributeurs :
|
||||||
|
* - liste admin des contributeurs limitée à leurs posts + posts où ils
|
||||||
|
* figurent en membres/autre_membres (et compteurs cohérents) ;
|
||||||
|
* - droit d'édition par post pour les membres listés (user_has_cap) ;
|
||||||
|
* - catégorie « Vie du labo » réservée aux connectés ;
|
||||||
|
* - redirections login/dashboard des non-admins.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Restrict Contributors to see only their own posts in admin,
|
||||||
|
// but also include posts where they appear in membres/autre_membres.
|
||||||
|
function restrict_contributor_posts( $query ) {
|
||||||
|
if ( ! is_admin() || ! $query->is_main_query() || current_user_can( 'edit_others_posts' ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global $user_ID, $wpdb;
|
||||||
|
|
||||||
|
// Posts where the user is listed as membre or autre_membre.
|
||||||
|
$membre_ids = array_map( 'intval', (array) $wpdb->get_col(
|
||||||
|
$wpdb->prepare(
|
||||||
|
"SELECT DISTINCT post_id FROM {$wpdb->postmeta}
|
||||||
|
WHERE meta_key IN ('membres', 'autre_membres') AND meta_value = %s",
|
||||||
|
$user_ID
|
||||||
|
)
|
||||||
|
) );
|
||||||
|
|
||||||
|
if ( empty( $membre_ids ) ) {
|
||||||
|
// Fast path: no membre posts, use simple author filter.
|
||||||
|
$query->set( 'author', $user_ID );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Posts authored by this user.
|
||||||
|
$authored_ids = array_map( 'intval', (array) $wpdb->get_col(
|
||||||
|
$wpdb->prepare(
|
||||||
|
"SELECT ID FROM {$wpdb->posts} WHERE post_author = %d",
|
||||||
|
$user_ID
|
||||||
|
)
|
||||||
|
) );
|
||||||
|
|
||||||
|
$all_ids = array_unique( array_merge( $authored_ids, $membre_ids ) );
|
||||||
|
|
||||||
|
// post__in with [0] returns nothing when the combined set is empty.
|
||||||
|
$query->set( 'post__in', empty( $all_ids ) ? [ 0 ] : $all_ids );
|
||||||
|
}
|
||||||
|
add_action( 'pre_get_posts', 'restrict_contributor_posts' );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Let contributors listed as membres/autre_membres on a post edit it.
|
||||||
|
*
|
||||||
|
* user_has_cap modifies capabilities only for this single check —
|
||||||
|
* it does not permanently alter the user's capability set.
|
||||||
|
*/
|
||||||
|
function thalim_membres_can_edit_post( $allcaps, $caps, $args, $user ) {
|
||||||
|
// Editors and above already have edit_others_posts — nothing to do.
|
||||||
|
if ( ! empty( $allcaps['edit_others_posts'] ) ) {
|
||||||
|
return $allcaps;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( empty( $args[0] ) ) {
|
||||||
|
return $allcaps;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cap = $args[0];
|
||||||
|
|
||||||
|
// Meta caps that carry a post ID in $args[2] (e.g. wp-admin/post.php load).
|
||||||
|
$meta_caps_with_id = [ 'edit_post', 'edit_page' ];
|
||||||
|
|
||||||
|
// Primitive caps called during the admin save flow *without* a
|
||||||
|
// post_id (e.g. wp-admin/includes/post.php:76 checks edit_others_posts
|
||||||
|
// directly when $post_author !== current user). We infer the post_id from
|
||||||
|
// the request so we can still authorize membres per-post.
|
||||||
|
//
|
||||||
|
// NOTE: publish_posts / publish_pages are intentionally NOT in this list —
|
||||||
|
// contributors listed in `membres` must be able to edit (incl. published)
|
||||||
|
// posts of the lab, but only editors/admins should be able to publish.
|
||||||
|
$primitive_caps_in_save_flow = [
|
||||||
|
'edit_others_posts',
|
||||||
|
'edit_others_pages',
|
||||||
|
'edit_published_posts',
|
||||||
|
'edit_published_pages',
|
||||||
|
];
|
||||||
|
|
||||||
|
$post_id = 0;
|
||||||
|
if ( in_array( $cap, $meta_caps_with_id, true ) && ! empty( $args[2] ) ) {
|
||||||
|
$post_id = (int) $args[2];
|
||||||
|
} elseif ( in_array( $cap, $primitive_caps_in_save_flow, true ) ) {
|
||||||
|
$post_id = (int) ( $_POST['post_ID'] ?? $_REQUEST['post'] ?? 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $post_id ) {
|
||||||
|
return $allcaps;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user_id = $user->ID;
|
||||||
|
|
||||||
|
$membre_ids = array_map(
|
||||||
|
'intval',
|
||||||
|
array_merge(
|
||||||
|
(array) get_post_meta( $post_id, 'membres', false ),
|
||||||
|
(array) get_post_meta( $post_id, 'autre_membres', false )
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( in_array( $user_id, $membre_ids, true ) ) {
|
||||||
|
// Grant every primitive cap mapped for this check
|
||||||
|
// (e.g. edit_others_posts, edit_published_posts).
|
||||||
|
foreach ( $caps as $c ) {
|
||||||
|
$allcaps[ $c ] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $allcaps;
|
||||||
|
}
|
||||||
|
add_filter( 'user_has_cap', 'thalim_membres_can_edit_post', 10, 4 );
|
||||||
|
|
||||||
|
// Prevent WP_Posts_List_Table from auto-redirecting contributors to the "Mine"
|
||||||
|
// view. The constructor sets $_GET['author'] = current_user_id() when the user
|
||||||
|
// lacks edit_others_posts and no other filter is active. Setting all_posts=1
|
||||||
|
// before the list table is constructed short-circuits that condition.
|
||||||
|
add_action( 'load-edit.php', function () {
|
||||||
|
if ( current_user_can( 'edit_others_posts' ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
empty( $_REQUEST['post_status'] )
|
||||||
|
&& empty( $_REQUEST['all_posts'] )
|
||||||
|
&& empty( $_REQUEST['author'] )
|
||||||
|
&& empty( $_REQUEST['show_sticky'] )
|
||||||
|
) {
|
||||||
|
$_GET['all_posts'] = 1;
|
||||||
|
$_REQUEST['all_posts'] = 1;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Adjust post-status counts so contributors see only posts they can access
|
||||||
|
// (posts they authored + posts listed in membres/autre_membres), not all posts.
|
||||||
|
// The wp_count_posts filter runs even on cached values, so it won't pollute
|
||||||
|
// the shared cache.
|
||||||
|
add_filter( 'wp_count_posts', function ( $counts, $type, $perm ) {
|
||||||
|
if ( ! is_admin() || current_user_can( 'edit_others_posts' ) ) {
|
||||||
|
return $counts;
|
||||||
|
}
|
||||||
|
|
||||||
|
global $wpdb;
|
||||||
|
$user_id = get_current_user_id();
|
||||||
|
|
||||||
|
$results = $wpdb->get_results(
|
||||||
|
$wpdb->prepare(
|
||||||
|
"SELECT post_status, COUNT(*) AS num_posts
|
||||||
|
FROM {$wpdb->posts}
|
||||||
|
WHERE post_type = %s
|
||||||
|
AND ID IN (
|
||||||
|
SELECT ID FROM {$wpdb->posts}
|
||||||
|
WHERE post_author = %d AND post_type = %s
|
||||||
|
UNION
|
||||||
|
SELECT post_id FROM {$wpdb->postmeta}
|
||||||
|
WHERE meta_key IN ('membres', 'autre_membres') AND meta_value = %s
|
||||||
|
)
|
||||||
|
GROUP BY post_status",
|
||||||
|
$type,
|
||||||
|
$user_id,
|
||||||
|
$type,
|
||||||
|
(string) $user_id
|
||||||
|
),
|
||||||
|
ARRAY_A
|
||||||
|
);
|
||||||
|
|
||||||
|
$new_counts = array_fill_keys( get_post_stati(), 0 );
|
||||||
|
foreach ( $results as $row ) {
|
||||||
|
$new_counts[ $row['post_status'] ] = (int) $row['num_posts'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (object) $new_counts;
|
||||||
|
}, 10, 3 );
|
||||||
|
|
||||||
|
// ── "Vie du labo" — restricted to logged-in users ────
|
||||||
|
add_action( 'pre_get_posts', function( $query ) {
|
||||||
|
if ( is_user_logged_in() ) return;
|
||||||
|
$vie_du_labo = thalim_cat_id( 'vie-du-labo' );
|
||||||
|
if ( ! $vie_du_labo ) return;
|
||||||
|
$excluded = $query->get( 'category__not_in' );
|
||||||
|
if ( ! is_array( $excluded ) ) $excluded = $excluded ? [ $excluded ] : [];
|
||||||
|
if ( ! in_array( $vie_du_labo, $excluded ) ) {
|
||||||
|
$excluded[] = $vie_du_labo;
|
||||||
|
$query->set( 'category__not_in', $excluded );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
add_action( 'template_redirect', function() {
|
||||||
|
if ( ! is_user_logged_in() && is_category( thalim_cat_id( 'vie-du-labo' ) ) ) {
|
||||||
|
wp_safe_redirect( home_url( '/' ) );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
add_filter( 'wp_nav_menu_objects', function( $items, $args ) {
|
||||||
|
if ( is_user_logged_in() ) return $items;
|
||||||
|
$vie_du_labo = thalim_cat_id( 'vie-du-labo' );
|
||||||
|
return array_values( array_filter( $items, function( $item ) use ( $vie_du_labo ) {
|
||||||
|
if ( $item->object === 'category' && (int) $item->object_id === $vie_du_labo ) return false;
|
||||||
|
if ( strpos( $item->url, 'vie-du-labo' ) !== false ) return false;
|
||||||
|
return true;
|
||||||
|
} ) );
|
||||||
|
}, 10, 2 );
|
||||||
|
|
||||||
|
// Non-admins: hide dashboard and tools menu, redirect to posts list
|
||||||
|
add_action( 'admin_menu', function() {
|
||||||
|
if ( ! current_user_can( 'manage_options' ) ) {
|
||||||
|
remove_menu_page( 'tools.php' );
|
||||||
|
remove_menu_page( 'index.php' );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Redirect non-admins away from dashboard to their posts list
|
||||||
|
add_action( 'admin_init', function() {
|
||||||
|
if ( current_user_can( 'manage_options' ) ) return;
|
||||||
|
global $pagenow;
|
||||||
|
if ( $pagenow === 'index.php' ) {
|
||||||
|
wp_safe_redirect( admin_url( 'edit.php' ) );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
// After login, send non-admins to posts list instead of dashboard
|
||||||
|
add_filter( 'login_redirect', function( $redirect_to, $requested, $user ) {
|
||||||
|
if ( ! is_wp_error( $user ) && ! $user->has_cap( 'manage_options' ) ) {
|
||||||
|
return admin_url( 'edit.php' );
|
||||||
|
}
|
||||||
|
return $redirect_to;
|
||||||
|
}, 10, 3 );
|
||||||
165
inc/admin-tweaks.php
Normal file
165
inc/admin-tweaks.php
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Customisations de l'admin WP : synchro display_name, champs Pods sur
|
||||||
|
* user-new.php, colonnes et filtres des taxonomies, filtre catégorie exact,
|
||||||
|
* admin bar, rewrite rule /autres, mode visuel forcé.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Auto-sync display_name from first_name + last_name on every profile save
|
||||||
|
add_action( 'profile_update', 'thalim_sync_display_name' );
|
||||||
|
add_action( 'user_register', 'thalim_sync_display_name' );
|
||||||
|
add_action( 'pods_api_post_save_pod_item_user', function( $pieces ) {
|
||||||
|
$user_id = isset( $pieces['id'] ) ? intval( $pieces['id'] ) : 0;
|
||||||
|
if ( $user_id ) thalim_sync_display_name( $user_id );
|
||||||
|
} );
|
||||||
|
|
||||||
|
function thalim_sync_display_name( int $user_id ): void {
|
||||||
|
$first = get_user_meta( $user_id, 'first_name', true );
|
||||||
|
$last = get_user_meta( $user_id, 'last_name', true );
|
||||||
|
// sanitize_text_field: le nom est réaffiché un peu partout (cards, data-*),
|
||||||
|
// on neutralise ici tout balisage saisi dans prénom/nom.
|
||||||
|
$name = sanitize_text_field( trim( "$first $last" ) );
|
||||||
|
if ( ! $name ) return;
|
||||||
|
// Bypass the filter to avoid infinite loop
|
||||||
|
remove_action( 'profile_update', 'thalim_sync_display_name' );
|
||||||
|
wp_update_user( [ 'ID' => $user_id, 'display_name' => $name ] );
|
||||||
|
add_action( 'profile_update', 'thalim_sync_display_name' );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show Pods user meta fields on the Add New User page (user-new.php).
|
||||||
|
// Fires the same hook Pods listens to on profile.php / user-edit.php.
|
||||||
|
add_action('user_new_form', function($operation) {
|
||||||
|
if ($operation === 'add-existing-user') return;
|
||||||
|
$user = new WP_User(0);
|
||||||
|
do_action('show_user_profile', $user);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save Pods user meta fields when a new user is registered.
|
||||||
|
// Re-fires Pods' personal_options_update callback with the new user ID
|
||||||
|
// while $_POST still contains the submitted form data.
|
||||||
|
add_action('user_register', function($user_id) {
|
||||||
|
do_action('personal_options_update', $user_id);
|
||||||
|
}, 20);
|
||||||
|
|
||||||
|
// Remove the Pods autocomplete 30-item cap for the "étiquettes" (post_tag) pick field
|
||||||
|
// so all tags are available in the dropdown, not just the first 30 alphabetically.
|
||||||
|
add_filter( 'pods_form_ui_field_pick_autocomplete_limit', function( $limit, $name ) {
|
||||||
|
if ( 'etiquettes' === $name ) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return $limit;
|
||||||
|
}, 10, 2 );
|
||||||
|
|
||||||
|
// Admin "Programmes de recherche" taxonomy list:
|
||||||
|
// - Filter by "Type de programme" (select injected via JS into the search form).
|
||||||
|
// - Add a "Type de programme" column to the list table.
|
||||||
|
|
||||||
|
// Server-side filtering: read the GET param and add a meta_query on pre_get_terms.
|
||||||
|
add_action( 'pre_get_terms', function( $query ) {
|
||||||
|
if ( ! is_admin() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( ! in_array( 'programme_de_recherche', (array) $query->query_vars['taxonomy'], true ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$type = isset( $_GET['type_de_programme'] ) ? sanitize_text_field( $_GET['type_de_programme'] ) : '';
|
||||||
|
if ( '' === $type ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$query->query_vars['meta_query'] = [
|
||||||
|
[
|
||||||
|
'key' => 'type_de_programme',
|
||||||
|
'value' => $type,
|
||||||
|
'compare' => '=',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Column: add "Type de programme" to the list table, replacing "Description".
|
||||||
|
add_filter( 'manage_edit-programme_de_recherche_columns', function( $columns ) {
|
||||||
|
unset( $columns['description'] );
|
||||||
|
$columns['type_de_programme'] = __( 'Type de programme', 'thalim' );
|
||||||
|
return $columns;
|
||||||
|
} );
|
||||||
|
|
||||||
|
add_filter( 'manage_programme_de_recherche_custom_column', function( $output, $column_name, $term_id ) {
|
||||||
|
if ( 'type_de_programme' !== $column_name ) {
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
return esc_html( get_term_meta( (int) $term_id, 'type_de_programme', true ) ?: '—' );
|
||||||
|
}, 10, 3 );
|
||||||
|
|
||||||
|
// Admin tag list: replace the "Description" column with the custom boolean field
|
||||||
|
// "ne_pas_afficher_dans_le_nuage" (Ne pas afficher dans le nuage de mots-clé).
|
||||||
|
|
||||||
|
add_filter( 'manage_edit-post_tag_columns', function( $columns ) {
|
||||||
|
unset( $columns['description'] );
|
||||||
|
$columns['nuage_exclus'] = __( 'Exclure du nuage', 'thalim' );
|
||||||
|
return $columns;
|
||||||
|
} );
|
||||||
|
|
||||||
|
add_filter( 'manage_post_tag_custom_column', function( $output, $column_name, $term_id ) {
|
||||||
|
if ( 'nuage_exclus' !== $column_name ) {
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
$val = get_term_meta( (int) $term_id, 'ne_pas_afficher_dans_le_nuage', true );
|
||||||
|
return $val ? '✓' : '—';
|
||||||
|
}, 10, 3 );
|
||||||
|
|
||||||
|
// In admin post list, filter by exact category only (exclude subcategories)
|
||||||
|
function thalim_exact_category_filter( $query ) {
|
||||||
|
if ( is_admin() && $query->is_main_query() ) {
|
||||||
|
$cat = $query->get( 'cat' );
|
||||||
|
if ( $cat ) {
|
||||||
|
$query->set( 'cat', '' );
|
||||||
|
$query->set( 'tax_query', [
|
||||||
|
[
|
||||||
|
'taxonomy' => 'category',
|
||||||
|
'field' => 'term_id',
|
||||||
|
'terms' => [ (int) $cat ],
|
||||||
|
'include_children' => false,
|
||||||
|
],
|
||||||
|
] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_action( 'pre_get_posts', 'thalim_exact_category_filter' );
|
||||||
|
|
||||||
|
// Rewrite rule for /category/{slug}/autres → posts directly in parent category
|
||||||
|
add_action('init', function() {
|
||||||
|
add_rewrite_rule(
|
||||||
|
'category/([^/]+)/autres/?$',
|
||||||
|
'index.php?category_name=$matches[1]&thalim_direct_posts=1',
|
||||||
|
'top'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
add_filter('query_vars', function($vars) {
|
||||||
|
$vars[] = 'thalim_direct_posts';
|
||||||
|
return $vars;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Admin bar customizations (front + back)
|
||||||
|
add_action('admin_bar_menu', function($wp_admin_bar) {
|
||||||
|
$wp_admin_bar->remove_node('wp-logo');
|
||||||
|
$wp_admin_bar->remove_node('customize');
|
||||||
|
foreach ($wp_admin_bar->get_nodes() as $node) {
|
||||||
|
if (empty($node->title) || stripos($node->title, 'article') === false) continue;
|
||||||
|
$node->title = preg_replace_callback('/article/i', function($m) {
|
||||||
|
$w = $m[0];
|
||||||
|
if ($w === strtoupper($w)) return 'ANNONCE';
|
||||||
|
if ($w[0] === strtoupper($w[0])) return 'Annonce';
|
||||||
|
return 'annonce';
|
||||||
|
}, $node->title);
|
||||||
|
$wp_admin_bar->add_node((array) $node);
|
||||||
|
}
|
||||||
|
}, 999);
|
||||||
|
|
||||||
|
add_action('wp_before_admin_bar_render', function() {
|
||||||
|
global $wp_admin_bar;
|
||||||
|
$wp_admin_bar->remove_node('wpforms-menu');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Force visual (TinyMCE) editor for all users — our admin CSS hides the
|
||||||
|
// Visual/Code tabs, so if a user has "Disable the visual editor" checked
|
||||||
|
// in their profile they get stuck in code mode with no way to switch back.
|
||||||
|
add_filter( 'user_can_richedit', '__return_true' );
|
||||||
305
inc/ajax.php
Normal file
305
inc/ajax.php
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Handlers AJAX du scroll infini (grille + agenda) et données des cartes agenda.
|
||||||
|
*
|
||||||
|
* Les deux handlers partagent la lecture des filtres POST et la construction
|
||||||
|
* de la requête (thalim_ajax_read_filters / thalim_ajax_build_query_args) —
|
||||||
|
* seuls divergent l'include_children de la clause catégorie, l'exclusion des
|
||||||
|
* épinglés (grille) et le calcul du today_offset (agenda).
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lit et assainit les filtres communs envoyés en POST par infiniteScroll.js
|
||||||
|
* et agendaView.js.
|
||||||
|
*/
|
||||||
|
function thalim_ajax_read_filters(): array {
|
||||||
|
return [
|
||||||
|
'page' => intval( $_POST['page'] ?? 1 ),
|
||||||
|
'category' => isset( $_POST['category'] ) ? intval( $_POST['category'] ) : 0,
|
||||||
|
'axe' => isset( $_POST['axe'] ) ? intval( $_POST['axe'] ) : 0,
|
||||||
|
'date_from' => isset( $_POST['date_from'] ) ? sanitize_text_field( $_POST['date_from'] ) : '',
|
||||||
|
'date_to' => isset( $_POST['date_to'] ) ? sanitize_text_field( $_POST['date_to'] ) : '',
|
||||||
|
'taxonomy' => isset( $_POST['taxonomy'] ) ? sanitize_key( $_POST['taxonomy'] ) : '',
|
||||||
|
'term' => isset( $_POST['term'] ) ? intval( $_POST['term'] ) : 0,
|
||||||
|
'filter_cat' => isset( $_POST['filter_cat'] ) ? intval( $_POST['filter_cat'] ) : 0,
|
||||||
|
'filter_autres' => isset( $_POST['filter_autres'] ) ? intval( $_POST['filter_autres'] ) : 0,
|
||||||
|
'exclude_cats' => isset( $_POST['exclude_cats'] ) ? sanitize_text_field( $_POST['exclude_cats'] ) : '',
|
||||||
|
'search' => isset( $_POST['search'] ) ? sanitize_text_field( $_POST['search'] ) : '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construit les arguments WP_Query communs aux deux handlers.
|
||||||
|
*
|
||||||
|
* @param array $f Filtres lus par thalim_ajax_read_filters().
|
||||||
|
* @param bool $include_children include_children de la clause catégorie principale.
|
||||||
|
*/
|
||||||
|
function thalim_ajax_build_query_args( array $f, bool $include_children ): array {
|
||||||
|
$query_args = [
|
||||||
|
'post_type' => 'post',
|
||||||
|
'post_status' => 'publish', // admin-ajax => is_admin() true: sans ça WP ajoute future/draft/pending et décale la pagination vs le front
|
||||||
|
'posts_per_page' => 12,
|
||||||
|
'paged' => $f['page'],
|
||||||
|
'orderby' => 'date',
|
||||||
|
'order' => 'DESC',
|
||||||
|
'thalim_event_date_order' => true,
|
||||||
|
];
|
||||||
|
if ( $f['search'] ) {
|
||||||
|
$query_args['s'] = $f['search'];
|
||||||
|
$query_args['relevanssi'] = true;
|
||||||
|
$query_args['orderby'] = 'relevance';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build tax_query — may combine category page filter, taxonomy term, and cat filter
|
||||||
|
$tax_clauses = [];
|
||||||
|
if ( $f['category'] ) {
|
||||||
|
$tax_clauses[] = [
|
||||||
|
'taxonomy' => 'category',
|
||||||
|
'field' => 'term_id',
|
||||||
|
'terms' => [ $f['category'] ],
|
||||||
|
'include_children' => $include_children,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if ( $f['taxonomy'] && $f['term'] ) {
|
||||||
|
$tax_clauses[] = [
|
||||||
|
'taxonomy' => $f['taxonomy'],
|
||||||
|
'field' => 'term_id',
|
||||||
|
'terms' => [ $f['term'] ],
|
||||||
|
];
|
||||||
|
// Exclure les séances de séminaire sur les pages de taxonomie
|
||||||
|
$tax_clauses[] = [
|
||||||
|
'taxonomy' => 'category',
|
||||||
|
'field' => 'term_id',
|
||||||
|
'terms' => [ thalim_cat_id( 'seance' ) ],
|
||||||
|
'operator' => 'NOT IN',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if ( $f['filter_cat'] ) {
|
||||||
|
$tax_clauses[] = [
|
||||||
|
'taxonomy' => 'category',
|
||||||
|
'field' => 'term_id',
|
||||||
|
'terms' => [ $f['filter_cat'] ],
|
||||||
|
'include_children' => ! $f['filter_autres'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if ( $f['exclude_cats'] ) {
|
||||||
|
$ids = array_filter( array_map( 'intval', explode( ',', $f['exclude_cats'] ) ) );
|
||||||
|
if ( ! empty( $ids ) ) {
|
||||||
|
$tax_clauses[] = [
|
||||||
|
'taxonomy' => 'category',
|
||||||
|
'field' => 'term_id',
|
||||||
|
'terms' => $ids,
|
||||||
|
'operator' => 'NOT IN',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( ! empty( $tax_clauses ) ) {
|
||||||
|
$query_args['tax_query'] = count( $tax_clauses ) > 1
|
||||||
|
? array_merge( [ 'relation' => 'AND' ], $tax_clauses )
|
||||||
|
: $tax_clauses;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $f['axe'] ) {
|
||||||
|
$query_args['meta_query'] = [[
|
||||||
|
'key' => 'axes_thematiques',
|
||||||
|
'value' => $f['axe'],
|
||||||
|
'type' => 'NUMERIC',
|
||||||
|
]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $f['date_from'] || $f['date_to'] ) {
|
||||||
|
$query_args['thalim_event_date_filter'] = [ 'from' => $f['date_from'], 'to' => $f['date_to'] ];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query_args;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AJAX handler for infinite scroll on category pages
|
||||||
|
function thalim_load_more_posts() {
|
||||||
|
check_ajax_referer('load_more_posts', 'nonce');
|
||||||
|
|
||||||
|
$GLOBALS['thalim_lang_override'] = sanitize_key( $_POST['lang'] ?? 'fr' );
|
||||||
|
|
||||||
|
$f = thalim_ajax_read_filters();
|
||||||
|
$query_args = thalim_ajax_build_query_args( $f, false );
|
||||||
|
|
||||||
|
// Exclude pinned posts on category pages to avoid duplicates (they already appear at the top,
|
||||||
|
// pulled out of the main flow by category.php). Must mirror category.php exactly.
|
||||||
|
if ( $f['category'] ) {
|
||||||
|
$pinned_ids = thalim_get_active_pinned_ids( $f['category'] );
|
||||||
|
if ( ! empty( $pinned_ids ) ) {
|
||||||
|
$query_args['post__not_in'] = $pinned_ids;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$posts = Timber::get_posts($query_args);
|
||||||
|
|
||||||
|
if (empty($posts)) {
|
||||||
|
wp_send_json_success(['html' => '']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cards = thalim_get_cards_data($posts);
|
||||||
|
|
||||||
|
$html = '';
|
||||||
|
foreach ($posts as $post) {
|
||||||
|
$html .= Timber::compile('partials/post-card.twig', [
|
||||||
|
'post' => $post,
|
||||||
|
'card' => $cards[$post->ID],
|
||||||
|
'show_category' => true,
|
||||||
|
'type_only' => $f['category'] > 0, // category pages: type chip only; everywhere else (taxonomy, search, annonces): type or category name
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_send_json_success(['html' => $html]);
|
||||||
|
}
|
||||||
|
add_action('wp_ajax_load_more_posts', 'thalim_load_more_posts');
|
||||||
|
add_action('wp_ajax_nopriv_load_more_posts', 'thalim_load_more_posts');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build structured data for one agenda slider card.
|
||||||
|
*/
|
||||||
|
function thalim_get_agenda_card_data( $post_id, $lang = 'fr' ) {
|
||||||
|
$months_fr = ['jan.','fév.','mars','avr.','mai','juin','juil.','août','sept.','oct.','nov.','déc.'];
|
||||||
|
$months_en = ['Jan.','Feb.','Mar.','Apr.','May','Jun.','Jul.','Aug.','Sep.','Oct.','Nov.','Dec.'];
|
||||||
|
$months = $lang === 'en' ? $months_en : $months_fr;
|
||||||
|
|
||||||
|
$raw_debut = get_post_meta( $post_id, 'date_de_debut', true );
|
||||||
|
$raw_datetime = get_post_meta( $post_id, 'datetime', true );
|
||||||
|
if ( $raw_debut && ! str_starts_with( $raw_debut, '0000' ) ) {
|
||||||
|
$ts = strtotime( $raw_debut );
|
||||||
|
} elseif ( $raw_datetime && ! str_starts_with( $raw_datetime, '0000' ) ) {
|
||||||
|
$ts = strtotime( $raw_datetime );
|
||||||
|
} else {
|
||||||
|
$ts = get_post_timestamp( $post_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
$raw_fin = get_post_meta( $post_id, 'date_de_fin', true );
|
||||||
|
$ts_debut = ( $raw_debut && ! str_starts_with( $raw_debut, '0000' ) ) ? strtotime( $raw_debut ) : 0;
|
||||||
|
$ts_fin = ( $raw_fin && ! str_starts_with( $raw_fin, '0000' ) ) ? strtotime( $raw_fin ) : 0;
|
||||||
|
|
||||||
|
// Build date_label — same rules as single.twig sidebar
|
||||||
|
$fmt_debut = $ts_debut ? thalim_format_date( $raw_debut, $lang ) : '';
|
||||||
|
$fmt_fin = $ts_fin ? thalim_format_date( $raw_fin, $lang ) : '';
|
||||||
|
$fmt_dt = ( $raw_datetime && ! str_starts_with( $raw_datetime, '0000' ) )
|
||||||
|
? thalim_format_date( $raw_datetime, $lang ) : '';
|
||||||
|
$h_debut = substr( get_post_meta( $post_id, 'heure_de_debut', true ) ?: '', 0, 5 );
|
||||||
|
$h_fin = substr( get_post_meta( $post_id, 'heure_de_fin', true ) ?: '', 0, 5 );
|
||||||
|
|
||||||
|
if ( $fmt_debut || $fmt_fin ) {
|
||||||
|
if ( $ts_debut && $ts_fin && date( 'Y-m-d', $ts_debut ) === date( 'Y-m-d', $ts_fin ) ) {
|
||||||
|
// Same day
|
||||||
|
if ( $h_debut && $h_fin ) {
|
||||||
|
$date_label = ( $lang === 'en' ? 'On ' : 'Le ' ) . $fmt_debut
|
||||||
|
. ' ' . ( $lang === 'en' ? 'from ' : 'de ' ) . $h_debut
|
||||||
|
. ' ' . ( $lang === 'en' ? 'to ' : 'à ' ) . $h_fin;
|
||||||
|
} elseif ( $h_debut ) {
|
||||||
|
$date_label = $fmt_debut . ( $lang === 'en' ? ' at ' : ' à ' ) . $h_debut;
|
||||||
|
} else {
|
||||||
|
$date_label = $fmt_debut;
|
||||||
|
}
|
||||||
|
} elseif ( $fmt_debut && $fmt_fin ) {
|
||||||
|
$date_label = ( $lang === 'en' ? 'From ' : 'Du ' ) . $fmt_debut
|
||||||
|
. ' ' . ( $lang === 'en' ? 'to ' : 'au ' ) . $fmt_fin;
|
||||||
|
} elseif ( $fmt_debut ) {
|
||||||
|
$date_label = $h_debut
|
||||||
|
? $fmt_debut . ( $lang === 'en' ? ' at ' : ' à ' ) . $h_debut
|
||||||
|
: $fmt_debut;
|
||||||
|
} else {
|
||||||
|
$date_label = ( $lang === 'en' ? 'Until ' : "Jusqu'au " ) . $fmt_fin;
|
||||||
|
}
|
||||||
|
} elseif ( $fmt_dt ) {
|
||||||
|
$date_label = $h_debut
|
||||||
|
? $fmt_dt . ( $lang === 'en' ? ' at ' : ' à ' ) . $h_debut
|
||||||
|
: $fmt_dt;
|
||||||
|
} else {
|
||||||
|
$date_label = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$type_fields = [
|
||||||
|
'type_colloque_journee_d_etude', 'type_soutenance', 'type_evenement_culturel',
|
||||||
|
'type_media', 'type_captation', 'type_revue_collection', 'type_autre',
|
||||||
|
];
|
||||||
|
$type_label = '';
|
||||||
|
foreach ( $type_fields as $f ) {
|
||||||
|
$v = get_post_meta( $post_id, $f, true );
|
||||||
|
if ( $v ) { $type_label = thalim_bilingual( $v, $lang ); break; }
|
||||||
|
}
|
||||||
|
if ( ! $type_label ) {
|
||||||
|
foreach ( get_the_category( $post_id ) as $cat ) {
|
||||||
|
if ( $cat->parent ) { $type_label = thalim_cat_name( $cat, $lang ); break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$end_day = $end_month = $end_year = null;
|
||||||
|
if ( $ts_fin && date( 'Ymd', $ts_fin ) !== date( 'Ymd', $ts ) ) {
|
||||||
|
$end_day = (int) date( 'j', $ts_fin );
|
||||||
|
$end_month = $months[ (int) date( 'n', $ts_fin ) - 1 ];
|
||||||
|
$end_year = (int) date( 'Y', $ts_fin );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Séance de séminaire: link to parent séminaire at #seance-{ID}
|
||||||
|
$link = get_permalink( $post_id );
|
||||||
|
if ( in_array( thalim_cat_id( 'seance' ), wp_list_pluck( get_the_category( $post_id ), 'term_id' ) ) ) {
|
||||||
|
$link = thalim_get_seance_link( $post_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'day' => (int) date( 'j', $ts ),
|
||||||
|
'month' => $months[ (int) date( 'n', $ts ) - 1 ],
|
||||||
|
'year' => (int) date( 'Y', $ts ),
|
||||||
|
'end_day' => $end_day,
|
||||||
|
'end_month' => $end_month,
|
||||||
|
'end_year' => $end_year,
|
||||||
|
'type_label' => $type_label,
|
||||||
|
'date_label' => $date_label,
|
||||||
|
'lieu' => thalim_bilingual( get_post_meta( $post_id, 'lieu', true ) ?: '', $lang ),
|
||||||
|
'link' => $link,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function thalim_load_more_agenda() {
|
||||||
|
check_ajax_referer( 'load_more_posts', 'nonce' );
|
||||||
|
|
||||||
|
$lang = sanitize_key( $_POST['lang'] ?? 'fr' );
|
||||||
|
$GLOBALS['thalim_lang_override'] = $lang;
|
||||||
|
|
||||||
|
$f = thalim_ajax_read_filters();
|
||||||
|
$query_args = thalim_ajax_build_query_args( $f, ! empty( $_POST['include_children'] ) );
|
||||||
|
|
||||||
|
// On first page, count future events to find today's anchor position
|
||||||
|
$today_offset = 0;
|
||||||
|
if ( (int) $f['page'] === 1 ) {
|
||||||
|
$offset_args = $query_args;
|
||||||
|
$offset_args['posts_per_page'] = 1;
|
||||||
|
$offset_args['no_found_rows'] = false;
|
||||||
|
$offset_args['paged'] = 1;
|
||||||
|
$today_str = current_time( 'Y-m-d' );
|
||||||
|
$existing_filter = $offset_args['thalim_event_date_filter'] ?? [];
|
||||||
|
$offset_args['thalim_event_date_filter'] = array_merge(
|
||||||
|
$existing_filter,
|
||||||
|
[ 'from' => $today_str ]
|
||||||
|
);
|
||||||
|
$future_query = new \WP_Query( $offset_args );
|
||||||
|
$today_offset = (int) $future_query->found_posts;
|
||||||
|
}
|
||||||
|
|
||||||
|
$posts = Timber::get_posts( $query_args );
|
||||||
|
if ( empty( $posts ) ) {
|
||||||
|
$response = [ 'html' => '' ];
|
||||||
|
if ( (int) $f['page'] === 1 ) $response['today_offset'] = $today_offset;
|
||||||
|
wp_send_json_success( $response );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$html = '';
|
||||||
|
foreach ( $posts as $post ) {
|
||||||
|
$data = thalim_get_agenda_card_data( $post->ID, $lang );
|
||||||
|
$html .= Timber::compile( 'partials/agenda-card.twig', array_merge( $data, [ 'post' => $post ] ) );
|
||||||
|
}
|
||||||
|
$response = [ 'html' => $html ];
|
||||||
|
if ( (int) $f['page'] === 1 ) $response['today_offset'] = $today_offset;
|
||||||
|
wp_send_json_success( $response );
|
||||||
|
}
|
||||||
|
add_action( 'wp_ajax_load_more_agenda', 'thalim_load_more_agenda' );
|
||||||
|
add_action( 'wp_ajax_nopriv_load_more_agenda', 'thalim_load_more_agenda' );
|
||||||
124
inc/archive-filters.php
Normal file
124
inc/archive-filters.php
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Helpers partagés des pages d'archives (category.php, taxonomy.php,
|
||||||
|
* search.php, page-annonces.php) : lecture des filtres GET, rubrique active,
|
||||||
|
* détection des posts « directs » et construction des listes de filtres.
|
||||||
|
*
|
||||||
|
* La construction des liens reste propre à chaque contrôleur (catégorie :
|
||||||
|
* navigation vers la page de catégorie ; taxonomie/recherche : paramètre
|
||||||
|
* filter_cat sur l'URL courante) — elle est injectée via $make_link.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catégories exclues des archives : séances de séminaire, non classé,
|
||||||
|
* et « Vie du labo » pour les visiteurs non connectés.
|
||||||
|
*/
|
||||||
|
function thalim_archive_excluded_cat_ids( bool $exclude_seances = true ): array {
|
||||||
|
$ids = [];
|
||||||
|
if ( $exclude_seances ) $ids[] = thalim_cat_id( 'seance' );
|
||||||
|
$ids[] = thalim_cat_id( 'non-classe' );
|
||||||
|
if ( ! is_user_logged_in() ) $ids[] = thalim_cat_id( 'vie-du-labo' );
|
||||||
|
return array_values( array_filter( $ids ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lit et assainit les filtres communs passés en GET sur les archives.
|
||||||
|
*/
|
||||||
|
function thalim_archive_read_filters(): array {
|
||||||
|
return [
|
||||||
|
'axe' => isset( $_GET['axe'] ) ? intval( $_GET['axe'] ) : 0,
|
||||||
|
'date_from' => isset( $_GET['date_from'] ) ? sanitize_text_field( $_GET['date_from'] ) : '',
|
||||||
|
'date_to' => isset( $_GET['date_to'] ) ? sanitize_text_field( $_GET['date_to'] ) : '',
|
||||||
|
'cat_id' => isset( $_GET['filter_cat'] ) ? intval( $_GET['filter_cat'] ) : 0,
|
||||||
|
'filter_autres' => isset( $_GET['filter_autres'] ) ? 1 : 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rubrique active déduite de la catégorie filtrée (parent si sous-catégorie).
|
||||||
|
*/
|
||||||
|
function thalim_archive_active_rubrique( int $active_cat_id ): int {
|
||||||
|
if ( ! $active_cat_id ) return 0;
|
||||||
|
$cat = get_category( $active_cat_id );
|
||||||
|
return ( $cat && ! is_wp_error( $cat ) && $cat->parent ) ? (int) $cat->parent : $active_cat_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Y a-t-il des posts publiés directement dans cette rubrique (sans
|
||||||
|
* sous-catégorie) ? $extra_tax_clauses permet d'ajouter la contrainte du
|
||||||
|
* terme de taxonomie courant (pages taxonomy.php).
|
||||||
|
*/
|
||||||
|
function thalim_rubrique_has_direct_posts( int $rubrique_id, array $extra_tax_clauses = [] ): bool {
|
||||||
|
$clauses = array_merge( [
|
||||||
|
[
|
||||||
|
'taxonomy' => 'category',
|
||||||
|
'field' => 'term_id',
|
||||||
|
'terms' => [ $rubrique_id ],
|
||||||
|
'include_children' => false,
|
||||||
|
],
|
||||||
|
], $extra_tax_clauses );
|
||||||
|
$tax_query = count( $clauses ) > 1
|
||||||
|
? array_merge( [ 'relation' => 'AND' ], $clauses )
|
||||||
|
: $clauses;
|
||||||
|
$direct_check = new WP_Query( [
|
||||||
|
'post_type' => 'post',
|
||||||
|
'posts_per_page' => 1,
|
||||||
|
'fields' => 'ids',
|
||||||
|
'no_found_rows' => true,
|
||||||
|
'tax_query' => $tax_query,
|
||||||
|
] );
|
||||||
|
return $direct_check->have_posts();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rubriques (catégories racines) pour la barre de filtres.
|
||||||
|
*
|
||||||
|
* @param array $all_cats Résultat de get_categories (hide_empty false, exclusions faites).
|
||||||
|
* @param callable $make_link fn(WP_Term $cat): string — URL du lien de filtre.
|
||||||
|
*/
|
||||||
|
function thalim_archive_filter_parents( array $all_cats, callable $make_link ): array {
|
||||||
|
$out = [];
|
||||||
|
foreach ( $all_cats as $cat ) {
|
||||||
|
if ( $cat->parent == 0 ) {
|
||||||
|
$out[] = [
|
||||||
|
'id' => $cat->term_id,
|
||||||
|
'name' => thalim_cat_name( $cat ),
|
||||||
|
'slug' => $cat->slug,
|
||||||
|
'link' => $make_link( $cat ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sous-catégories de la rubrique active pour la barre de filtres.
|
||||||
|
*/
|
||||||
|
function thalim_archive_filter_children( array $all_cats, int $rubrique_id, callable $make_link ): array {
|
||||||
|
$out = [];
|
||||||
|
if ( ! $rubrique_id ) return $out;
|
||||||
|
foreach ( $all_cats as $cat ) {
|
||||||
|
if ( $cat->parent == $rubrique_id ) {
|
||||||
|
$out[] = [
|
||||||
|
'id' => $cat->term_id,
|
||||||
|
'name' => thalim_cat_name( $cat ),
|
||||||
|
'slug' => $cat->slug,
|
||||||
|
'link' => $make_link( $cat ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entrée « Autres » de la barre de filtres (posts directement dans la rubrique).
|
||||||
|
*/
|
||||||
|
function thalim_archive_autres_entry( string $link ): array {
|
||||||
|
$lang = thalim_current_language();
|
||||||
|
return [
|
||||||
|
'id' => 'autres',
|
||||||
|
'name' => $lang === 'en' ? 'Other' : 'Autres',
|
||||||
|
'slug' => 'autres',
|
||||||
|
'link' => $link,
|
||||||
|
];
|
||||||
|
}
|
||||||
284
inc/assets.php
Normal file
284
inc/assets.php
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Enqueue des assets front (theme_enqueue_assets) et admin (enqueue_admin_js).
|
||||||
|
* Cache-busting par filemtime — pas de bundler.
|
||||||
|
*
|
||||||
|
* Les dépendances tierces (Swiper 12.2.0, Iconoir 7.11.0) sont auto-hébergées
|
||||||
|
* dans assets/vendor/ (fiabilité + RGPD — plus aucun appel CDN). Pour les
|
||||||
|
* mettre à jour : retélécharger les fichiers en épinglant la version exacte.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function thalim_enqueue_swiper() {
|
||||||
|
wp_enqueue_style(
|
||||||
|
'swiper',
|
||||||
|
get_template_directory_uri() . '/assets/vendor/swiper/swiper-bundle.min.css',
|
||||||
|
[],
|
||||||
|
'12.2.0'
|
||||||
|
);
|
||||||
|
wp_enqueue_script(
|
||||||
|
'swiper',
|
||||||
|
get_template_directory_uri() . '/assets/vendor/swiper/swiper-bundle.min.js',
|
||||||
|
[],
|
||||||
|
'12.2.0',
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function theme_enqueue_assets() {
|
||||||
|
wp_enqueue_style(
|
||||||
|
'main-styles',
|
||||||
|
get_template_directory_uri() . '/css/style.css',
|
||||||
|
[],
|
||||||
|
filemtime(get_template_directory() . '/css/style.css')
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_enqueue_style(
|
||||||
|
'iconoir',
|
||||||
|
get_template_directory_uri() . '/assets/vendor/iconoir/iconoir.css',
|
||||||
|
[],
|
||||||
|
'7.11.0'
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_enqueue_script(
|
||||||
|
'overlay',
|
||||||
|
get_template_directory_uri() . '/js/overlay.js',
|
||||||
|
[],
|
||||||
|
filemtime(get_template_directory() . '/js/overlay.js'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
if (is_front_page() || is_404()) {
|
||||||
|
wp_enqueue_script(
|
||||||
|
'animatedLogo',
|
||||||
|
get_template_directory_uri() . '/js/animatedLogo.js',
|
||||||
|
[],
|
||||||
|
filemtime(get_template_directory() . '/js/animatedLogo.js'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
wp_add_inline_script( 'animatedLogo', 'var themeDirURI = ' . wp_json_encode( get_template_directory_uri() ) . ';', 'before' );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( is_category() ) {
|
||||||
|
thalim_enqueue_swiper();
|
||||||
|
wp_enqueue_script( 'agendaView', get_template_directory_uri() . '/js/agendaView.js', ['swiper'], filemtime( get_template_directory() . '/js/agendaView.js' ), true );
|
||||||
|
wp_localize_script( 'agendaView', 'agendaViewData', [
|
||||||
|
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
|
||||||
|
'nonce' => wp_create_nonce( 'load_more_posts' ),
|
||||||
|
'lang' => thalim_current_language(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_front_page()) {
|
||||||
|
wp_enqueue_script(
|
||||||
|
'coloredWordsHero',
|
||||||
|
get_template_directory_uri() . '/js/coloredWordsHero.js',
|
||||||
|
[],
|
||||||
|
filemtime(get_template_directory() . '/js/coloredWordsHero.js'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
thalim_enqueue_swiper();
|
||||||
|
|
||||||
|
wp_enqueue_script(
|
||||||
|
'annoncesSwiper',
|
||||||
|
get_template_directory_uri() . '/js/annoncesSwiper.js',
|
||||||
|
['swiper'],
|
||||||
|
filemtime(get_template_directory() . '/js/annoncesSwiper.js'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_enqueue_script(
|
||||||
|
'messageLabo',
|
||||||
|
get_template_directory_uri() . '/js/messageLabo.js',
|
||||||
|
[],
|
||||||
|
filemtime(get_template_directory() . '/js/messageLabo.js'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_enqueue_script(
|
||||||
|
'keywordCloud',
|
||||||
|
get_template_directory_uri() . '/js/keywordCloud.js',
|
||||||
|
[],
|
||||||
|
filemtime(get_template_directory() . '/js/keywordCloud.js'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_enqueue_script(
|
||||||
|
'quickLinks',
|
||||||
|
get_template_directory_uri() . '/js/quickLinks.js',
|
||||||
|
[],
|
||||||
|
filemtime(get_template_directory() . '/js/quickLinks.js'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
$kw_tags = get_terms([
|
||||||
|
'taxonomy' => 'post_tag',
|
||||||
|
'hide_empty' => true,
|
||||||
|
'orderby' => 'name',
|
||||||
|
'order' => 'ASC',
|
||||||
|
]);
|
||||||
|
if (!is_wp_error($kw_tags) && !empty($kw_tags)) {
|
||||||
|
$kw_lang = thalim_current_language();
|
||||||
|
$kw_tags = array_filter($kw_tags, function ($tag) {
|
||||||
|
return !get_term_meta($tag->term_id, 'ne_pas_afficher_dans_le_nuage', true);
|
||||||
|
});
|
||||||
|
wp_localize_script('keywordCloud', 'thalimTags', array_values(array_map(function ($tag) use ($kw_lang) {
|
||||||
|
return ['name' => html_entity_decode(thalim_bilingual($tag->name, $kw_lang), ENT_QUOTES | ENT_HTML5, 'UTF-8'), 'url' => get_term_link($tag)];
|
||||||
|
}, $kw_tags)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
wp_enqueue_script(
|
||||||
|
'navAxesToggle',
|
||||||
|
get_template_directory_uri() . '/js/navAxesToggle.js',
|
||||||
|
[],
|
||||||
|
filemtime(get_template_directory() . '/js/navAxesToggle.js'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_enqueue_script(
|
||||||
|
'stickyHeader',
|
||||||
|
get_template_directory_uri() . '/js/stickyHeader.js',
|
||||||
|
[],
|
||||||
|
filemtime(get_template_directory() . '/js/stickyHeader.js'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_enqueue_script(
|
||||||
|
'frenchTypography',
|
||||||
|
get_template_directory_uri() . '/js/frenchTypography.js',
|
||||||
|
[],
|
||||||
|
filemtime(get_template_directory() . '/js/frenchTypography.js'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
if (is_page('membres')) {
|
||||||
|
wp_enqueue_script(
|
||||||
|
'membresFilters',
|
||||||
|
get_template_directory_uri() . '/js/membresFilters.js',
|
||||||
|
[],
|
||||||
|
filemtime(get_template_directory() . '/js/membresFilters.js'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
wp_enqueue_script(
|
||||||
|
'membresPopover',
|
||||||
|
get_template_directory_uri() . '/js/membresPopover.js',
|
||||||
|
[],
|
||||||
|
filemtime(get_template_directory() . '/js/membresPopover.js'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_single() || is_author() || is_page('membres') || is_page('le-laboratoire') || is_page('programmes-de-recherche')) {
|
||||||
|
wp_enqueue_script(
|
||||||
|
'seanceToggle',
|
||||||
|
get_template_directory_uri() . '/js/seanceToggle.js',
|
||||||
|
[],
|
||||||
|
filemtime(get_template_directory() . '/js/seanceToggle.js'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_single()) {
|
||||||
|
thalim_enqueue_swiper();
|
||||||
|
|
||||||
|
wp_enqueue_script(
|
||||||
|
'imageSwiper',
|
||||||
|
get_template_directory_uri() . '/js/imageSwiper.js',
|
||||||
|
['swiper'],
|
||||||
|
filemtime(get_template_directory() . '/js/imageSwiper.js'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_enqueue_script(
|
||||||
|
'fitPostCardTitle',
|
||||||
|
get_template_directory_uri() . '/js/fitPostCardTitle.js',
|
||||||
|
['frenchTypography'],
|
||||||
|
filemtime(get_template_directory() . '/js/fitPostCardTitle.js'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
$is_archive_page = is_category() || is_tax() || is_tag() || is_page(['annonces', 'announcements']) || is_search();
|
||||||
|
|
||||||
|
if ($is_archive_page) {
|
||||||
|
wp_enqueue_script(
|
||||||
|
'infiniteScroll',
|
||||||
|
get_template_directory_uri() . '/js/infiniteScroll.js',
|
||||||
|
[],
|
||||||
|
filemtime(get_template_directory() . '/js/infiniteScroll.js'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
wp_localize_script('infiniteScroll', 'infiniteScrollData', [
|
||||||
|
'ajaxUrl' => admin_url('admin-ajax.php'),
|
||||||
|
'nonce' => wp_create_nonce('load_more_posts'),
|
||||||
|
'lang' => thalim_current_language(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
wp_enqueue_script(
|
||||||
|
'categoryFilters',
|
||||||
|
get_template_directory_uri() . '/js/categoryFilters.js',
|
||||||
|
[],
|
||||||
|
filemtime(get_template_directory() . '/js/categoryFilters.js'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_action('wp_enqueue_scripts', 'theme_enqueue_assets');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scripts admin découpés par contexte de page (js/admin/*), enqueue
|
||||||
|
* conditionnel par écran. admin-base.js fournit le namespace partagé
|
||||||
|
* window.ThalimAdmin dont dépendent les autres.
|
||||||
|
*/
|
||||||
|
function enqueue_admin_js() {
|
||||||
|
wp_enqueue_style(
|
||||||
|
'adminDashboardStyles',
|
||||||
|
get_template_directory_uri() . '/css/admin.css',
|
||||||
|
[],
|
||||||
|
filemtime(get_template_directory() . '/css/admin.css')
|
||||||
|
);
|
||||||
|
|
||||||
|
$base_uri = get_template_directory_uri() . '/js/admin';
|
||||||
|
$base_dir = get_template_directory() . '/js/admin';
|
||||||
|
$enqueue = function ( $handle, $file, $deps ) use ( $base_uri, $base_dir ) {
|
||||||
|
wp_enqueue_script( $handle, "$base_uri/$file", $deps, filemtime( "$base_dir/$file" ), true );
|
||||||
|
};
|
||||||
|
|
||||||
|
// Toutes pages admin : socle partagé + rename « Article » → « Annonce »
|
||||||
|
$enqueue( 'thalim-admin-base', 'admin-base.js', [ 'jquery' ] );
|
||||||
|
$enqueue( 'thalim-admin-rename', 'admin-rename.js', [ 'thalim-admin-base' ] );
|
||||||
|
|
||||||
|
$screen = get_current_screen();
|
||||||
|
if ( ! $screen ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// post.php / post-new.php (tous post types)
|
||||||
|
if ( 'post' === $screen->base ) {
|
||||||
|
$enqueue( 'thalim-admin-post-edit', 'admin-post-edit.js', [ 'jquery', 'thalim-admin-base' ] );
|
||||||
|
|
||||||
|
$axes_groups = thalim_get_axes_filter_groups();
|
||||||
|
if ( current_user_can( 'contributor' ) && ! current_user_can( 'edit_others_posts' ) ) {
|
||||||
|
$axes_groups = array_slice( $axes_groups, 0, 1 );
|
||||||
|
}
|
||||||
|
wp_localize_script( 'thalim-admin-post-edit', 'thalimAxesGroups', $axes_groups );
|
||||||
|
|
||||||
|
// Modale Pods de création de séance (iframe avec ?pods_modal)
|
||||||
|
if ( isset( $_GET['pods_modal'] ) ) {
|
||||||
|
$enqueue( 'thalim-admin-pods-modal', 'admin-pods-modal.js', [ 'jquery', 'thalim-admin-base' ] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// profile.php / user-edit.php / user-new.php
|
||||||
|
if ( in_array( $screen->base, [ 'profile', 'user-edit', 'user' ], true ) ) {
|
||||||
|
$enqueue( 'thalim-admin-profile', 'admin-profile.js', [ 'jquery', 'thalim-admin-base' ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
// edit-tags.php / term.php
|
||||||
|
if ( in_array( $screen->base, [ 'edit-tags', 'term' ], true ) ) {
|
||||||
|
$enqueue( 'thalim-admin-taxonomy-list', 'admin-taxonomy-list.js', [ 'jquery', 'thalim-admin-base' ] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_action( 'admin_enqueue_scripts', 'enqueue_admin_js' );
|
||||||
@@ -116,20 +116,22 @@ function thalim_get_author_data($user_id) {
|
|||||||
? thalim_bilingual(get_user_meta($user_id, 'affiliation_autre', true) ?: '', $lang)
|
? thalim_bilingual(get_user_meta($user_id, 'affiliation_autre', true) ?: '', $lang)
|
||||||
: $v;
|
: $v;
|
||||||
})(),
|
})(),
|
||||||
'bio' => wpautop( make_clickable( get_user_meta($user_id, 'biographie', true) ?: '' ) ),
|
// wp_kses_post: ces champs sont éditables par les contributeurs (profil)
|
||||||
'bio_en' => wpautop( make_clickable( get_user_meta($user_id, 'biographie_en', true) ?: '' ) ),
|
// et rendus en |raw dans author.twig → XSS stocké sans filtrage.
|
||||||
|
'bio' => wpautop( make_clickable( wp_kses_post( get_user_meta($user_id, 'biographie', true) ?: '' ) ) ),
|
||||||
|
'bio_en' => wpautop( make_clickable( wp_kses_post( get_user_meta($user_id, 'biographie_en', true) ?: '' ) ) ),
|
||||||
'domaines_tags' => $domaines_tags,
|
'domaines_tags' => $domaines_tags,
|
||||||
'domaines' => wpautop( make_clickable( get_user_meta($user_id, 'autres_domaines_de_recherches', true) ?: '' ) ),
|
'domaines' => wpautop( make_clickable( wp_kses_post( get_user_meta($user_id, 'autres_domaines_de_recherches', true) ?: '' ) ) ),
|
||||||
'domaines_en' => wpautop( make_clickable( get_user_meta($user_id, 'autres_domaines_de_recherches_en', true) ?: '' ) ),
|
'domaines_en' => wpautop( make_clickable( wp_kses_post( get_user_meta($user_id, 'autres_domaines_de_recherches_en', true) ?: '' ) ) ),
|
||||||
'recherches' => wpautop( get_user_meta($user_id, 'recherches_en_cours', true) ?: '' ),
|
'recherches' => wpautop( wp_kses_post( get_user_meta($user_id, 'recherches_en_cours', true) ?: '' ) ),
|
||||||
'recherches_en' => wpautop( get_user_meta($user_id, 'recherches_en_cours_en', true) ?: '' ),
|
'recherches_en' => wpautop( wp_kses_post( get_user_meta($user_id, 'recherches_en_cours_en', true) ?: '' ) ),
|
||||||
'axes' => $axes,
|
'axes' => $axes,
|
||||||
'titre_these' => thalim_bilingual(get_user_meta($user_id, 'titre_de_these', true) ?: '', $lang),
|
'titre_these' => thalim_bilingual(get_user_meta($user_id, 'titre_de_these', true) ?: '', $lang),
|
||||||
'date_soutenance' => get_user_meta($user_id, 'date_de_soutenance', true) ?: '',
|
'date_soutenance' => get_user_meta($user_id, 'date_de_soutenance', true) ?: '',
|
||||||
'directeur_thalim'=> $directeur_thalim,
|
'directeur_thalim'=> $directeur_thalim,
|
||||||
'autre_directeur' => get_user_meta($user_id, 'autre_directeur_de_these', true) ?: '',
|
'autre_directeur' => get_user_meta($user_id, 'autre_directeur_de_these', true) ?: '',
|
||||||
'resume_these' => wpautop( get_user_meta($user_id, 'resume_de_la_these', true) ?: '' ),
|
'resume_these' => wpautop( wp_kses_post( get_user_meta($user_id, 'resume_de_la_these', true) ?: '' ) ),
|
||||||
'resume_these_en' => wpautop( get_user_meta($user_id, 'resume_de_la_these_en', true) ?: '' ),
|
'resume_these_en' => wpautop( wp_kses_post( get_user_meta($user_id, 'resume_de_la_these_en', true) ?: '' ) ),
|
||||||
'email' => $show_email ? $user->user_email : '',
|
'email' => $show_email ? $user->user_email : '',
|
||||||
'liens_externes' => $liens_externes,
|
'liens_externes' => $liens_externes,
|
||||||
'documents' => $documents,
|
'documents' => $documents,
|
||||||
@@ -148,7 +150,8 @@ function thalim_get_author_data($user_id) {
|
|||||||
* Returns an array sorted by post count (descending).
|
* Returns an array sorted by post count (descending).
|
||||||
*/
|
*/
|
||||||
function thalim_get_author_posts_by_category($user_id) {
|
function thalim_get_author_posts_by_category($user_id) {
|
||||||
$excluded_cats = [12, 31]; // séances de séminaire, etc.
|
$seance_cat = thalim_cat_id('seance');
|
||||||
|
$excluded_cats = array_filter([ $seance_cat, thalim_cat_id('non-classe') ]);
|
||||||
$lang = thalim_current_language();
|
$lang = thalim_current_language();
|
||||||
|
|
||||||
$posts = Timber::get_posts([
|
$posts = Timber::get_posts([
|
||||||
@@ -166,7 +169,6 @@ function thalim_get_author_posts_by_category($user_id) {
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
'thalim_event_date_order' => true,
|
'thalim_event_date_order' => true,
|
||||||
'lang' => '',
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$groups = [];
|
$groups = [];
|
||||||
@@ -205,29 +207,28 @@ function thalim_get_author_posts_by_category($user_id) {
|
|||||||
$groups[$cat_id]['posts'][] = $post;
|
$groups[$cat_id]['posts'][] = $post;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Séances de séminaire — dedicated group. Posts in cat 12 where the member
|
// Séances de séminaire — dedicated group. Posts in the séance category
|
||||||
// is listed in `membres`/`autre_membres`. Cards use the parent séminaire
|
// where the member is listed in `membres`/`autre_membres`. Cards use the
|
||||||
// permalink with a #seance-{ID} hash (see thalim_get_card_data).
|
// parent séminaire permalink with a #seance-{ID} hash (see thalim_get_card_data).
|
||||||
$seances = Timber::get_posts([
|
$seances = Timber::get_posts([
|
||||||
'post_type' => 'post',
|
'post_type' => 'post',
|
||||||
'posts_per_page' => -1,
|
'posts_per_page' => -1,
|
||||||
'category__in' => [12],
|
'category__in' => [ $seance_cat ],
|
||||||
'meta_query' => [
|
'meta_query' => [
|
||||||
'relation' => 'OR',
|
'relation' => 'OR',
|
||||||
[ 'key' => 'membres', 'value' => $user_id ],
|
[ 'key' => 'membres', 'value' => $user_id ],
|
||||||
[ 'key' => 'autre_membres', 'value' => $user_id ],
|
[ 'key' => 'autre_membres', 'value' => $user_id ],
|
||||||
],
|
],
|
||||||
'thalim_event_date_order' => true,
|
'thalim_event_date_order' => true,
|
||||||
'lang' => '',
|
|
||||||
]);
|
]);
|
||||||
if (count($seances) > 0) {
|
if (count($seances) > 0) {
|
||||||
$seance_cat = get_term(12, 'category');
|
$seance_term = get_term($seance_cat, 'category');
|
||||||
$groups[12] = [
|
$groups[$seance_cat] = [
|
||||||
'cat_id' => 12,
|
'cat_id' => $seance_cat,
|
||||||
'cat_name' => $seance_cat && !is_wp_error($seance_cat)
|
'cat_name' => $seance_term && !is_wp_error($seance_term)
|
||||||
? thalim_cat_name($seance_cat, $lang)
|
? thalim_cat_name($seance_term, $lang)
|
||||||
: ($lang === 'en' ? 'Seminar sessions' : 'Séances de séminaire'),
|
: ($lang === 'en' ? 'Seminar sessions' : 'Séances de séminaire'),
|
||||||
'cat_url' => get_category_link(12),
|
'cat_url' => get_category_link($seance_cat),
|
||||||
'posts' => $seances,
|
'posts' => $seances,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
93
inc/avatars.php
Normal file
93
inc/avatars.php
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Avatars utilisateurs — chaîne de fallback
|
||||||
|
* Simple Local Avatar → Gravatar (cache préchauffé par cron) → chaîne vide
|
||||||
|
* (initiales côté template).
|
||||||
|
*
|
||||||
|
* Le rendu ne fait JAMAIS de requête réseau : l'existence du Gravatar est
|
||||||
|
* vérifiée par un cron quotidien (thalim_warm_gravatar_cache) qui remplit
|
||||||
|
* les transients. En cas de cache manquant (nouvel utilisateur, transient
|
||||||
|
* expiré), on renvoie '' (initiales) et on planifie un réchauffage unitaire.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the avatar URL for a user:
|
||||||
|
* 1. Simple Local Avatar (media library upload) if set
|
||||||
|
* 2. Gravatar if the user has one (transient rempli par cron)
|
||||||
|
* 3. Empty string → templates fall back to initials/placeholder
|
||||||
|
*/
|
||||||
|
function thalim_get_user_avatar_url( int $user_id ): string {
|
||||||
|
// 1. Simple Local Avatar plugin
|
||||||
|
$meta = get_user_meta( $user_id, 'simple_local_avatar', true );
|
||||||
|
if ( is_array( $meta ) && ! empty( $meta['full'] ) ) {
|
||||||
|
// Use media_id for a dynamic URL that works across environments
|
||||||
|
if ( ! empty( $meta['media_id'] ) ) {
|
||||||
|
$url = wp_get_attachment_url( (int) $meta['media_id'] );
|
||||||
|
if ( $url ) return $url;
|
||||||
|
}
|
||||||
|
// Fallback: rewrite stored URL to current site domain
|
||||||
|
$pos = strpos( $meta['full'], '/wp-content/' );
|
||||||
|
if ( $pos !== false ) {
|
||||||
|
return rtrim( site_url(), '/' ) . substr( $meta['full'], $pos );
|
||||||
|
}
|
||||||
|
return $meta['full'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Gravatar — uniquement depuis le cache (pas de HEAD dans le rendu)
|
||||||
|
$cached = get_transient( 'thalim_gravatar_' . $user_id );
|
||||||
|
if ( $cached !== false ) return $cached;
|
||||||
|
|
||||||
|
// Cache manquant : fallback initiales tout de suite, réchauffage en différé
|
||||||
|
if ( ! wp_next_scheduled( 'thalim_warm_gravatar_user', [ $user_id ] ) ) {
|
||||||
|
wp_schedule_single_event( time() + 60, 'thalim_warm_gravatar_user', [ $user_id ] );
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie l'existence du Gravatar d'un utilisateur (HEAD avec d=404) et met
|
||||||
|
* le résultat (positif ou négatif) en transient. Appelé uniquement en cron.
|
||||||
|
*/
|
||||||
|
function thalim_refresh_gravatar_cache( int $user_id ): void {
|
||||||
|
$user = get_userdata( $user_id );
|
||||||
|
if ( ! $user ) return;
|
||||||
|
|
||||||
|
$hash = md5( strtolower( trim( $user->user_email ) ) );
|
||||||
|
$response = wp_remote_head(
|
||||||
|
'https://www.gravatar.com/avatar/' . $hash . '?d=404',
|
||||||
|
[ 'timeout' => 3 ]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Erreur réseau : ne pas mettre en cache un faux négatif, retenter plus tard
|
||||||
|
if ( is_wp_error( $response ) ) return;
|
||||||
|
|
||||||
|
$url = wp_remote_retrieve_response_code( $response ) === 200
|
||||||
|
? 'https://www.gravatar.com/avatar/' . $hash . '?s=300'
|
||||||
|
: '';
|
||||||
|
set_transient( 'thalim_gravatar_' . $user_id, $url, WEEK_IN_SECONDS );
|
||||||
|
}
|
||||||
|
add_action( 'thalim_warm_gravatar_user', 'thalim_refresh_gravatar_cache' );
|
||||||
|
|
||||||
|
// Cron quotidien : réchauffe le cache Gravatar des utilisateurs sans avatar
|
||||||
|
// local, avant expiration des transients (1 semaine) — le premier rendu de
|
||||||
|
// /membres ne paie plus jamais N requêtes HEAD de 3 s.
|
||||||
|
add_action( 'init', function () {
|
||||||
|
if ( ! wp_next_scheduled( 'thalim_warm_gravatar_cache' ) ) {
|
||||||
|
wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'thalim_warm_gravatar_cache' );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
add_action( 'thalim_warm_gravatar_cache', function () {
|
||||||
|
$users = get_users( [ 'fields' => 'ID' ] );
|
||||||
|
foreach ( $users as $user_id ) {
|
||||||
|
$user_id = (int) $user_id;
|
||||||
|
// Avatar local → pas besoin de Gravatar
|
||||||
|
$meta = get_user_meta( $user_id, 'simple_local_avatar', true );
|
||||||
|
if ( is_array( $meta ) && ! empty( $meta['full'] ) ) continue;
|
||||||
|
// Transient encore frais (posé il y a < 1 semaine) : on le rafraîchit
|
||||||
|
// quand même s'il expire dans moins de 2 jours, sinon on saute.
|
||||||
|
$timeout = (int) get_option( '_transient_timeout_thalim_gravatar_' . $user_id );
|
||||||
|
if ( $timeout && $timeout - time() > 2 * DAY_IN_SECONDS ) continue;
|
||||||
|
thalim_refresh_gravatar_cache( $user_id );
|
||||||
|
}
|
||||||
|
} );
|
||||||
90
inc/config.php
Normal file
90
inc/config.php
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Configuration centralisée des identifiants du site.
|
||||||
|
*
|
||||||
|
* Les term_ids auto-incrémentés peuvent changer lors d'une réimportation de
|
||||||
|
* base ; partout où c'est possible on résout par slug (stable) avec cache
|
||||||
|
* statique par requête. Les quelques maps indexées par term_id documentent
|
||||||
|
* explicitement leur dépendance à l'installation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Résout un term_id par slug (catégories, rôles, etc.), avec cache statique.
|
||||||
|
* Renvoie 0 si le terme n'existe pas — les appelants doivent tolérer 0
|
||||||
|
* (clause tax_query vide, comparaison toujours fausse…).
|
||||||
|
*/
|
||||||
|
function thalim_term_id_by_slug( string $slug, string $taxonomy = 'category' ): int {
|
||||||
|
static $cache = [];
|
||||||
|
$key = $taxonomy . ':' . $slug;
|
||||||
|
if ( ! isset( $cache[ $key ] ) ) {
|
||||||
|
$term = get_term_by( 'slug', $slug, $taxonomy );
|
||||||
|
$cache[ $key ] = ( $term && ! is_wp_error( $term ) ) ? (int) $term->term_id : 0;
|
||||||
|
}
|
||||||
|
return $cache[ $key ];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catégories « structurelles » du site, résolues par slug.
|
||||||
|
* Clés logiques → slug en base. Utiliser thalim_cat_id('seance') etc.
|
||||||
|
*/
|
||||||
|
function thalim_cat_id( string $key ): int {
|
||||||
|
static $slugs = [
|
||||||
|
// racines / rubriques
|
||||||
|
'laboratoire' => 'le-laboratoire',
|
||||||
|
'manifestations' => 'manifestations-scientifiques',
|
||||||
|
'publications' => 'publications-et-productions',
|
||||||
|
'mediation' => 'mediation-scientifique',
|
||||||
|
'ressources' => 'ressources',
|
||||||
|
// catégories techniques
|
||||||
|
'seance' => 'seance-de-seminaire',
|
||||||
|
'non-classe' => 'non-classe',
|
||||||
|
'vie-du-labo' => 'vie-du-labo-intranet',
|
||||||
|
'newsletter' => 'newsletter',
|
||||||
|
'message-labo' => 'message-du-laboratoire',
|
||||||
|
// sous-catégories utilisées dans la logique métier
|
||||||
|
'seminaires' => 'seminaires',
|
||||||
|
'colloques' => 'colloques-et-journees-detudes',
|
||||||
|
'communications' => 'communications',
|
||||||
|
'soutenances' => 'soutenances',
|
||||||
|
'ouvrages' => 'ouvrages',
|
||||||
|
'articles' => 'articles',
|
||||||
|
'revues' => 'revues-et-collections',
|
||||||
|
'multimedia' => 'multimedia',
|
||||||
|
'evenements-culturels' => 'evenements-culturels',
|
||||||
|
'medias' => 'medias',
|
||||||
|
'gazette' => 'gazette',
|
||||||
|
'podcast' => 'podcast-de-thalim',
|
||||||
|
'captations' => 'captations',
|
||||||
|
'appels' => 'appels-a-contribution',
|
||||||
|
];
|
||||||
|
if ( ! isset( $slugs[ $key ] ) ) return 0;
|
||||||
|
return thalim_term_id_by_slug( $slugs[ $key ], 'category' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rôles (taxonomy `role`) techniques, exclus de la recherche de membres.
|
||||||
|
*/
|
||||||
|
function thalim_excluded_role_ids(): array {
|
||||||
|
return array_values( array_filter( [
|
||||||
|
thalim_term_id_by_slug( 'a-ranger', 'role' ),
|
||||||
|
thalim_term_id_by_slug( 'archive', 'role' ),
|
||||||
|
] ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clé couleur stable d'une catégorie racine, indexée sur le term_id (immuable)
|
||||||
|
* plutôt que sur le slug (que l'admin peut régénérer en renommant la catégorie).
|
||||||
|
* Renvoie la clé canonique attendue par les classes CSS .gradient--{clé} /
|
||||||
|
* .category--{clé} (_postcard.scss, _single.scss, _category.scss).
|
||||||
|
* Fallback sur le slug live pour toute racine hors des 5 rubriques connues.
|
||||||
|
*/
|
||||||
|
function thalim_category_color_slug( $root_term_id, $fallback_slug = '' ) {
|
||||||
|
$map = [
|
||||||
|
1 => 'le-laboratoire',
|
||||||
|
3 => 'manifestations-scientifiques',
|
||||||
|
4 => 'publications-et-productions',
|
||||||
|
5 => 'mediation-scientifique',
|
||||||
|
6 => 'ressources',
|
||||||
|
];
|
||||||
|
return $map[ (int) $root_term_id ] ?? $fallback_slug;
|
||||||
|
}
|
||||||
71
inc/context.php
Normal file
71
inc/context.php
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Contexte Twig global (menus, contenu général, axes courants, URL annonces,
|
||||||
|
* sélecteur de langue). Branché sur le filtre timber/context.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function add_to_context($context) {
|
||||||
|
$current_lang = thalim_current_language();
|
||||||
|
|
||||||
|
// menus
|
||||||
|
$menu_slug = ($current_lang === 'en') ? 'Navigation-en' : 'Navigation';
|
||||||
|
$context['menu'] = Timber::get_menu($menu_slug);
|
||||||
|
|
||||||
|
$footer_menu_slug = ($current_lang === 'en') ? 'Footer-en' : 'Footer';
|
||||||
|
$context['footer_menu'] = Timber::get_menu($footer_menu_slug);
|
||||||
|
|
||||||
|
// contenus généraux (single post, bilingual)
|
||||||
|
$gc_posts = Timber::get_posts([
|
||||||
|
'post_type' => 'contenu_general',
|
||||||
|
'posts_per_page' => 1,
|
||||||
|
'orderby' => 'ID',
|
||||||
|
'order' => 'ASC',
|
||||||
|
]);
|
||||||
|
$gc_post = $gc_posts[0] ?? null;
|
||||||
|
if ( $gc_post ) {
|
||||||
|
$context['gc'] = [
|
||||||
|
'umr' => thalim_bilingual( $gc_post->umr ?: '', $current_lang ),
|
||||||
|
'thalim' => thalim_bilingual( $gc_post->thalim ?: '', $current_lang ),
|
||||||
|
'siecles' => thalim_bilingual( $gc_post->siecles ?: '', $current_lang ),
|
||||||
|
'presentation' => ( $current_lang === 'en' && $gc_post->presentation_en ) ? $gc_post->presentation_en : $gc_post->presentation,
|
||||||
|
'presentation_detail' => ( $current_lang === 'en' && $gc_post->presentation_detail_en ) ? $gc_post->presentation_detail_en : $gc_post->presentation_detail,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$context['gc'] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$context['current_language'] = $current_lang;
|
||||||
|
|
||||||
|
// Axes thématiques courants (annee_fin >= current year) for navigation dropdown
|
||||||
|
$current_year = (int) date('Y');
|
||||||
|
$all_axes = get_terms(['taxonomy' => 'axe_thematique', 'hide_empty' => false, 'orderby' => 'name']);
|
||||||
|
$axes_courants = [];
|
||||||
|
if (!is_wp_error($all_axes)) {
|
||||||
|
foreach ($all_axes as $axe) {
|
||||||
|
$fin = (int) get_term_meta($axe->term_id, 'annee_fin', true);
|
||||||
|
if ($fin >= $current_year) {
|
||||||
|
$link = get_term_link($axe);
|
||||||
|
if (!is_wp_error($link)) {
|
||||||
|
$axes_courants[] = [
|
||||||
|
'name' => thalim_bilingual($axe->name, $current_lang),
|
||||||
|
'link' => $link,
|
||||||
|
'ordre' => (int) get_term_meta($axe->term_id, 'ordre_daffichage', true),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
usort($axes_courants, fn($a, $b) => $a['ordre'] <=> $b['ordre']);
|
||||||
|
}
|
||||||
|
$context['axes_courants'] = $axes_courants;
|
||||||
|
|
||||||
|
// Annonces page URL (language-aware)
|
||||||
|
$annonces_page = get_page_by_path('annonces');
|
||||||
|
$annonces_base = $annonces_page ? get_permalink($annonces_page->ID) : home_url('/annonces/');
|
||||||
|
$context['annonces_url'] = $annonces_base;
|
||||||
|
|
||||||
|
// Language switcher
|
||||||
|
$context['languages'] = thalim_language_switcher();
|
||||||
|
|
||||||
|
return $context;
|
||||||
|
}
|
||||||
|
add_filter('timber/context', 'add_to_context');
|
||||||
148
inc/event-dates.php
Normal file
148
inc/event-dates.php
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Tri et filtre par date d'événement (date_de_debut / datetime / post_date)
|
||||||
|
* + helpers de listes : groupes d'axes pour les filtres, posts épinglés.
|
||||||
|
*
|
||||||
|
* Activation sur un WP_Query / Timber::get_posts :
|
||||||
|
* 'thalim_event_date_order' => true
|
||||||
|
* 'thalim_event_date_filter' => ['from' => 'Y-m-d', 'to' => 'Y-m-d']
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Event date ordering: COALESCE(date_de_debut, datetime, post_date)
|
||||||
|
// Activated by adding 'thalim_event_date_order' => true to WP_Query args.
|
||||||
|
add_filter('posts_join', function ($join, $query) {
|
||||||
|
if (!$query->get('thalim_event_date_order') && !$query->get('thalim_event_date_filter')) return $join;
|
||||||
|
global $wpdb;
|
||||||
|
$join .= " LEFT JOIN {$wpdb->postmeta} AS thalim_ed"
|
||||||
|
. " ON (thalim_ed.post_id = {$wpdb->posts}.ID"
|
||||||
|
. " AND thalim_ed.meta_key = 'date_de_debut') ";
|
||||||
|
$join .= " LEFT JOIN {$wpdb->postmeta} AS thalim_dt"
|
||||||
|
. " ON (thalim_dt.post_id = {$wpdb->posts}.ID"
|
||||||
|
. " AND thalim_dt.meta_key = 'datetime') ";
|
||||||
|
return $join;
|
||||||
|
}, 10, 2);
|
||||||
|
|
||||||
|
add_filter('posts_orderby', function ($orderby, $query) {
|
||||||
|
if (!$query->get('thalim_event_date_order')) return $orderby;
|
||||||
|
global $wpdb;
|
||||||
|
$valid = "IS NOT NULL AND %s != '' AND %s NOT LIKE '0000-00-00%%'";
|
||||||
|
return "CASE"
|
||||||
|
. " WHEN thalim_ed.meta_value " . sprintf($valid, 'thalim_ed.meta_value', 'thalim_ed.meta_value') . " THEN thalim_ed.meta_value"
|
||||||
|
. " WHEN thalim_dt.meta_value " . sprintf($valid, 'thalim_dt.meta_value', 'thalim_dt.meta_value') . " THEN thalim_dt.meta_value"
|
||||||
|
. " ELSE {$wpdb->posts}.post_date"
|
||||||
|
. " END DESC, {$wpdb->posts}.ID DESC"; // tiebreaker déterministe: sans lui, les dates ex-æquo paginent de façon instable entre les requêtes séparées (initial vs AJAX)
|
||||||
|
}, 10, 2);
|
||||||
|
|
||||||
|
// Event date range filter: uses same CASE logic as ordering so date_de_debut/datetime take priority over post_date.
|
||||||
|
// Activated by adding 'thalim_event_date_filter' => ['from' => $date_from, 'to' => $date_to] to WP_Query args.
|
||||||
|
add_filter('posts_where', function ($where, $query) {
|
||||||
|
$filter = $query->get('thalim_event_date_filter');
|
||||||
|
if (empty($filter) || (!isset($filter['from']) && !isset($filter['to']))) return $where;
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$effective = "CASE"
|
||||||
|
. " WHEN thalim_ed.meta_value IS NOT NULL AND thalim_ed.meta_value != '' AND thalim_ed.meta_value NOT LIKE '0000-00-00%' THEN thalim_ed.meta_value"
|
||||||
|
. " WHEN thalim_dt.meta_value IS NOT NULL AND thalim_dt.meta_value != '' AND thalim_dt.meta_value NOT LIKE '0000-00-00%' THEN thalim_dt.meta_value"
|
||||||
|
. " ELSE {$wpdb->posts}.post_date"
|
||||||
|
. " END";
|
||||||
|
|
||||||
|
if (!empty($filter['from'])) {
|
||||||
|
$from = $wpdb->prepare('%s', $filter['from']);
|
||||||
|
$where .= " AND ({$effective}) >= {$from}";
|
||||||
|
}
|
||||||
|
if (!empty($filter['to'])) {
|
||||||
|
$to = $wpdb->prepare('%s', $filter['to'] . ' 23:59:59');
|
||||||
|
$where .= " AND ({$effective}) <= {$to}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $where;
|
||||||
|
}, 10, 2);
|
||||||
|
|
||||||
|
// Return the IDs of posts currently pinned ("épinglé dans la catégorie") in a given category.
|
||||||
|
// A pin is active if epingler_dans_la_categorie == 1 AND date_de_fin_depinglage is empty/0000-00-00/future.
|
||||||
|
// Shared by category.php (pull them out of the main flow) and the AJAX handler (exclude them from
|
||||||
|
// pagination) so both sides stay in sync — a mismatch shifts the page boundary and dupes posts.
|
||||||
|
function thalim_get_active_pinned_ids( $category_id ) {
|
||||||
|
if ( ! $category_id ) return [];
|
||||||
|
$today = date( 'Y-m-d' );
|
||||||
|
$pinned_query = new WP_Query([
|
||||||
|
'post_type' => 'post',
|
||||||
|
'post_status' => 'publish',
|
||||||
|
'posts_per_page' => -1,
|
||||||
|
'fields' => 'ids',
|
||||||
|
'no_found_rows' => true,
|
||||||
|
'tax_query' => [[
|
||||||
|
'taxonomy' => 'category',
|
||||||
|
'field' => 'term_id',
|
||||||
|
'terms' => [ $category_id ],
|
||||||
|
'include_children' => false,
|
||||||
|
]],
|
||||||
|
'meta_query' => [[
|
||||||
|
'key' => 'epingler_dans_la_categorie',
|
||||||
|
'value' => '1',
|
||||||
|
]],
|
||||||
|
]);
|
||||||
|
$pinned_ids = [];
|
||||||
|
foreach ( $pinned_query->posts as $pid ) {
|
||||||
|
$fin = get_post_meta( $pid, 'date_de_fin_depinglage', true );
|
||||||
|
if ( empty( $fin ) || $fin === '0000-00-00' || $fin >= $today ) {
|
||||||
|
$pinned_ids[] = $pid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $pinned_ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Axes thématiques groupés pour les filtres ──────────────────
|
||||||
|
// Retourne un tableau de groupes triés par période (plus récent en premier,
|
||||||
|
// "passés" toujours en dernier). Chaque terme contient id, name, ordre.
|
||||||
|
function thalim_get_axes_filter_groups() {
|
||||||
|
$terms = get_terms( [ 'taxonomy' => 'axe_thematique', 'hide_empty' => false ] );
|
||||||
|
$axes_map = [];
|
||||||
|
|
||||||
|
foreach ( $terms as $term ) {
|
||||||
|
$debut = trim( get_term_meta( $term->term_id, 'annee_debut', true ) );
|
||||||
|
$fin = trim( get_term_meta( $term->term_id, 'annee_fin', true ) );
|
||||||
|
|
||||||
|
if ( $debut && $fin ) {
|
||||||
|
$key = $debut . '-' . $fin;
|
||||||
|
$label = $debut . ' – ' . $fin;
|
||||||
|
} else {
|
||||||
|
$key = 'passes';
|
||||||
|
$label = 'Axes antérieurs';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! isset( $axes_map[ $key ] ) ) {
|
||||||
|
$axes_map[ $key ] = [ 'label' => $label, 'debut' => intval( $debut ), 'terms' => [] ];
|
||||||
|
}
|
||||||
|
|
||||||
|
$ordre = trim( get_term_meta( $term->term_id, 'ordre_daffichage', true ) );
|
||||||
|
$axes_map[ $key ]['terms'][] = [
|
||||||
|
'id' => $term->term_id,
|
||||||
|
'name' => $term->name,
|
||||||
|
'ordre' => $ordre !== '' ? intval( $ordre ) : null,
|
||||||
|
'href' => get_term_link( $term ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tri des groupes : plus récent en premier, passés toujours en dernier
|
||||||
|
uasort( $axes_map, function ( $a, $b ) {
|
||||||
|
if ( $a['label'] === 'Axes antérieurs' ) return 1;
|
||||||
|
if ( $b['label'] === 'Axes antérieurs' ) return -1;
|
||||||
|
return $b['debut'] - $a['debut'];
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Tri des termes dans chaque groupe : ordre_daffichage d'abord, puis alphabétique
|
||||||
|
foreach ( $axes_map as &$group ) {
|
||||||
|
usort( $group['terms'], function ( $a, $b ) {
|
||||||
|
$a_has = $a['ordre'] !== null;
|
||||||
|
$b_has = $b['ordre'] !== null;
|
||||||
|
if ( $a_has && $b_has ) return $a['ordre'] - $b['ordre'];
|
||||||
|
if ( $a_has ) return -1;
|
||||||
|
if ( $b_has ) return 1;
|
||||||
|
return strcmp( $a['name'], $b['name'] );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
unset( $group );
|
||||||
|
|
||||||
|
return array_values( $axes_map );
|
||||||
|
}
|
||||||
120
inc/i18n.php
Normal file
120
inc/i18n.php
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Multilingue maison (remplace Polylang).
|
||||||
|
*
|
||||||
|
* Détection de langue par préfixe /en/, champs bilingues "FR // EN",
|
||||||
|
* préfixage des URLs internes, traduction des noms de catégories,
|
||||||
|
* filtres Twig (|bilingual, |en_url, |cat_name) et sélecteur de langue.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ── Language detection (replaces Polylang) ───────────────────────
|
||||||
|
define( 'THALIM_ORIGINAL_URI', $_SERVER['REQUEST_URI'] ?? '/' );
|
||||||
|
|
||||||
|
function thalim_current_language(): string {
|
||||||
|
if ( isset( $GLOBALS['thalim_lang_override'] ) ) return $GLOBALS['thalim_lang_override'];
|
||||||
|
$uri = THALIM_ORIGINAL_URI;
|
||||||
|
return ( strpos( $uri, '/en/' ) === 0 || rtrim( $uri, '/' ) === '/en' ) ? 'en' : 'fr';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip /en/ prefix before WordPress resolves the URL
|
||||||
|
add_filter( 'do_parse_request', function ( $do_parse ) {
|
||||||
|
$uri = THALIM_ORIGINAL_URI;
|
||||||
|
if ( strpos( $uri, '/en/' ) === 0 ) {
|
||||||
|
$_SERVER['REQUEST_URI'] = substr( $uri, 3 );
|
||||||
|
} elseif ( rtrim( $uri, '/' ) === '/en' ) {
|
||||||
|
$_SERVER['REQUEST_URI'] = '/';
|
||||||
|
}
|
||||||
|
return $do_parse;
|
||||||
|
}, 1 );
|
||||||
|
|
||||||
|
// Prevent WP canonical redirect from overriding /en/ URLs
|
||||||
|
add_filter( 'redirect_canonical', function ( $redirect ) {
|
||||||
|
if ( strpos( THALIM_ORIGINAL_URI, '/en/' ) === 0 ) return false;
|
||||||
|
return $redirect;
|
||||||
|
}, 10, 2 );
|
||||||
|
|
||||||
|
// Split "FR // EN" bilingual fields — returns the right part for the given lang
|
||||||
|
function thalim_bilingual( string $value, string $lang = null ): string {
|
||||||
|
if ( $lang === null ) $lang = thalim_current_language();
|
||||||
|
if ( strpos( $value, ' // ' ) === false ) return $value;
|
||||||
|
$parts = explode( ' // ', $value, 2 );
|
||||||
|
return ( $lang === 'en' && trim( $parts[1] ?? '' ) !== '' ) ? trim( $parts[1] ) : trim( $parts[0] );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepend /en to any internal URL when current language is EN.
|
||||||
|
// Idempotent: safe to call multiple times on the same URL.
|
||||||
|
function thalim_en_url( string $url ): string {
|
||||||
|
if ( thalim_current_language() !== 'en' ) return $url;
|
||||||
|
$home = rtrim( home_url(), '/' );
|
||||||
|
$path = substr( $url, strlen( $home ) );
|
||||||
|
if ( str_starts_with( $path, '/en/' ) || $path === '/en' ) return $url;
|
||||||
|
return $home . '/en' . $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-prefix all WP-generated internal URLs in EN mode.
|
||||||
|
// Safe on admin: THALIM_ORIGINAL_URI starts with /wp-admin/ there,
|
||||||
|
// so thalim_current_language() returns 'fr' and thalim_en_url() is a no-op.
|
||||||
|
add_filter( 'term_link', 'thalim_en_url' ); // get_term_link(), get_category_link(), get_tag_link()
|
||||||
|
add_filter( 'post_link', 'thalim_en_url' ); // get_permalink() on regular posts
|
||||||
|
add_filter( 'page_link', 'thalim_en_url' ); // get_permalink() on pages
|
||||||
|
add_filter( 'post_type_link', 'thalim_en_url' ); // get_permalink() on CPTs
|
||||||
|
add_filter( 'author_link', 'thalim_en_url' ); // get_author_posts_url()
|
||||||
|
|
||||||
|
// Return the translated category name if viewing in EN and titre_anglais is set.
|
||||||
|
// Accepts a WP_Term, Timber\Term, term_id (int), or a name string + term_id.
|
||||||
|
function thalim_cat_name( $cat, string $lang = null ): string {
|
||||||
|
if ( $lang === null ) $lang = thalim_current_language();
|
||||||
|
if ( is_object( $cat ) ) {
|
||||||
|
$term_id = $cat->term_id ?? ( $cat->id ?? 0 );
|
||||||
|
$fallback = $cat->name;
|
||||||
|
} elseif ( is_numeric( $cat ) ) {
|
||||||
|
$term_id = (int) $cat;
|
||||||
|
$term = get_term( $term_id, 'category' );
|
||||||
|
$fallback = $term && ! is_wp_error( $term ) ? $term->name : (string) $cat;
|
||||||
|
} else {
|
||||||
|
return (string) $cat;
|
||||||
|
}
|
||||||
|
if ( $lang !== 'en' ) return $fallback;
|
||||||
|
$en = get_term_meta( $term_id, 'titre_anglais', true );
|
||||||
|
return ( $en !== '' && $en !== false ) ? $en : $fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register bilingual and en_url as Twig filters
|
||||||
|
add_filter( 'timber/twig', function ( $twig ) {
|
||||||
|
$twig->addFilter( new \Twig\TwigFilter( 'bilingual', 'thalim_bilingual' ) );
|
||||||
|
$twig->addFilter( new \Twig\TwigFilter( 'en_url', 'thalim_en_url' ) );
|
||||||
|
$twig->addFilter( new \Twig\TwigFilter( 'cat_name', 'thalim_cat_name' ) );
|
||||||
|
return $twig;
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Language switcher data (replaces pll_the_languages)
|
||||||
|
// Output matches the structure header.twig expects: slug, url, current_lang
|
||||||
|
function thalim_language_switcher(): array {
|
||||||
|
$uri = THALIM_ORIGINAL_URI;
|
||||||
|
$path = parse_url( $uri, PHP_URL_PATH ) ?? '/';
|
||||||
|
$query = ( $q = parse_url( $uri, PHP_URL_QUERY ) ) ? '?' . $q : '';
|
||||||
|
$is_en = thalim_current_language() === 'en';
|
||||||
|
|
||||||
|
$fr_path = $is_en ? ( substr( $path, 3 ) ?: '/' ) : $path;
|
||||||
|
$en_path = $is_en ? $path : '/en' . $path;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'fr' => [ 'slug' => 'fr', 'url' => home_url( $fr_path ) . $query, 'current_lang' => ! $is_en ],
|
||||||
|
'en' => [ 'slug' => 'en', 'url' => home_url( $en_path ) . $query, 'current_lang' => $is_en ],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply bilingual split to the browser tab title + translate category names
|
||||||
|
add_filter('document_title_parts', function ($title_parts) {
|
||||||
|
if (!empty($title_parts['title'])) {
|
||||||
|
$title_parts['title'] = thalim_bilingual($title_parts['title']);
|
||||||
|
// On category archives, replace the title with the translated category name
|
||||||
|
if ( is_category() ) {
|
||||||
|
$cat = get_queried_object();
|
||||||
|
if ( $cat ) {
|
||||||
|
$title_parts['title'] = thalim_cat_name( $cat );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $title_parts;
|
||||||
|
});
|
||||||
@@ -135,6 +135,12 @@ function thalim_get_membres_groups() {
|
|||||||
// Pre-build member data for all relevant users (cache by ID)
|
// Pre-build member data for all relevant users (cache by ID)
|
||||||
$member_cache = [];
|
$member_cache = [];
|
||||||
$all_users = array_merge( $users, $direction_users );
|
$all_users = array_merge( $users, $direction_users );
|
||||||
|
|
||||||
|
// Précharge les usermeta de tout le monde en une requête : chaque
|
||||||
|
// thalim_build_membre_data() fait ensuite ses get_user_meta sur le cache
|
||||||
|
// (rôles, statuts, avatar local, domaines…) au lieu d'une requête par champ.
|
||||||
|
update_meta_cache( 'user', wp_list_pluck( $all_users, 'ID' ) );
|
||||||
|
|
||||||
foreach ( $all_users as $user ) {
|
foreach ( $all_users as $user ) {
|
||||||
if ( ! isset( $member_cache[ $user->ID ] ) ) {
|
if ( ! isset( $member_cache[ $user->ID ] ) ) {
|
||||||
$member_cache[ $user->ID ] = thalim_build_membre_data( $user );
|
$member_cache[ $user->ID ] = thalim_build_membre_data( $user );
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ function thalim_get_card_data($post_id) {
|
|||||||
$data = [
|
$data = [
|
||||||
'card_image' => null,
|
'card_image' => null,
|
||||||
'card_membres' => [],
|
'card_membres' => [],
|
||||||
'card_axes' => [],
|
|
||||||
'card_etiquettes' => [],
|
|
||||||
'parent_slug' => '',
|
'parent_slug' => '',
|
||||||
'card_category_name' => '',
|
'card_category_name' => '',
|
||||||
'card_category_url' => '',
|
'card_category_url' => '',
|
||||||
@@ -23,12 +21,13 @@ function thalim_get_card_data($post_id) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Category-based date formatting:
|
// Category-based date formatting:
|
||||||
// - Séminaire (cat 11, not cat 12): "Du X au Y" from first/last linked séance dates
|
// - Séminaire (hors séances): "Du X au Y" from first/last linked séance dates
|
||||||
// - Ouvrage (cat 15): year only — includes the post.date fallback (overrides Twig default d/m/Y)
|
// - Ouvrage: year only — includes the post.date fallback (overrides Twig default d/m/Y)
|
||||||
// - Default: date_de_debut > datetime > (empty → Twig falls back to post.date('d/m/Y'))
|
// - Default: date_de_debut > datetime > (empty → Twig falls back to post.date('d/m/Y'))
|
||||||
|
$seance_cat = thalim_cat_id('seance');
|
||||||
$cat_ids = wp_get_post_categories($post_id);
|
$cat_ids = wp_get_post_categories($post_id);
|
||||||
$is_seminaire = in_array(11, $cat_ids, true) && !in_array(12, $cat_ids, true);
|
$is_seminaire = in_array(thalim_cat_id('seminaires'), $cat_ids, true) && !in_array($seance_cat, $cat_ids, true);
|
||||||
$is_ouvrage = in_array(15, $cat_ids, true);
|
$is_ouvrage = in_array(thalim_cat_id('ouvrages'), $cat_ids, true);
|
||||||
|
|
||||||
if ($is_seminaire) {
|
if ($is_seminaire) {
|
||||||
// Aggregate timestamps from linked séances (Pods `seances` meta = array of post IDs)
|
// Aggregate timestamps from linked séances (Pods `seances` meta = array of post IDs)
|
||||||
@@ -86,10 +85,10 @@ function thalim_get_card_data($post_id) {
|
|||||||
|
|
||||||
// Resolve top-level parent category slug for color theming and direct category name for display
|
// Resolve top-level parent category slug for color theming and direct category name for display
|
||||||
$categories = wp_get_post_categories($post_id, ['fields' => 'all']);
|
$categories = wp_get_post_categories($post_id, ['fields' => 'all']);
|
||||||
$excluded_ids = [12, 31];
|
$excluded_ids = array_filter([ $seance_cat, thalim_cat_id('non-classe') ]);
|
||||||
$is_seance = false;
|
$is_seance = false;
|
||||||
foreach ($categories as $cat) {
|
foreach ($categories as $cat) {
|
||||||
if ($cat->term_id === 12) { $is_seance = true; }
|
if ($cat->term_id === $seance_cat) { $is_seance = true; }
|
||||||
}
|
}
|
||||||
foreach ($categories as $cat) {
|
foreach ($categories as $cat) {
|
||||||
if (in_array($cat->term_id, $excluded_ids)) continue;
|
if (in_array($cat->term_id, $excluded_ids)) continue;
|
||||||
@@ -107,28 +106,21 @@ function thalim_get_card_data($post_id) {
|
|||||||
|
|
||||||
// Séances de séminaire: link to parent séminaire with hash, derive color from parent's categories
|
// Séances de séminaire: link to parent séminaire with hash, derive color from parent's categories
|
||||||
if ($is_seance) {
|
if ($is_seance) {
|
||||||
// Always show the category label for séances even though cat 12 is excluded from color resolution
|
// Always show the category label for séances even though the séance
|
||||||
|
// category is excluded from color resolution
|
||||||
if (!$data['card_category_name']) {
|
if (!$data['card_category_name']) {
|
||||||
$seance_cat = get_category(12);
|
$seance_term = get_category($seance_cat);
|
||||||
if ($seance_cat) {
|
if ($seance_term) {
|
||||||
$data['card_category_name'] = thalim_cat_name($seance_cat);
|
$data['card_category_name'] = thalim_cat_name($seance_term);
|
||||||
$data['card_category_url'] = get_category_link(12);
|
$data['card_category_url'] = get_category_link($seance_cat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
global $wpdb;
|
$parent_id = thalim_get_seance_parent_id($post_id);
|
||||||
$parent_id = $wpdb->get_var($wpdb->prepare(
|
|
||||||
"SELECT pm.post_id FROM {$wpdb->postmeta} pm
|
|
||||||
JOIN {$wpdb->posts} p ON p.ID = pm.post_id
|
|
||||||
WHERE pm.meta_key = 'seances' AND pm.meta_value = %s
|
|
||||||
AND p.post_status = 'publish'
|
|
||||||
LIMIT 1",
|
|
||||||
(string) $post_id
|
|
||||||
));
|
|
||||||
if ($parent_id) {
|
if ($parent_id) {
|
||||||
$data['card_link'] = get_permalink((int) $parent_id) . '#seance-' . $post_id;
|
$data['card_link'] = get_permalink($parent_id) . '#seance-' . $post_id;
|
||||||
// Derive color from parent séminaire's categories if not already set
|
// Derive color from parent séminaire's categories if not already set
|
||||||
if (!$data['parent_slug']) {
|
if (!$data['parent_slug']) {
|
||||||
foreach (wp_get_post_categories((int) $parent_id, ['fields' => 'all']) as $cat) {
|
foreach (wp_get_post_categories($parent_id, ['fields' => 'all']) as $cat) {
|
||||||
if (in_array($cat->term_id, $excluded_ids)) continue;
|
if (in_array($cat->term_id, $excluded_ids)) continue;
|
||||||
$ancestor_ids = get_ancestors($cat->term_id, 'category');
|
$ancestor_ids = get_ancestors($cat->term_id, 'category');
|
||||||
$root = !empty($ancestor_ids) ? get_category(end($ancestor_ids)) : $cat;
|
$root = !empty($ancestor_ids) ? get_category(end($ancestor_ids)) : $cat;
|
||||||
@@ -186,32 +178,44 @@ function thalim_get_card_data($post_id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Axes thématiques (post IDs → titles)
|
|
||||||
$axe_ids = get_post_meta($post_id, 'axes_thematiques', false);
|
|
||||||
foreach ($axe_ids as $axe_id) {
|
|
||||||
$axe = get_post($axe_id);
|
|
||||||
if ($axe) {
|
|
||||||
$data['card_axes'][] = $axe->post_title;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Etiquettes (post IDs → titles)
|
|
||||||
$tag_ids = get_post_meta($post_id, 'etiquettes', false);
|
|
||||||
foreach ($tag_ids as $tag_id) {
|
|
||||||
$tag_post = get_post($tag_id);
|
|
||||||
if ($tag_post) {
|
|
||||||
$data['card_etiquettes'][] = $tag_post->post_title;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build card data map for a collection of posts.
|
* Build card data map for a collection of posts.
|
||||||
* Returns an array keyed by post ID.
|
* Returns an array keyed by post ID.
|
||||||
|
*
|
||||||
|
* Précharge en lot les caches meta/termes des posts et les utilisateurs
|
||||||
|
* référencés (membres/autre_membres) pour éviter les requêtes N+1 de
|
||||||
|
* thalim_get_card_data() (une grille de 12 cartes passait par des dizaines
|
||||||
|
* de get_post_meta/get_userdata individuels).
|
||||||
*/
|
*/
|
||||||
function thalim_get_cards_data($posts) {
|
function thalim_get_cards_data($posts) {
|
||||||
|
$ids = [];
|
||||||
|
foreach ($posts as $post) {
|
||||||
|
$ids[] = $post->ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ids) {
|
||||||
|
// Meta + termes en 2 requêtes pour tout le lot
|
||||||
|
update_meta_cache('post', $ids);
|
||||||
|
update_object_term_cache($ids, 'post');
|
||||||
|
|
||||||
|
// Utilisateurs référencés par les cartes (lecture servie par le cache meta)
|
||||||
|
$user_ids = [];
|
||||||
|
foreach ($ids as $pid) {
|
||||||
|
foreach (['membres', 'autre_membres'] as $key) {
|
||||||
|
foreach (get_post_meta($pid, $key, false) as $uid) {
|
||||||
|
$user_ids[] = (int) $uid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$user_ids = array_values(array_unique(array_filter($user_ids)));
|
||||||
|
if ($user_ids) {
|
||||||
|
cache_users($user_ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$cards = [];
|
$cards = [];
|
||||||
foreach ($posts as $post) {
|
foreach ($posts as $post) {
|
||||||
$cards[$post->ID] = thalim_get_card_data($post->ID);
|
$cards[$post->ID] = thalim_get_card_data($post->ID);
|
||||||
|
|||||||
52
inc/seance-helpers.php
Normal file
52
inc/seance-helpers.php
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Séances de séminaire (cat « seance-de-seminaire ») :
|
||||||
|
* résolution mémoïsée séance → séminaire parent, et redirection des
|
||||||
|
* permaliens de séance vers le parent avec ancre #seance-{ID}.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renvoie l'ID du séminaire parent (publié) qui liste cette séance dans son
|
||||||
|
* champ Pods `seances`, ou 0. Mémoïsé par requête : la même résolution est
|
||||||
|
* utilisée par les cards, l'agenda, l'AJAX et la redirection.
|
||||||
|
*/
|
||||||
|
function thalim_get_seance_parent_id( int $seance_id ): int {
|
||||||
|
static $cache = [];
|
||||||
|
if ( ! isset( $cache[ $seance_id ] ) ) {
|
||||||
|
global $wpdb;
|
||||||
|
$cache[ $seance_id ] = (int) $wpdb->get_var( $wpdb->prepare(
|
||||||
|
"SELECT pm.post_id FROM {$wpdb->postmeta} pm
|
||||||
|
JOIN {$wpdb->posts} p ON p.ID = pm.post_id
|
||||||
|
WHERE pm.meta_key = 'seances' AND pm.meta_value = %s
|
||||||
|
AND p.post_status = 'publish'
|
||||||
|
LIMIT 1",
|
||||||
|
(string) $seance_id
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
return $cache[ $seance_id ];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lien public d'une séance : permalien du séminaire parent + #seance-{ID},
|
||||||
|
* ou permalien de la séance elle-même si aucun parent n'est trouvé.
|
||||||
|
*/
|
||||||
|
function thalim_get_seance_link( int $seance_id ): string {
|
||||||
|
$parent_id = thalim_get_seance_parent_id( $seance_id );
|
||||||
|
return $parent_id
|
||||||
|
? get_permalink( $parent_id ) . '#seance-' . $seance_id
|
||||||
|
: (string) get_permalink( $seance_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Séance de séminaire: redirect to parent séminaire with #seance-{ID} anchor
|
||||||
|
add_action( 'template_redirect', function() {
|
||||||
|
if ( ! is_single() ) return;
|
||||||
|
if ( ! has_category( thalim_cat_id( 'seance' ) ) ) return;
|
||||||
|
|
||||||
|
$seance_id = get_the_ID();
|
||||||
|
$parent_id = thalim_get_seance_parent_id( $seance_id );
|
||||||
|
|
||||||
|
if ( $parent_id ) {
|
||||||
|
wp_redirect( get_permalink( $parent_id ) . '#seance-' . $seance_id, 301 );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
} );
|
||||||
@@ -40,14 +40,16 @@ function thalim_get_single_data($post_id) {
|
|||||||
$data = [
|
$data = [
|
||||||
// Text fields
|
// Text fields
|
||||||
'sous_titre' => thalim_bilingual( get_post_meta($post_id, 'sous-titre', true) ?: '', $lang ),
|
'sous_titre' => thalim_bilingual( get_post_meta($post_id, 'sous-titre', true) ?: '', $lang ),
|
||||||
'reference_bibliographique' => get_post_meta($post_id, 'reference_bibliographique', true) ?: '',
|
// wp_kses_post: rendus en |raw dans single.twig (autoescape off) et
|
||||||
|
// éditables par les contributeurs listés en membres → filtrer le HTML.
|
||||||
|
'reference_bibliographique' => wp_kses_post( get_post_meta($post_id, 'reference_bibliographique', true) ?: '' ),
|
||||||
'editeur' => get_post_meta($post_id, 'editeur', true) ?: '',
|
'editeur' => get_post_meta($post_id, 'editeur', true) ?: '',
|
||||||
'journal' => get_post_meta($post_id, 'journal', true) ?: '',
|
'journal' => get_post_meta($post_id, 'journal', true) ?: '',
|
||||||
'lieu' => thalim_bilingual( get_post_meta($post_id, 'lieu', true) ?: '', $lang ),
|
'lieu' => thalim_bilingual( get_post_meta($post_id, 'lieu', true) ?: '', $lang ),
|
||||||
'adresse' => nl2br( esc_html( get_post_meta($post_id, 'adresse', true) ?: '' ) ),
|
'adresse' => nl2br( esc_html( get_post_meta($post_id, 'adresse', true) ?: '' ) ),
|
||||||
'autrepersonnes' => get_post_meta($post_id, 'autrepersonnes', true) ?: '',
|
'autrepersonnes' => get_post_meta($post_id, 'autrepersonnes', true) ?: '',
|
||||||
'autre_autrepersonnes' => get_post_meta($post_id, 'autre_autrepersonnes', true) ?: '',
|
'autre_autrepersonnes' => get_post_meta($post_id, 'autre_autrepersonnes', true) ?: '',
|
||||||
'body_en' => apply_filters( 'the_content', get_post_meta($post_id, 'body_en', true) ?: '' ),
|
'body_en' => apply_filters( 'the_content', wp_kses_post( get_post_meta($post_id, 'body_en', true) ?: '' ) ),
|
||||||
|
|
||||||
// Dates (formatted for display)
|
// Dates (formatted for display)
|
||||||
'datetime' => thalim_format_date(get_post_meta($post_id, 'datetime', true), $lang),
|
'datetime' => thalim_format_date(get_post_meta($post_id, 'datetime', true), $lang),
|
||||||
@@ -110,10 +112,10 @@ function thalim_get_single_data($post_id) {
|
|||||||
if ($ts_debut) $data['date_debut_ymd'] = date('Y-m-d', $ts_debut);
|
if ($ts_debut) $data['date_debut_ymd'] = date('Y-m-d', $ts_debut);
|
||||||
if ($ts_fin) $data['date_fin_ymd'] = date('Y-m-d', $ts_fin);
|
if ($ts_fin) $data['date_fin_ymd'] = date('Y-m-d', $ts_fin);
|
||||||
|
|
||||||
// Ouvrages (cat 15): override display to year only — raw timestamps and
|
// Ouvrages: override display to year only — raw timestamps and
|
||||||
// *_ymd fields stay full-precision so sorting/filtering on index pages
|
// *_ymd fields stay full-precision so sorting/filtering on index pages
|
||||||
// (`thalim_event_date_order`) keeps working.
|
// (`thalim_event_date_order`) keeps working.
|
||||||
if (in_array(15, wp_get_post_categories($post_id), true)) {
|
if (in_array(thalim_cat_id('ouvrages'), wp_get_post_categories($post_id), true)) {
|
||||||
$data['date_de_debut'] = thalim_format_date($raw_debut, $lang, 'Y');
|
$data['date_de_debut'] = thalim_format_date($raw_debut, $lang, 'Y');
|
||||||
$data['date_de_fin'] = thalim_format_date($raw_fin, $lang, 'Y');
|
$data['date_de_fin'] = thalim_format_date($raw_fin, $lang, 'Y');
|
||||||
$data['datetime'] = thalim_format_date(get_post_meta($post_id, 'datetime', true), $lang, 'Y');
|
$data['datetime'] = thalim_format_date(get_post_meta($post_id, 'datetime', true), $lang, 'Y');
|
||||||
@@ -138,7 +140,7 @@ function thalim_get_single_data($post_id) {
|
|||||||
|
|
||||||
// --- Category hierarchy for breadcrumb and color ---
|
// --- Category hierarchy for breadcrumb and color ---
|
||||||
$categories = wp_get_post_categories($post_id, ['fields' => 'all']);
|
$categories = wp_get_post_categories($post_id, ['fields' => 'all']);
|
||||||
$excluded_ids = [12, 31];
|
$excluded_ids = array_filter([ thalim_cat_id('seance'), thalim_cat_id('non-classe') ]);
|
||||||
foreach ($categories as $cat) {
|
foreach ($categories as $cat) {
|
||||||
if (in_array($cat->term_id, $excluded_ids)) continue;
|
if (in_array($cat->term_id, $excluded_ids)) continue;
|
||||||
$ancestor_ids = get_ancestors($cat->term_id, 'category');
|
$ancestor_ids = get_ancestors($cat->term_id, 'category');
|
||||||
@@ -256,7 +258,6 @@ function thalim_get_single_data($post_id) {
|
|||||||
'post_type' => 'post',
|
'post_type' => 'post',
|
||||||
'post__in' => array_map('intval', $related_ids),
|
'post__in' => array_map('intval', $related_ids),
|
||||||
'posts_per_page' => -1,
|
'posts_per_page' => -1,
|
||||||
'lang' => '',
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,7 +273,6 @@ function thalim_get_single_data($post_id) {
|
|||||||
'orderby' => 'meta_value',
|
'orderby' => 'meta_value',
|
||||||
'meta_key' => 'date_de_debut',
|
'meta_key' => 'date_de_debut',
|
||||||
'order' => 'ASC',
|
'order' => 'ASC',
|
||||||
'lang' => '',
|
|
||||||
'post_status' => ['publish', 'future'],
|
'post_status' => ['publish', 'future'],
|
||||||
]);
|
]);
|
||||||
$now = time();
|
$now = time();
|
||||||
@@ -303,7 +303,7 @@ function thalim_get_single_data($post_id) {
|
|||||||
'heure_de_fin' => substr( get_post_meta($seance->ID, 'heure_de_fin', true) ?: '', 0, 5 ),
|
'heure_de_fin' => substr( get_post_meta($seance->ID, 'heure_de_fin', true) ?: '', 0, 5 ),
|
||||||
'lieu' => thalim_bilingual( get_post_meta($seance->ID, 'lieu', true) ?: '', $lang ),
|
'lieu' => thalim_bilingual( get_post_meta($seance->ID, 'lieu', true) ?: '', $lang ),
|
||||||
'adresse' => nl2br( esc_html( get_post_meta($seance->ID, 'adresse', true) ?: '' ) ),
|
'adresse' => nl2br( esc_html( get_post_meta($seance->ID, 'adresse', true) ?: '' ) ),
|
||||||
'body_en' => apply_filters( 'the_content', get_post_meta($seance->ID, 'body_en', true) ?: '' ),
|
'body_en' => apply_filters( 'the_content', wp_kses_post( get_post_meta($seance->ID, 'body_en', true) ?: '' ) ),
|
||||||
'intervenants' => [],
|
'intervenants' => [],
|
||||||
'images' => [],
|
'images' => [],
|
||||||
'documents' => [],
|
'documents' => [],
|
||||||
@@ -372,7 +372,6 @@ function thalim_get_single_data($post_id) {
|
|||||||
'post_type' => 'post',
|
'post_type' => 'post',
|
||||||
'post__in' => array_map('intval', $s_related_ids),
|
'post__in' => array_map('intval', $s_related_ids),
|
||||||
'posts_per_page' => -1,
|
'posts_per_page' => -1,
|
||||||
'lang' => '',
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
54
index.php
54
index.php
@@ -30,7 +30,6 @@ $annonces_raw = Timber::get_posts([
|
|||||||
]],
|
]],
|
||||||
'orderby' => 'date',
|
'orderby' => 'date',
|
||||||
'order' => 'DESC',
|
'order' => 'DESC',
|
||||||
'lang' => '',
|
|
||||||
'thalim_event_date_order' => true,
|
'thalim_event_date_order' => true,
|
||||||
]);
|
]);
|
||||||
$context['annonces'] = $sort_with_pinned($annonces_raw, 'epingler_dans_le_diaporama_dannonces', $max_swiper);
|
$context['annonces'] = $sort_with_pinned($annonces_raw, 'epingler_dans_le_diaporama_dannonces', $max_swiper);
|
||||||
@@ -42,20 +41,19 @@ $publications_raw = Timber::get_posts([
|
|||||||
'posts_per_page' => 30,
|
'posts_per_page' => 30,
|
||||||
'orderby' => 'date',
|
'orderby' => 'date',
|
||||||
'order' => 'DESC',
|
'order' => 'DESC',
|
||||||
'lang' => '',
|
|
||||||
'tax_query' => [
|
'tax_query' => [
|
||||||
'relation' => 'AND',
|
'relation' => 'AND',
|
||||||
[
|
[
|
||||||
'taxonomy' => 'category',
|
'taxonomy' => 'category',
|
||||||
'field' => 'term_id',
|
'field' => 'term_id',
|
||||||
'terms' => [4],
|
'terms' => [ thalim_cat_id('publications') ],
|
||||||
'operator' => 'IN',
|
'operator' => 'IN',
|
||||||
'include_children' => true,
|
'include_children' => true,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'taxonomy' => 'category',
|
'taxonomy' => 'category',
|
||||||
'field' => 'term_id',
|
'field' => 'term_id',
|
||||||
'terms' => [16],
|
'terms' => [ thalim_cat_id('articles') ],
|
||||||
'operator' => 'NOT IN',
|
'operator' => 'NOT IN',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -74,24 +72,34 @@ $publications_raw = Timber::get_posts([
|
|||||||
]);
|
]);
|
||||||
$context['publications'] = $sort_with_pinned($publications_raw, 'epingler_dans_le_diaporama_des_publications_et_productions', $max_swiper);
|
$context['publications'] = $sort_with_pinned($publications_raw, 'epingler_dans_le_diaporama_des_publications_et_productions', $max_swiper);
|
||||||
$context['publications_cards'] = thalim_get_cards_data($publications_raw);
|
$context['publications_cards'] = thalim_get_cards_data($publications_raw);
|
||||||
$context['publications_link'] = thalim_en_url( get_category_link(4) );
|
$context['publications_link'] = thalim_en_url( get_category_link( thalim_cat_id('publications') ) );
|
||||||
$context['annonces_link'] = thalim_en_url( get_permalink(29100) );
|
// Page « annonces » résolue par slug dans add_to_context() (annonces_url),
|
||||||
|
// au lieu d'un get_permalink(29100) codé en dur (post potentiellement absent).
|
||||||
|
$context['annonces_link'] = $context['annonces_url'];
|
||||||
|
|
||||||
// --- Message du laboratoire ---
|
// --- Message du laboratoire ---
|
||||||
$messages_labo = Timber::get_posts([
|
$messages_labo = Timber::get_posts([
|
||||||
'post_type' => 'post',
|
'post_type' => 'post',
|
||||||
'posts_per_page' => 5,
|
'posts_per_page' => 5,
|
||||||
'cat' => 268,
|
'cat' => thalim_cat_id('message-labo'),
|
||||||
'orderby' => 'date',
|
'orderby' => 'date',
|
||||||
'order' => 'DESC',
|
'order' => 'DESC',
|
||||||
'lang' => '',
|
|
||||||
]);
|
]);
|
||||||
$context['messages_labo'] = $messages_labo ?: [];
|
$context['messages_labo'] = $messages_labo ?: [];
|
||||||
$context['message_labo_link'] = thalim_en_url( get_category_link(268) );
|
$context['message_labo_link'] = thalim_en_url( get_category_link( thalim_cat_id('message-labo') ) );
|
||||||
|
|
||||||
// --- Agenda (médiation scientifique + séances de séminaire à venir) ---
|
// --- Agenda (médiation scientifique + séances de séminaire à venir) ---
|
||||||
$agenda_lang = thalim_current_language();
|
$agenda_lang = thalim_current_language();
|
||||||
$mediation_cat_ids = [5, 18, 19, 20, 21, 22, 23];
|
// Catégories « agenda » résolues par slug (médiation + newsletter/gazette historiques)
|
||||||
|
$mediation_cat_ids = array_values( array_filter( [
|
||||||
|
thalim_cat_id('mediation'),
|
||||||
|
thalim_cat_id('evenements-culturels'),
|
||||||
|
thalim_cat_id('medias'),
|
||||||
|
thalim_cat_id('newsletter'),
|
||||||
|
thalim_cat_id('gazette'),
|
||||||
|
thalim_cat_id('podcast'),
|
||||||
|
thalim_cat_id('captations'),
|
||||||
|
] ) );
|
||||||
$months_fr = ['jan.', 'fév.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'];
|
$months_fr = ['jan.', 'fév.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'];
|
||||||
$months_en = ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'Jun.', 'Jul.', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.'];
|
$months_en = ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'Jun.', 'Jul.', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.'];
|
||||||
$agenda_type_fields = [
|
$agenda_type_fields = [
|
||||||
@@ -123,7 +131,6 @@ $mediation_upcoming = Timber::get_posts([
|
|||||||
'posts_per_page' => 8,
|
'posts_per_page' => 8,
|
||||||
'category__in' => $mediation_cat_ids,
|
'category__in' => $mediation_cat_ids,
|
||||||
'orderby' => ['date_clause' => 'ASC'],
|
'orderby' => ['date_clause' => 'ASC'],
|
||||||
'lang' => '',
|
|
||||||
'meta_query' => [
|
'meta_query' => [
|
||||||
'date_clause' => [
|
'date_clause' => [
|
||||||
'key' => 'date_de_debut',
|
'key' => 'date_de_debut',
|
||||||
@@ -150,13 +157,12 @@ foreach ($mediation_upcoming as $mpost) {
|
|||||||
if ($item) $agenda_items[] = $item;
|
if ($item) $agenda_items[] = $item;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Upcoming séances de séminaire (cat 12)
|
// 2. Upcoming séances de séminaire
|
||||||
$seances_upcoming = Timber::get_posts([
|
$seances_upcoming = Timber::get_posts([
|
||||||
'post_type' => 'post',
|
'post_type' => 'post',
|
||||||
'posts_per_page' => 8,
|
'posts_per_page' => 8,
|
||||||
'category__in' => [12],
|
'category__in' => [ thalim_cat_id('seance') ],
|
||||||
'orderby' => ['date_clause' => 'ASC'],
|
'orderby' => ['date_clause' => 'ASC'],
|
||||||
'lang' => '',
|
|
||||||
'meta_query' => [
|
'meta_query' => [
|
||||||
'date_clause' => [
|
'date_clause' => [
|
||||||
'key' => 'date_de_debut',
|
'key' => 'date_de_debut',
|
||||||
@@ -169,19 +175,7 @@ $seances_upcoming = Timber::get_posts([
|
|||||||
foreach ($seances_upcoming as $seance) {
|
foreach ($seances_upcoming as $seance) {
|
||||||
$raw_date = get_post_meta($seance->ID, 'date_de_debut', true);
|
$raw_date = get_post_meta($seance->ID, 'date_de_debut', true);
|
||||||
if (!$raw_date) continue;
|
if (!$raw_date) continue;
|
||||||
// Direct DB lookup — bypasses Polylang and other hook filters
|
$link = thalim_get_seance_link($seance->ID);
|
||||||
global $wpdb;
|
|
||||||
$parent_id = $wpdb->get_var($wpdb->prepare(
|
|
||||||
"SELECT pm.post_id FROM {$wpdb->postmeta} pm
|
|
||||||
JOIN {$wpdb->posts} p ON p.ID = pm.post_id
|
|
||||||
WHERE pm.meta_key = 'seances' AND pm.meta_value = %s
|
|
||||||
AND p.post_status = 'publish'
|
|
||||||
LIMIT 1",
|
|
||||||
(string) $seance->ID
|
|
||||||
));
|
|
||||||
$link = $parent_id
|
|
||||||
? get_permalink((int) $parent_id) . '#seance-' . $seance->ID
|
|
||||||
: get_permalink($seance->ID);
|
|
||||||
$label = $agenda_lang === 'en' ? 'Seminar session' : 'Séance de séminaire';
|
$label = $agenda_lang === 'en' ? 'Seminar session' : 'Séance de séminaire';
|
||||||
$item = $make_agenda_item($seance, $raw_date, $label, get_post_meta($seance->ID, 'lieu', true) ?: '', $link);
|
$item = $make_agenda_item($seance, $raw_date, $label, get_post_meta($seance->ID, 'lieu', true) ?: '', $link);
|
||||||
if ($item) $agenda_items[] = $item;
|
if ($item) $agenda_items[] = $item;
|
||||||
@@ -198,7 +192,6 @@ if (empty($agenda_items)) {
|
|||||||
'posts_per_page' => 5,
|
'posts_per_page' => 5,
|
||||||
'category__in' => $mediation_cat_ids,
|
'category__in' => $mediation_cat_ids,
|
||||||
'orderby' => ['date_clause' => 'DESC'],
|
'orderby' => ['date_clause' => 'DESC'],
|
||||||
'lang' => '',
|
|
||||||
'meta_query' => [
|
'meta_query' => [
|
||||||
'date_clause' => ['key' => 'date_de_debut', 'type' => 'DATETIME'],
|
'date_clause' => ['key' => 'date_de_debut', 'type' => 'DATETIME'],
|
||||||
],
|
],
|
||||||
@@ -222,7 +215,7 @@ if (empty($agenda_items)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$context['agenda_items'] = $agenda_items;
|
$context['agenda_items'] = $agenda_items;
|
||||||
$context['manifestations_link'] = thalim_en_url( add_query_arg( 'view', 'agenda', get_category_link(3) ) );
|
$context['manifestations_link'] = thalim_en_url( add_query_arg( 'view', 'agenda', get_category_link( thalim_cat_id('manifestations') ) ) );
|
||||||
|
|
||||||
// --- Quick links ---
|
// --- Quick links ---
|
||||||
$newsletter_cat = get_category_by_slug('newsletter');
|
$newsletter_cat = get_category_by_slug('newsletter');
|
||||||
@@ -247,7 +240,7 @@ if ( ! $newsletter_url ) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
$context['quick_links'] = [
|
$context['quick_links'] = [
|
||||||
'agenda' => thalim_en_url(add_query_arg('view', 'agenda', get_category_link(3))),
|
'agenda' => thalim_en_url(add_query_arg('view', 'agenda', get_category_link( thalim_cat_id('manifestations') ))),
|
||||||
'contacts' => thalim_en_url(home_url('/contacts/')),
|
'contacts' => thalim_en_url(home_url('/contacts/')),
|
||||||
'newsletter' => $newsletter_url,
|
'newsletter' => $newsletter_url,
|
||||||
];
|
];
|
||||||
@@ -257,7 +250,6 @@ $context['has_tags'] = !empty(get_terms([
|
|||||||
'taxonomy' => 'post_tag',
|
'taxonomy' => 'post_tag',
|
||||||
'hide_empty' => true,
|
'hide_empty' => true,
|
||||||
'number' => 1,
|
'number' => 1,
|
||||||
'lang' => '',
|
|
||||||
]));
|
]));
|
||||||
|
|
||||||
Timber::render('index.twig', $context);
|
Timber::render('index.twig', $context);
|
||||||
405
js/admin/admin-base.js
Normal file
405
js/admin/admin-base.js
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
/**
|
||||||
|
* Socle partagé des customisations admin (namespace window.ThalimAdmin).
|
||||||
|
* Chargé sur toutes les pages admin, avant les scripts de contexte
|
||||||
|
* (admin-rename, admin-post-edit, admin-profile, admin-taxonomy-list,
|
||||||
|
* admin-pods-modal) qui en dépendent.
|
||||||
|
*/
|
||||||
|
(function($) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// ── Configuration ──────────────────────────────────────────
|
||||||
|
// Sélecteurs et identifiants Pods utilisés par les modules admin,
|
||||||
|
// centralisés pour qu'un renommage côté Pods ne demande qu'une édition ici.
|
||||||
|
var CONFIG = {
|
||||||
|
// Options désactivées dans le select Pods « Type d'annonce » (term IDs)
|
||||||
|
disabledCategoryIds: ['1', '12', '5', '20'],
|
||||||
|
// Catégorie « Séance de séminaire » (verrouillée dans la modale Pods)
|
||||||
|
seanceCategoryId: '12',
|
||||||
|
// Select Pods de la catégorie
|
||||||
|
categorySelect: '#pods-form-ui-pods-meta-categorie',
|
||||||
|
// IDs des éditeurs TinyMCE Pods à réparer (reinitEditor)
|
||||||
|
editors: {
|
||||||
|
bodyEn: 'pods-form-ui-pods-meta-body-en',
|
||||||
|
refBib: 'pods-form-ui-pods-meta-reference-bibliographique'
|
||||||
|
},
|
||||||
|
// Metaboxes Pods déplacées / observées
|
||||||
|
boxes: {
|
||||||
|
bodyEn: '#pods-meta-body-en',
|
||||||
|
typeDannonce: '#pods-meta-type-dannonce',
|
||||||
|
affichageAccueil: '#pods-meta-affichage-sur-laccueil',
|
||||||
|
thematique: '#pods-meta-thematique',
|
||||||
|
champsContextuels: '#pods-meta-champs-contextuels',
|
||||||
|
documentsJoints: '#pods-meta-documents-joints',
|
||||||
|
membres: '#pods-meta-membres'
|
||||||
|
},
|
||||||
|
// Classes des lignes Pods conditionnelles observées (MutationObserver)
|
||||||
|
rows: {
|
||||||
|
axes: 'pods-form-ui-row-name-axes-thematiques',
|
||||||
|
refBib: 'pods-form-ui-row-name-reference-bibliographique'
|
||||||
|
},
|
||||||
|
// Taxonomies dont la page liste reçoit l'info-bulle « FR // EN »
|
||||||
|
translateTaxonomies: ['axe_thematique', 'programme_de_recherche', 'post_tag']
|
||||||
|
};
|
||||||
|
|
||||||
|
// Exécute un bloc d'init de façon isolée : une exception dans un module
|
||||||
|
// n'empêche pas les modules suivants de s'initialiser.
|
||||||
|
function safeRun(name, fn) {
|
||||||
|
try {
|
||||||
|
fn();
|
||||||
|
} catch (err) {
|
||||||
|
if (window.console && console.error) {
|
||||||
|
console.error('[thalim-admin] ' + name + ' failed:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 updatePostboxVisibility() {
|
||||||
|
document.querySelectorAll('.postbox').forEach(function(postBox) {
|
||||||
|
if (postBox.id.startsWith('pods')) {
|
||||||
|
// body-en is controlled by language tabs — never auto-hide it
|
||||||
|
if ('#' + postBox.id === CONFIG.boxes.bodyEn) return;
|
||||||
|
var fields = postBox.querySelectorAll('tr');
|
||||||
|
var hasVisibleFields = Array.from(fields).some(function(field) {
|
||||||
|
return 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Info-popovers (post / user / taxonomy) ─────────────────
|
||||||
|
var INFO_ICON = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 11v6"/><path d="M12 8v.01" stroke-width="2"/></svg>';
|
||||||
|
var TRANSLATE_ICON = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>';
|
||||||
|
|
||||||
|
var TRANSLATE_LINES = [
|
||||||
|
'Traduction en anglais après //',
|
||||||
|
'ex : Texte en français // 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ès //',
|
||||||
|
'ex : Titre de l’annonce // Title of the announcement'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: '#pods-meta-documents-joints .postbox-header h2',
|
||||||
|
lines: [
|
||||||
|
'Ajouter les images dans les documents.',
|
||||||
|
'Ajouter les légendes comme titre du document.'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: '#pods-meta-membres .postbox-header h2',
|
||||||
|
lines: [
|
||||||
|
'Le champ fonction change le libellé de la liste de personnes citées.',
|
||||||
|
'Le champ membre permet de lister les membres de Thalim liés à l’annonce.',
|
||||||
|
'Le champ autre personnes permet de lister des personnes extérieures à Thalim.'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: '#pods-meta-dates .postbox-header h2',
|
||||||
|
lines: [
|
||||||
|
'Pour entrer une date sans l’heure, régler l’heure sur 00 :00.'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: '#pods-meta-affichage-sur-laccueil .postbox-header h2',
|
||||||
|
lines: [
|
||||||
|
'Épingler l’annonce dans le diaporama la fait s’afficher avant les autres.'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: '#pods-meta-medias .postbox-header h2',
|
||||||
|
lines: [
|
||||||
|
'Pour ajouter un média Canal U, copier le lien depuis « Citer cette ressource ».',
|
||||||
|
'ex : 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 '<p>' + line + '</p>';
|
||||||
|
}).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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Reveal (#wpbody est masqué en CSS sur post/profil jusqu'à l'init) ──
|
||||||
|
function markReady() {
|
||||||
|
document.body.classList.add('admin-mods-ready');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback global : force le reveal après 2 s même si le script de
|
||||||
|
// contexte a planté ou n'a pas été chargé.
|
||||||
|
$(document).ready(function() {
|
||||||
|
setTimeout(markReady, 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.ThalimAdmin = {
|
||||||
|
CONFIG: CONFIG,
|
||||||
|
safeRun: safeRun,
|
||||||
|
isPostEditPage: isPostEditPage,
|
||||||
|
isProfileEditPage: isProfileEditPage,
|
||||||
|
getProfileForm: getProfileForm,
|
||||||
|
isPodsModal: isPodsModal,
|
||||||
|
ensureVisualMode: ensureVisualMode,
|
||||||
|
reinitEditor: reinitEditor,
|
||||||
|
updatePostboxVisibility: updatePostboxVisibility,
|
||||||
|
initInfoPopovers: initInfoPopovers,
|
||||||
|
markReady: markReady
|
||||||
|
};
|
||||||
|
|
||||||
|
})(jQuery);
|
||||||
49
js/admin/admin-pods-modal.js
Normal file
49
js/admin/admin-pods-modal.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Modale Pods de création de séance (URL contenant pods_modal) :
|
||||||
|
* verrouille la catégorie sur « Séance de séminaire ».
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
if (!TA.isPodsModal()) return;
|
||||||
|
|
||||||
|
var lockSeanceCategory = function() {
|
||||||
|
var seanceCat = CONFIG.seanceCategoryId;
|
||||||
|
var itemId = $('#post_ID').val();
|
||||||
|
if (window.PodsDFV && itemId) {
|
||||||
|
window.PodsDFV.setFieldValue('post', itemId, 'categorie', seanceCat, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock category select to the séance category in iframe —
|
||||||
|
// delay to run after Pods React re-render
|
||||||
|
setTimeout(function() {
|
||||||
|
var $select = $(CONFIG.categorySelect);
|
||||||
|
if ($select.length) {
|
||||||
|
$select.find('option').each(function() {
|
||||||
|
this.disabled = this.value !== seanceCat;
|
||||||
|
});
|
||||||
|
$select.val(seanceCat);
|
||||||
|
}
|
||||||
|
safeRun('updatePostboxVisibility', TA.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') {
|
||||||
|
safeRun('lockSeanceCategory', lockSeanceCategory);
|
||||||
|
} else {
|
||||||
|
$(window).on('load', function() {
|
||||||
|
safeRun('lockSeanceCategory', lockSeanceCategory);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
})(jQuery);
|
||||||
299
js/admin/admin-post-edit.js
Normal file
299
js/admin/admin-post-edit.js
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
/**
|
||||||
|
* Page d'édition de post (post.php / post-new.php) : onglets FR/EN du corps,
|
||||||
|
* réordonnancement des metaboxes, groupement des axes, visibilité
|
||||||
|
* conditionnelle des boxes Pods, popovers d'aide, fixes TinyMCE/Gutenberg.
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
// 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.querySelector(CONFIG.boxes.bodyEn);
|
||||||
|
if (!nativeEditor || !bodyEnBox) return;
|
||||||
|
|
||||||
|
var tabBar = document.createElement('div');
|
||||||
|
tabBar.className = 'body-lang-tabs';
|
||||||
|
tabBar.innerHTML =
|
||||||
|
'<button type="button" class="body-lang-tab is-active" data-panel="fr">Français</button>' +
|
||||||
|
'<button type="button" class="body-lang-tab" data-panel="en">English</button>';
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.querySelector(CONFIG.boxes.bodyEn);
|
||||||
|
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) TA.ensureVisualMode('content');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var enEditorId = CONFIG.editors.bodyEn;
|
||||||
|
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
|
||||||
|
TA.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
|
||||||
|
TA.ensureVisualMode('content');
|
||||||
|
TA.ensureVisualMode(enEditorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function groupAxesCheckboxes() {
|
||||||
|
if (!window.thalimAxesGroups || !thalimAxesGroups.length) return;
|
||||||
|
|
||||||
|
var row = document.querySelector('.' + CONFIG.rows.axes);
|
||||||
|
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 <li> 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);
|
||||||
149
js/admin/admin-profile.js
Normal file
149
js/admin/admin-profile.js
Normal 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);
|
||||||
77
js/admin/admin-rename.js
Normal file
77
js/admin/admin-rename.js
Normal file
@@ -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);
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
||||||
80
js/admin/admin-taxonomy-list.js
Normal file
80
js/admin/admin-taxonomy-list.js
Normal file
@@ -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 <p> (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);
|
||||||
@@ -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 =
|
|
||||||
'<button type="button" class="body-lang-tab is-active" data-panel="fr">Fran\u00e7ais</button>' +
|
|
||||||
'<button type="button" class="body-lang-tab" data-panel="en">English</button>';
|
|
||||||
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 <li> 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 = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 11v6"/><path d="M12 8v.01" stroke-width="2"/></svg>';
|
|
||||||
var TRANSLATE_ICON = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>';
|
|
||||||
|
|
||||||
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 '<p>' + line + '</p>';
|
|
||||||
}).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 <p> (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);
|
|
||||||
@@ -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 = $('<div class="notice notice-info is-dismissible"><p>Votre contenu a \u00e9t\u00e9 restaur\u00e9 suite \u00e0 une erreur de validation. V\u00e9rifiez les champs obligatoires avant de publier.</p></div>');
|
|
||||||
$('#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);
|
|
||||||
@@ -1,4 +1,15 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function () {
|
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 ────────────────────────────────────────
|
// ── Filters toggle ────────────────────────────────────────
|
||||||
var membresToggleBtn = document.getElementById('membres-filters-toggle');
|
var membresToggleBtn = document.getElementById('membres-filters-toggle');
|
||||||
var membresFiltersEl = document.getElementById('membres-filters');
|
var membresFiltersEl = document.getElementById('membres-filters');
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
<?php
|
<?php
|
||||||
$context = Timber::context();
|
$context = Timber::context();
|
||||||
|
|
||||||
$excluded_cat_ids = [12, 31]; // Séance de séminaire, Non classé
|
// Séance de séminaire, Non classé (+ Vie du labo pour les non-connectés)
|
||||||
if ( ! is_user_logged_in() ) $excluded_cat_ids[] = 9; // Vie du labo
|
$excluded_cat_ids = thalim_archive_excluded_cat_ids();
|
||||||
|
|
||||||
// Read filter query params
|
// Read filter query params
|
||||||
$active_axe = isset($_GET['axe']) ? intval($_GET['axe']) : 0;
|
$f = thalim_archive_read_filters();
|
||||||
$active_date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : '';
|
$active_axe = $f['axe'];
|
||||||
$active_date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : '';
|
$active_date_from = $f['date_from'];
|
||||||
$active_cat_id = isset($_GET['filter_cat']) ? intval($_GET['filter_cat']) : 0;
|
$active_date_to = $f['date_to'];
|
||||||
$filter_autres = isset($_GET['filter_autres']) ? 1 : 0;
|
$active_cat_id = $f['cat_id'];
|
||||||
|
$filter_autres = $f['filter_autres'];
|
||||||
|
|
||||||
$context['active_axe'] = $active_axe;
|
$context['active_axe'] = $active_axe;
|
||||||
$context['active_date_from'] = $active_date_from;
|
$context['active_date_from'] = $active_date_from;
|
||||||
@@ -36,13 +37,7 @@ if ( $active_cat_id && ! $filter_autres ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Determine active rubrique
|
// Determine active rubrique
|
||||||
$active_rubrique_id = 0;
|
$active_rubrique_id = thalim_archive_active_rubrique($active_cat_id);
|
||||||
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;
|
|
||||||
}
|
|
||||||
$context['active_rubrique'] = $active_rubrique_id;
|
$context['active_rubrique'] = $active_rubrique_id;
|
||||||
|
|
||||||
// Base filter params preserved across all filter links
|
// Base filter params preserved across all filter links
|
||||||
@@ -76,7 +71,6 @@ $query_args = [
|
|||||||
'posts_per_page' => 12,
|
'posts_per_page' => 12,
|
||||||
'orderby' => 'date',
|
'orderby' => 'date',
|
||||||
'order' => 'DESC',
|
'order' => 'DESC',
|
||||||
'lang' => '',
|
|
||||||
'tax_query' => $tax_query,
|
'tax_query' => $tax_query,
|
||||||
'thalim_event_date_order' => true,
|
'thalim_event_date_order' => true,
|
||||||
];
|
];
|
||||||
@@ -100,61 +94,20 @@ $context['filter_axes'] = $current_axes;
|
|||||||
$page_url = get_permalink();
|
$page_url = get_permalink();
|
||||||
$all_cats = get_categories(['taxonomy' => 'category', 'hide_empty' => false, 'exclude' => $excluded_cat_ids]);
|
$all_cats = get_categories(['taxonomy' => 'category', 'hide_empty' => false, 'exclude' => $excluded_cat_ids]);
|
||||||
|
|
||||||
$filter_parents = [];
|
// Liens de filtre : navigation vers la page de catégorie, en conservant axe/dates
|
||||||
foreach ($all_cats as $cat) {
|
$make_cat_link = function ($cat) use ($base_filter_params) {
|
||||||
if ($cat->parent == 0) {
|
return $base_filter_params
|
||||||
$filter_parents[] = [
|
? add_query_arg($base_filter_params, get_category_link($cat->term_id))
|
||||||
'id' => $cat->term_id,
|
: get_category_link($cat->term_id);
|
||||||
'name' => thalim_cat_name($cat),
|
};
|
||||||
'slug' => $cat->slug,
|
$context['filter_parents'] = thalim_archive_filter_parents($all_cats, $make_cat_link);
|
||||||
'link' => $base_filter_params
|
|
||||||
? add_query_arg($base_filter_params, get_category_link($cat->term_id))
|
$filter_categories = thalim_archive_filter_children($all_cats, $active_rubrique_id, $make_cat_link);
|
||||||
: get_category_link($cat->term_id),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$context['filter_parents'] = $filter_parents;
|
|
||||||
|
|
||||||
$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
|
// Add "Autres" entry if active rubrique has posts directly assigned to it
|
||||||
if ($active_rubrique_id && !empty($filter_categories)) {
|
if ($active_rubrique_id && !empty($filter_categories) && thalim_rubrique_has_direct_posts($active_rubrique_id)) {
|
||||||
$lang = thalim_current_language();
|
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $active_rubrique_id, 'filter_autres' => 1]));
|
||||||
$direct_check = new WP_Query([
|
$filter_categories[] = thalim_archive_autres_entry(add_query_arg($params, $page_url));
|
||||||
'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),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$context['filter_categories'] = $filter_categories;
|
$context['filter_categories'] = $filter_categories;
|
||||||
|
|
||||||
|
|||||||
@@ -91,18 +91,19 @@ unset( $group );
|
|||||||
$context['axes_groups'] = array_values( $axes_map );
|
$context['axes_groups'] = array_values( $axes_map );
|
||||||
|
|
||||||
// ── Body (English override) ──────────────────────────────────
|
// ── 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 ────────────────────────────────────────────
|
// ── 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_en', true )
|
||||||
: ( get_post_meta( $page_id, 'partenaires_internationaux', 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 ) )
|
$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_en', true )
|
||||||
: ( get_post_meta( $page_id, 'partenaires_nationaux', true ) ?: '' ) );
|
: ( get_post_meta( $page_id, 'partenaires_nationaux', true ) ?: '' ) ) );
|
||||||
$context['bibliotheques'] = wpautop( ( $labo_lang === 'en' && get_post_meta( $page_id, 'bibliotheques_en', 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_en', true )
|
||||||
: ( get_post_meta( $page_id, 'bibliotheques', true ) ?: '' ) );
|
: ( get_post_meta( $page_id, 'bibliotheques', true ) ?: '' ) ) );
|
||||||
|
|
||||||
// ── Edit link ─────────────────────────────────────────────────
|
// ── Edit link ─────────────────────────────────────────────────
|
||||||
$context['page_edit_link'] = current_user_can( 'edit_page', $page_id ) ? get_edit_post_link( $page_id ) : '';
|
$context['page_edit_link'] = current_user_can( 'edit_page', $page_id ) ? get_edit_post_link( $page_id ) : '';
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ foreach ( $terms as $term ) {
|
|||||||
|
|
||||||
$item = [
|
$item = [
|
||||||
'name' => $term->name,
|
'name' => $term->name,
|
||||||
'description' => wpautop( $term->description ),
|
'description' => wpautop( wp_kses_post( $term->description ) ),
|
||||||
'url' => get_term_link( $term ),
|
'url' => get_term_link( $term ),
|
||||||
'annee_fin' => (int) get_term_meta( $term->term_id, 'annee_fin', true ),
|
'annee_fin' => (int) get_term_meta( $term->term_id, 'annee_fin', true ),
|
||||||
];
|
];
|
||||||
|
|||||||
26
phpcs.xml.dist
Normal file
26
phpcs.xml.dist
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<ruleset name="THALIM Theme">
|
||||||
|
<description>WordPress Coding Standards pour le thème THALIM.</description>
|
||||||
|
|
||||||
|
<!-- Installation (une fois, nécessite Composer) :
|
||||||
|
composer global require wp-coding-standards/wpcs dealerdirect/phpcodesniffer-composer-installer
|
||||||
|
Exécution depuis ce dossier : phpcs -->
|
||||||
|
|
||||||
|
<file>.</file>
|
||||||
|
<exclude-pattern>vendor/*</exclude-pattern>
|
||||||
|
<exclude-pattern>node_modules/*</exclude-pattern>
|
||||||
|
<exclude-pattern>assets/vendor/*</exclude-pattern>
|
||||||
|
|
||||||
|
<arg name="extensions" value="php"/>
|
||||||
|
<arg value="sp"/>
|
||||||
|
|
||||||
|
<rule ref="WordPress-Core">
|
||||||
|
<!-- Le code existant utilise la syntaxe courte de tableaux et
|
||||||
|
l'indentation 4 espaces — on ne reformate pas tout le thème. -->
|
||||||
|
<exclude name="Universal.Arrays.DisallowShortArraySyntax"/>
|
||||||
|
<exclude name="WordPress.WhiteSpace.PrecisionAlignment"/>
|
||||||
|
<exclude name="Generic.WhiteSpace.DisallowSpaceIndent"/>
|
||||||
|
</rule>
|
||||||
|
<rule ref="WordPress.Security"/>
|
||||||
|
<rule ref="WordPress.DB.PreparedSQL"/>
|
||||||
|
</ruleset>
|
||||||
102
search.php
102
search.php
@@ -1,18 +1,18 @@
|
|||||||
<?php
|
<?php
|
||||||
$context = Timber::context();
|
$context = Timber::context();
|
||||||
|
|
||||||
// Séances de séminaire (cat 12) are included: post-card-helpers rewrites their
|
// Les séances de séminaire sont incluses : post-card-helpers réécrit leur
|
||||||
// link to the parent séminaire + #seance-{ID} hash.
|
// lien vers le séminaire parent + ancre #seance-{ID}.
|
||||||
$excluded_cat_ids = [31]; // Non classé
|
$excluded_cat_ids = thalim_archive_excluded_cat_ids( false ); // Non classé (+ Vie du labo non connectés)
|
||||||
if ( ! is_user_logged_in() ) $excluded_cat_ids[] = 9; // Vie du labo
|
|
||||||
$search_query = get_search_query();
|
$search_query = get_search_query();
|
||||||
|
|
||||||
// Read filter query params
|
// Read filter query params
|
||||||
$active_axe = isset($_GET['axe']) ? intval($_GET['axe']) : 0;
|
$f = thalim_archive_read_filters();
|
||||||
$active_date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : '';
|
$active_axe = $f['axe'];
|
||||||
$active_date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : '';
|
$active_date_from = $f['date_from'];
|
||||||
$active_cat_id = isset($_GET['filter_cat']) ? intval($_GET['filter_cat']) : 0;
|
$active_date_to = $f['date_to'];
|
||||||
$filter_autres = isset($_GET['filter_autres']) ? 1 : 0;
|
$active_cat_id = $f['cat_id'];
|
||||||
|
$filter_autres = $f['filter_autres'];
|
||||||
|
|
||||||
$context['search_query'] = $search_query;
|
$context['search_query'] = $search_query;
|
||||||
$context['active_axe'] = $active_axe;
|
$context['active_axe'] = $active_axe;
|
||||||
@@ -23,13 +23,7 @@ $context['active_cat_id'] = $active_cat_id;
|
|||||||
$context['filter_autres'] = $filter_autres;
|
$context['filter_autres'] = $filter_autres;
|
||||||
|
|
||||||
// Determine active rubrique
|
// Determine active rubrique
|
||||||
$active_rubrique_id = 0;
|
$active_rubrique_id = thalim_archive_active_rubrique($active_cat_id);
|
||||||
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;
|
|
||||||
}
|
|
||||||
$context['active_rubrique'] = $active_rubrique_id;
|
$context['active_rubrique'] = $active_rubrique_id;
|
||||||
|
|
||||||
// Base URL for search filter links (language-aware)
|
// Base URL for search filter links (language-aware)
|
||||||
@@ -72,7 +66,6 @@ $query_args = [
|
|||||||
'posts_per_page' => 12,
|
'posts_per_page' => 12,
|
||||||
'orderby' => 'relevance',
|
'orderby' => 'relevance',
|
||||||
'order' => 'DESC',
|
'order' => 'DESC',
|
||||||
'lang' => '',
|
|
||||||
'tax_query' => $tax_query,
|
'tax_query' => $tax_query,
|
||||||
];
|
];
|
||||||
if ($active_axe) {
|
if ($active_axe) {
|
||||||
@@ -98,59 +91,19 @@ $context['axe_stay_on_page'] = true;
|
|||||||
// Rubrique/catégorie filter links (all preserve search term)
|
// Rubrique/catégorie filter links (all preserve search term)
|
||||||
$all_cats = get_categories(['taxonomy' => 'category', 'hide_empty' => false, 'exclude' => $excluded_cat_ids]);
|
$all_cats = get_categories(['taxonomy' => 'category', 'hide_empty' => false, 'exclude' => $excluded_cat_ids]);
|
||||||
|
|
||||||
$filter_parents = [];
|
// Liens de filtre : on reste sur la recherche avec un paramètre filter_cat
|
||||||
foreach ($all_cats as $cat) {
|
$make_filter_link = function ($cat) use ($base_filter_params, $search_base) {
|
||||||
if ($cat->parent == 0) {
|
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id]));
|
||||||
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id]));
|
return add_query_arg($params, $search_base);
|
||||||
$filter_parents[] = [
|
};
|
||||||
'id' => $cat->term_id,
|
$context['filter_parents'] = thalim_archive_filter_parents($all_cats, $make_filter_link);
|
||||||
'name' => thalim_cat_name($cat),
|
|
||||||
'slug' => $cat->slug,
|
$filter_categories = thalim_archive_filter_children($all_cats, $active_rubrique_id, $make_filter_link);
|
||||||
'link' => add_query_arg($params, $search_base),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$context['filter_parents'] = $filter_parents;
|
|
||||||
|
|
||||||
$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
|
// Add "Autres" entry if active rubrique has posts directly assigned to it
|
||||||
if ($active_rubrique_id && !empty($filter_categories)) {
|
if ($active_rubrique_id && !empty($filter_categories) && thalim_rubrique_has_direct_posts($active_rubrique_id)) {
|
||||||
$lang = thalim_current_language();
|
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $active_rubrique_id, 'filter_autres' => 1]));
|
||||||
$direct_check = new WP_Query([
|
$filter_categories[] = thalim_archive_autres_entry(add_query_arg($params, $search_base));
|
||||||
'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),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$context['filter_categories'] = $filter_categories;
|
$context['filter_categories'] = $filter_categories;
|
||||||
|
|
||||||
@@ -161,21 +114,24 @@ $context['posts'] = $posts;
|
|||||||
// Search users (members) by display_name
|
// Search users (members) by display_name
|
||||||
$author_cards = [];
|
$author_cards = [];
|
||||||
if ( $search_query ) {
|
if ( $search_query ) {
|
||||||
$excluded_role_ids = [ 600, 598 ]; // "À ranger", "Archive"
|
$excluded_role_ids = thalim_excluded_role_ids(); // « À ranger », « Archive » (résolus par slug)
|
||||||
$user_query = new WP_User_Query([
|
$user_query_args = [
|
||||||
'search' => '*' . $search_query . '*',
|
'search' => '*' . $search_query . '*',
|
||||||
'search_columns' => ['display_name'],
|
'search_columns' => ['display_name'],
|
||||||
'number' => 6,
|
'number' => 6,
|
||||||
'orderby' => 'display_name',
|
'orderby' => 'display_name',
|
||||||
'order' => 'ASC',
|
'order' => 'ASC',
|
||||||
'meta_query' => [
|
];
|
||||||
|
if ( $excluded_role_ids ) {
|
||||||
|
$user_query_args['meta_query'] = [
|
||||||
[
|
[
|
||||||
'key' => 'role_1',
|
'key' => 'role_1',
|
||||||
'value' => $excluded_role_ids,
|
'value' => $excluded_role_ids,
|
||||||
'compare' => 'NOT IN',
|
'compare' => 'NOT IN',
|
||||||
],
|
],
|
||||||
],
|
];
|
||||||
]);
|
}
|
||||||
|
$user_query = new WP_User_Query( $user_query_args );
|
||||||
$lang = thalim_current_language();
|
$lang = thalim_current_language();
|
||||||
|
|
||||||
// Direction IDs (same source as membres page and author page)
|
// Direction IDs (same source as membres page and author page)
|
||||||
|
|||||||
100
taxonomy.php
100
taxonomy.php
@@ -10,15 +10,16 @@ $context['parent_slug'] = '';
|
|||||||
$tax_object = get_taxonomy($taxonomy);
|
$tax_object = get_taxonomy($taxonomy);
|
||||||
$context['taxonomy_label'] = $tax_object ? $tax_object->labels->singular_name : $taxonomy;
|
$context['taxonomy_label'] = $tax_object ? $tax_object->labels->singular_name : $taxonomy;
|
||||||
|
|
||||||
$excluded_ids = [12, 31]; // Séance de séminaire, Non classé
|
// Séance de séminaire, Non classé (+ Vie du labo pour les non-connectés)
|
||||||
if ( ! is_user_logged_in() ) $excluded_ids[] = 9; // Vie du labo
|
$excluded_ids = thalim_archive_excluded_cat_ids();
|
||||||
|
|
||||||
// Read filter query params
|
// Read filter query params
|
||||||
$active_axe = isset($_GET['axe']) ? intval($_GET['axe']) : 0;
|
$f = thalim_archive_read_filters();
|
||||||
$active_date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : '';
|
$active_axe = $f['axe'];
|
||||||
$active_date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : '';
|
$active_date_from = $f['date_from'];
|
||||||
$active_cat_id = isset($_GET['filter_cat']) ? intval($_GET['filter_cat']) : 0;
|
$active_date_to = $f['date_to'];
|
||||||
$filter_autres = isset($_GET['filter_autres']) ? 1 : 0;
|
$active_cat_id = $f['cat_id'];
|
||||||
|
$filter_autres = $f['filter_autres'];
|
||||||
|
|
||||||
$context['active_axe'] = $active_axe;
|
$context['active_axe'] = $active_axe;
|
||||||
$context['active_date_from'] = $active_date_from;
|
$context['active_date_from'] = $active_date_from;
|
||||||
@@ -28,13 +29,7 @@ $context['active_cat_id'] = $active_cat_id;
|
|||||||
$context['filter_autres'] = $filter_autres;
|
$context['filter_autres'] = $filter_autres;
|
||||||
|
|
||||||
// Determine active rubrique from active category (parent if subcategory, itself if top-level)
|
// Determine active rubrique from active category (parent if subcategory, itself if top-level)
|
||||||
$active_rubrique_id = 0;
|
$active_rubrique_id = thalim_archive_active_rubrique($active_cat_id);
|
||||||
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;
|
|
||||||
}
|
|
||||||
$context['active_rubrique'] = $active_rubrique_id;
|
$context['active_rubrique'] = $active_rubrique_id;
|
||||||
|
|
||||||
// Base params shared across all filter links (preserves active filters when navigating)
|
// Base params shared across all filter links (preserves active filters when navigating)
|
||||||
@@ -53,11 +48,11 @@ $tax_query = [
|
|||||||
'field' => 'term_id',
|
'field' => 'term_id',
|
||||||
'terms' => [$term->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',
|
'taxonomy' => 'category',
|
||||||
'field' => 'term_id',
|
'field' => 'term_id',
|
||||||
'terms' => [12],
|
'terms' => [ thalim_cat_id('seance') ],
|
||||||
'operator' => 'NOT IN',
|
'operator' => 'NOT IN',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
@@ -101,66 +96,28 @@ $context['axe_stay_on_page'] = !$axe_taxonomy_mode;
|
|||||||
$current_term_url = get_term_link($term);
|
$current_term_url = get_term_link($term);
|
||||||
$all_cats = get_categories(['taxonomy' => 'category', 'hide_empty' => false, 'exclude' => $excluded_ids]);
|
$all_cats = get_categories(['taxonomy' => 'category', 'hide_empty' => false, 'exclude' => $excluded_ids]);
|
||||||
|
|
||||||
$filter_parents = [];
|
// Liens de filtre : on reste sur l'URL du terme courant avec un paramètre filter_cat
|
||||||
foreach ($all_cats as $cat) {
|
$make_filter_link = function ($cat) use ($base_filter_params, $current_term_url) {
|
||||||
if ($cat->parent == 0) {
|
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id]));
|
||||||
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id]));
|
return add_query_arg($params, $current_term_url);
|
||||||
$filter_parents[] = [
|
};
|
||||||
'id' => $cat->term_id,
|
$context['filter_parents'] = thalim_archive_filter_parents($all_cats, $make_filter_link);
|
||||||
'name' => thalim_cat_name($cat),
|
|
||||||
'slug' => $cat->slug,
|
$filter_categories = thalim_archive_filter_children($all_cats, $active_rubrique_id, $make_filter_link);
|
||||||
'link' => add_query_arg($params, $current_term_url),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$context['filter_parents'] = $filter_parents;
|
|
||||||
|
|
||||||
$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
|
// 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)) {
|
if ($active_rubrique_id && !empty($filter_categories)) {
|
||||||
$lang = thalim_current_language();
|
$has_direct = thalim_rubrique_has_direct_posts($active_rubrique_id, [
|
||||||
$direct_check = new WP_Query([
|
[
|
||||||
'post_type' => 'post',
|
'taxonomy' => $taxonomy,
|
||||||
'posts_per_page' => 1,
|
'field' => 'term_id',
|
||||||
'fields' => 'ids',
|
'terms' => [$term->term_id],
|
||||||
'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,
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
if ($direct_check->have_posts()) {
|
if ($has_direct) {
|
||||||
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $active_rubrique_id, 'filter_autres' => 1]));
|
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $active_rubrique_id, 'filter_autres' => 1]));
|
||||||
$filter_categories[] = [
|
$filter_categories[] = thalim_archive_autres_entry(add_query_arg($params, $current_term_url));
|
||||||
'id' => 'autres',
|
|
||||||
'name' => $lang === 'en' ? 'Other' : 'Autres',
|
|
||||||
'slug' => 'autres',
|
|
||||||
'link' => add_query_arg($params, $current_term_url),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$context['filter_categories'] = $filter_categories;
|
$context['filter_categories'] = $filter_categories;
|
||||||
@@ -171,7 +128,6 @@ $posts = Timber::get_posts(array_merge([
|
|||||||
'posts_per_page' => 12,
|
'posts_per_page' => 12,
|
||||||
'orderby' => 'date',
|
'orderby' => 'date',
|
||||||
'order' => 'DESC',
|
'order' => 'DESC',
|
||||||
'lang' => '',
|
|
||||||
'thalim_event_date_order' => true,
|
'thalim_event_date_order' => true,
|
||||||
], $extra_query_args));
|
], $extra_query_args));
|
||||||
$context['cards'] = thalim_get_cards_data($posts);
|
$context['cards'] = thalim_get_cards_data($posts);
|
||||||
@@ -181,6 +137,6 @@ $context['posts'] = $posts;
|
|||||||
$tax_lang = thalim_current_language();
|
$tax_lang = thalim_current_language();
|
||||||
$pres_fr = get_term_meta($term->term_id, 'presentation', true) ?: '';
|
$pres_fr = get_term_meta($term->term_id, 'presentation', true) ?: '';
|
||||||
$pres_en = get_term_meta($term->term_id, 'presentation_en', 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);
|
Timber::render('taxonomy.twig', $context);
|
||||||
|
|||||||
@@ -22,13 +22,13 @@
|
|||||||
{% if author.avatar_url %}
|
{% if author.avatar_url %}
|
||||||
<div class="author-header">
|
<div class="author-header">
|
||||||
<div class="author-avatar">
|
<div class="author-avatar">
|
||||||
<img src="{{ author.avatar_url }}" alt="{{ author.display_name }}">
|
<img src="{{ author.avatar_url|esc_url }}" alt="{{ author.display_name|esc_attr }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="author-identity">
|
<div class="author-identity">
|
||||||
<h2><p>{{ author.display_name }}</p></h2>
|
<h2><p>{{ author.display_name|esc_html }}</p></h2>
|
||||||
{% if author.role_label or author.role_complement or author.affiliation %}
|
{% if author.role_label or author.role_complement or author.affiliation %}
|
||||||
<p class="author-role">
|
<p class="author-role">
|
||||||
{{ author.role_label }}{% if author.role_complement %} {{ author.role_complement }}{% if author.affiliation %},{% endif %}{% endif %}{% if author.affiliation %} {{ author.affiliation }}{% endif %}
|
{{ author.role_label|esc_html }}{% if author.role_complement %} {{ author.role_complement|esc_html }}{% if author.affiliation %},{% endif %}{% endif %}{% if author.affiliation %} {{ author.affiliation|esc_html }}{% endif %}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p class="maj">{{ current_language == 'en' ? 'Updated on' : 'Mis à jour le' }} {{ author.user_since }}</p>
|
<p class="maj">{{ current_language == 'en' ? 'Updated on' : 'Mis à jour le' }} {{ author.user_since }}</p>
|
||||||
@@ -63,10 +63,10 @@
|
|||||||
|
|
||||||
{% if not author.avatar_url %}
|
{% if not author.avatar_url %}
|
||||||
<div class="author-identity">
|
<div class="author-identity">
|
||||||
<h2><p>{{ author.display_name }}</p></h2>
|
<h2><p>{{ author.display_name|esc_html }}</p></h2>
|
||||||
{% if author.role_label or author.role_complement or author.affiliation %}
|
{% if author.role_label or author.role_complement or author.affiliation %}
|
||||||
<p class="author-role">
|
<p class="author-role">
|
||||||
{{ author.role_label }}{% if author.role_complement %} {{ author.role_complement }}{% if author.affiliation %},{% endif %}{% endif %}{% if author.affiliation %} {{ author.affiliation }}{% endif %}
|
{{ author.role_label|esc_html }}{% if author.role_complement %} {{ author.role_complement|esc_html }}{% if author.affiliation %},{% endif %}{% endif %}{% if author.affiliation %} {{ author.affiliation|esc_html }}{% endif %}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p class="maj">{{ current_language == 'en' ? 'Updated on' : 'Mis à jour le' }} {{ author.user_since }}</p>
|
<p class="maj">{{ current_language == 'en' ? 'Updated on' : 'Mis à jour le' }} {{ author.user_since }}</p>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li data-role="">{{ current_language == 'en' ? 'All statuses' : 'Tous les statuts' }}</li>
|
<li data-role="">{{ current_language == 'en' ? 'All statuses' : 'Tous les statuts' }}</li>
|
||||||
{% for role in filter_roles %}
|
{% for role in filter_roles %}
|
||||||
<li data-role="{{ role.name }}">{{ role.name }}</li>
|
<li data-role="{{ role.name|esc_attr }}">{{ role.name|esc_html }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -81,18 +81,20 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for member in group.members %}
|
{% for member in group.members %}
|
||||||
<tr onclick="window.location.href='{{ member.url }}'"
|
{# data-url + listener délégué (membresFilters.js) au lieu d'un onclick inline ;
|
||||||
data-name="{{ member.display_name }}"
|
tout passe par esc_attr/esc_html : ces valeurs viennent des profils utilisateurs #}
|
||||||
data-sort-name="{{ member.sort_key }}"
|
<tr data-url="{{ member.url|esc_url }}"
|
||||||
data-roles="{{ member.role_names|join('|') }}"
|
data-name="{{ member.display_name|esc_attr }}"
|
||||||
data-avatar="{{ member.avatar_url }}"
|
data-sort-name="{{ member.sort_key|esc_attr }}"
|
||||||
data-status="{{ member.status }}"
|
data-roles="{{ member.role_names|join('|')|esc_attr }}"
|
||||||
data-affiliation="{{ member.affiliation }}"
|
data-avatar="{{ member.avatar_url|esc_url }}"
|
||||||
data-domaines="{{ member.domaines|join(', ') }}"
|
data-status="{{ member.status|esc_attr }}"
|
||||||
data-autres-domaines="{{ member.autres_domaines }}">
|
data-affiliation="{{ member.affiliation|esc_attr }}"
|
||||||
<td>{{ member.display_name }}</td>
|
data-domaines="{{ member.domaines|join(', ')|esc_attr }}"
|
||||||
<td>{{ member.status }}</td>
|
data-autres-domaines="{{ member.autres_domaines|esc_attr }}">
|
||||||
<td>{{ member.affiliation }}</td>
|
<td>{{ member.display_name|esc_html }}</td>
|
||||||
|
<td>{{ member.status|esc_html }}</td>
|
||||||
|
<td>{{ member.affiliation|esc_html }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -20,6 +20,6 @@
|
|||||||
{% if type_label %}<span>{{ type_label }}</span>{% endif %}
|
{% if type_label %}<span>{{ type_label }}</span>{% endif %}
|
||||||
{% if lieu %}<span>{{ lieu }}</span>{% endif %}
|
{% if lieu %}<span>{{ lieu }}</span>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<p class="agenda-card__title">{{ post.title }}</p>
|
<p class="agenda-card__title">{{ post.title|bilingual }}</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
<article class="author-card">
|
<article class="author-card">
|
||||||
<a href="{{ author.url }}" class="author-card__visual" tabindex="-1" aria-hidden="true">
|
<a href="{{ author.url|esc_url }}" class="author-card__visual" tabindex="-1" aria-hidden="true">
|
||||||
{% if author.avatar_url %}
|
{% if author.avatar_url %}
|
||||||
<img src="{{ author.avatar_url }}" alt="{{ author.name }}" loading="lazy">
|
<img src="{{ author.avatar_url|esc_url }}" alt="{{ author.name|esc_attr }}" loading="lazy">
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="author-card__initials">{{ author.initials }}</span>
|
<span class="author-card__initials">{{ author.initials|esc_html }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
<div class="author-card__info">
|
<div class="author-card__info">
|
||||||
<h2 class="author-card__name"><a href="{{ author.url }}">{{ author.name }}</a></h2>
|
<h2 class="author-card__name"><a href="{{ author.url|esc_url }}">{{ author.name|esc_html }}</a></h2>
|
||||||
{% if author.role_label %}
|
{% if author.role_label %}
|
||||||
<p class="author-card__role">{{ author.role_label }}</p>
|
<p class="author-card__role">{{ author.role_label|esc_html }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if author.affiliation %}
|
{% if author.affiliation %}
|
||||||
<p class="author-card__affiliation">{{ author.affiliation }}</p>
|
<p class="author-card__affiliation">{{ author.affiliation|esc_html }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -40,77 +40,3 @@
|
|||||||
</h2>
|
</h2>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</article>
|
</article>
|
||||||
{#
|
|
||||||
<article class="post-card">
|
|
||||||
{% if card.card_image %}
|
|
||||||
<div class="post-card__image">
|
|
||||||
<img src="{{ card.card_image }}" alt="{{ post.title }}" loading="lazy">
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="post-card__content">
|
|
||||||
<h3 class="post-card__title">
|
|
||||||
<a href="{{ post.link }}">{{ post.title }}</a>
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
{% if post.meta('sous-titre') %}
|
|
||||||
<p class="post-card__subtitle">{{ post.meta('sous-titre') }}</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<time class="post-card__date" datetime="{{ post.date('Y-m-d') }}">{{ post.date('d/m/Y') }}</time>
|
|
||||||
|
|
||||||
{% if card.card_membres is not empty or post.meta('autrepersonnes') %}
|
|
||||||
<div class="post-card__authors">
|
|
||||||
{% for name in card.card_membres %}
|
|
||||||
<span class="post-card__author">{{ name }}</span>
|
|
||||||
{% endfor %}
|
|
||||||
{% if post.meta('autrepersonnes') %}
|
|
||||||
<span class="post-card__author post-card__author--external">{{ post.meta('autrepersonnes') }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if post.meta('fonction_auteur') %}
|
|
||||||
<span class="post-card__role">{{ post.meta('fonction_auteur') }}</span>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if post.meta('editeur') %}
|
|
||||||
<span class="post-card__publisher">{{ post.meta('editeur') }}</span>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if post.meta('journal') %}
|
|
||||||
<span class="post-card__journal">{{ post.meta('journal') }}</span>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if card.card_axes is not empty %}
|
|
||||||
<div class="post-card__axes">
|
|
||||||
{% for axe in card.card_axes %}
|
|
||||||
<span class="post-card__axe">{{ axe }}</span>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if card.card_etiquettes is not empty %}
|
|
||||||
<div class="post-card__tags">
|
|
||||||
{% for tag in card.card_etiquettes %}
|
|
||||||
<span class="post-card__tag">{{ tag }}</span>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="post-card__links">
|
|
||||||
{% if post.meta('lien_externe_1') %}
|
|
||||||
<a href="{{ post.meta('lien_externe_1') }}" class="post-card__link" target="_blank" rel="noopener">
|
|
||||||
{{ post.meta('titre_du_lien_externe_1') ?: post.meta('lien_externe_1') }}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if post.meta('hal_url') %}
|
|
||||||
<a href="{{ post.meta('hal_url') }}" class="post-card__link post-card__link--hal" target="_blank" rel="noopener">
|
|
||||||
HAL
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
#}
|
|
||||||
@@ -13,9 +13,7 @@
|
|||||||
{% if article.category_name and article.category_name != article.parent_name %}
|
{% if article.category_name and article.category_name != article.parent_name %}
|
||||||
<span class="breadcrumb__separator">→</span>
|
<span class="breadcrumb__separator">→</span>
|
||||||
<a class="breadcrumb__cat" href="{{ article.category_link }}">{{ article.category_name }}</a>
|
<a class="breadcrumb__cat" href="{{ article.category_link }}">{{ article.category_name }}</a>
|
||||||
{% endif %}{#
|
{% endif %}
|
||||||
<span class="breadcrumb__separator">→</span>
|
|
||||||
<span class="breadcrumb__current">{{ post.title }}</span> #}
|
|
||||||
</nav>
|
</nav>
|
||||||
{% if post.edit_link %}
|
{% if post.edit_link %}
|
||||||
<a href="{{ post.edit_link }}" class="link-button" target="_blank" rel="noopener">
|
<a href="{{ post.edit_link }}" class="link-button" target="_blank" rel="noopener">
|
||||||
|
|||||||
100
tests/run-tests.php
Normal file
100
tests/run-tests.php
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Tests du thème THALIM — fonctions pures à fort enjeu.
|
||||||
|
*
|
||||||
|
* Exécution (environnement Docker de dev) :
|
||||||
|
* docker exec wordpress php /var/www/html/wp-content/themes/thalim/tests/run-tests.php
|
||||||
|
*
|
||||||
|
* Pas de PHPUnit (pas de pipeline) : assertions maison, sortie lisible,
|
||||||
|
* code retour ≠ 0 en cas d'échec. Charge WordPress (wp-load) car les
|
||||||
|
* fonctions testées vivent dans functions.php/inc/*.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (PHP_SAPI !== 'cli') {
|
||||||
|
exit("CLI only\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SERVER['HTTP_HOST'] = $_SERVER['HTTP_HOST'] ?? 'localhost';
|
||||||
|
$_SERVER['REQUEST_URI'] = $_SERVER['REQUEST_URI'] ?? '/';
|
||||||
|
|
||||||
|
require dirname(__DIR__, 4) . '/wp-load.php';
|
||||||
|
|
||||||
|
$failures = 0;
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
function check(string $name, $actual, $expected): void {
|
||||||
|
global $failures, $count;
|
||||||
|
$count++;
|
||||||
|
if ($actual === $expected) {
|
||||||
|
echo " ok $name\n";
|
||||||
|
} else {
|
||||||
|
$failures++;
|
||||||
|
echo " FAIL $name\n attendu: " . var_export($expected, true)
|
||||||
|
. "\n obtenu : " . var_export($actual, true) . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "== thalim_bilingual ==\n";
|
||||||
|
check('FR par défaut', thalim_bilingual('Bonjour // Hello'), 'Bonjour');
|
||||||
|
check('EN explicite', thalim_bilingual('Bonjour // Hello', 'en'), 'Hello');
|
||||||
|
check('FR explicite', thalim_bilingual('Bonjour // Hello', 'fr'), 'Bonjour');
|
||||||
|
check('sans séparateur', thalim_bilingual('Bonjour', 'en'), 'Bonjour');
|
||||||
|
check('EN vide → FR', thalim_bilingual('Bonjour // ', 'en'), 'Bonjour');
|
||||||
|
check('espaces normalisés', thalim_bilingual(' Bonjour // Hello ', 'en'), 'Hello');
|
||||||
|
check('chaîne vide', thalim_bilingual('', 'en'), '');
|
||||||
|
check('séparateur multiple', thalim_bilingual('A // B // C', 'en'), 'B // C');
|
||||||
|
|
||||||
|
echo "== thalim_en_url ==\n";
|
||||||
|
$GLOBALS['thalim_lang_override'] = 'fr';
|
||||||
|
check('no-op en FR', thalim_en_url(home_url('/membres/')), home_url('/membres/'));
|
||||||
|
$GLOBALS['thalim_lang_override'] = 'en';
|
||||||
|
$home = rtrim(home_url(), '/');
|
||||||
|
check('préfixe /en', thalim_en_url($home . '/membres/'), $home . '/en/membres/');
|
||||||
|
check('idempotente', thalim_en_url($home . '/en/membres/'), $home . '/en/membres/');
|
||||||
|
unset($GLOBALS['thalim_lang_override']);
|
||||||
|
|
||||||
|
echo "== thalim_cat_name ==\n";
|
||||||
|
$articles = get_term_by('slug', 'articles', 'category');
|
||||||
|
check('FR = nom natif', thalim_cat_name($articles, 'fr'), $articles->name);
|
||||||
|
$en_meta = get_term_meta($articles->term_id, 'titre_anglais', true);
|
||||||
|
check('EN = titre_anglais ou fallback', thalim_cat_name($articles, 'en'), $en_meta ?: $articles->name);
|
||||||
|
check('valeur non-term', thalim_cat_name('Texte brut'), 'Texte brut');
|
||||||
|
|
||||||
|
echo "== thalim_format_date ==\n";
|
||||||
|
check('date vide', thalim_format_date(''), '');
|
||||||
|
check('0000-00-00', thalim_format_date('0000-00-00 00:00:00'), '');
|
||||||
|
check('format Y', thalim_format_date('2026-03-15 00:00:00', 'fr', 'Y'), '2026');
|
||||||
|
check('format Y-m-d', thalim_format_date('2026-03-15 10:30:00', 'fr', 'Y-m-d'), '2026-03-15');
|
||||||
|
|
||||||
|
echo "== config (résolution par slug) ==\n";
|
||||||
|
check('cat seance', thalim_cat_id('seance'), 12);
|
||||||
|
check('cat non-classe', thalim_cat_id('non-classe'), 31);
|
||||||
|
check('cat vie-du-labo', thalim_cat_id('vie-du-labo'), 9);
|
||||||
|
check('cat publications', thalim_cat_id('publications'), 4);
|
||||||
|
check('cat message-labo', thalim_cat_id('message-labo'), 268);
|
||||||
|
check('clé inconnue', thalim_cat_id('nexiste-pas'), 0);
|
||||||
|
check('slug inexistant', thalim_term_id_by_slug('slug-bidon-xyz'), 0);
|
||||||
|
check('rôles exclus', thalim_excluded_role_ids(), [600, 598]);
|
||||||
|
|
||||||
|
echo "== thalim_get_active_pinned_ids ==\n";
|
||||||
|
check('catégorie 0 → []', thalim_get_active_pinned_ids(0), []);
|
||||||
|
|
||||||
|
echo "== thalim_get_seance_parent_id ==\n";
|
||||||
|
global $wpdb;
|
||||||
|
$seance_id = (int) $wpdb->get_var(
|
||||||
|
"SELECT tr.object_id FROM {$wpdb->term_relationships} tr
|
||||||
|
JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id
|
||||||
|
JOIN {$wpdb->posts} p ON p.ID = tr.object_id
|
||||||
|
WHERE tt.term_id = 12 AND p.post_status = 'publish' LIMIT 1"
|
||||||
|
);
|
||||||
|
if ($seance_id) {
|
||||||
|
$parent = thalim_get_seance_parent_id($seance_id);
|
||||||
|
check('séance a un parent publié', $parent > 0, true);
|
||||||
|
check('lien = parent + ancre', thalim_get_seance_link($seance_id), get_permalink($parent) . '#seance-' . $seance_id);
|
||||||
|
} else {
|
||||||
|
echo " skip aucune séance publiée en base\n";
|
||||||
|
}
|
||||||
|
check('séance inexistante → 0', thalim_get_seance_parent_id(999999999), 0);
|
||||||
|
|
||||||
|
echo "\n$count tests, $failures échec(s)\n";
|
||||||
|
exit($failures ? 1 : 0);
|
||||||
Reference in New Issue
Block a user