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 ---
|
||||
vendor/
|
||||
/vendor/
|
||||
|
||||
# --- Artefacts SASS ---
|
||||
.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['cards'] = [];
|
||||
|
||||
$excluded_ids = [12, 31]; // Séance de séminaire, Non classé
|
||||
if ( ! is_user_logged_in() ) $excluded_ids[] = 9; // Vie du labo
|
||||
// Séance de séminaire, Non classé (+ Vie du labo pour les non-connectés)
|
||||
$excluded_ids = thalim_archive_excluded_cat_ids();
|
||||
|
||||
// Parent category slug for color theming
|
||||
if ($category->parent) {
|
||||
@@ -54,68 +54,25 @@ $all_cats = get_categories([
|
||||
'exclude' => $excluded_ids,
|
||||
]);
|
||||
|
||||
$filter_parents = [];
|
||||
foreach ($all_cats as $cat) {
|
||||
if ($cat->parent == 0) {
|
||||
$link = get_category_link($cat->term_id);
|
||||
if ($filter_query) $link .= '?' . $filter_query;
|
||||
$filter_parents[] = [
|
||||
'id' => $cat->term_id,
|
||||
'name' => thalim_cat_name($cat),
|
||||
'slug' => $cat->slug,
|
||||
'link' => $link,
|
||||
];
|
||||
}
|
||||
}
|
||||
$context['filter_parents'] = $filter_parents;
|
||||
// Liens de filtre : navigation vers la page de catégorie, en conservant axe/dates
|
||||
$make_cat_link = function ($cat) use ($filter_query) {
|
||||
$link = get_category_link($cat->term_id);
|
||||
return $filter_query ? $link . '?' . $filter_query : $link;
|
||||
};
|
||||
$context['filter_parents'] = thalim_archive_filter_parents($all_cats, $make_cat_link);
|
||||
|
||||
// Children of active rubrique for catégorie filter (with links)
|
||||
$active_rubrique_id = $context['active_rubrique'];
|
||||
$is_direct = (bool) get_query_var('thalim_direct_posts');
|
||||
$lang = thalim_current_language();
|
||||
|
||||
$filter_categories = [];
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
$filter_categories = thalim_archive_filter_children($all_cats, $active_rubrique_id, $make_cat_link);
|
||||
|
||||
// Add "Autres" entry if the active rubrique has posts directly assigned to it
|
||||
if ($is_direct) {
|
||||
$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();
|
||||
}
|
||||
$has_direct_posts = $is_direct ?: thalim_rubrique_has_direct_posts($active_rubrique_id);
|
||||
if ($has_direct_posts && !empty($filter_categories)) {
|
||||
$autres_link = trailingslashit(get_category_link($active_rubrique_id)) . 'autres/';
|
||||
if ($filter_query) $autres_link .= '?' . $filter_query;
|
||||
$filter_categories[] = [
|
||||
'id' => 'autres',
|
||||
'name' => $lang === 'en' ? 'Other' : 'Autres',
|
||||
'slug' => 'autres',
|
||||
'link' => $autres_link,
|
||||
];
|
||||
$filter_categories[] = thalim_archive_autres_entry($autres_link);
|
||||
}
|
||||
|
||||
$context['filter_categories'] = $filter_categories;
|
||||
@@ -135,23 +92,23 @@ $children = get_categories([
|
||||
'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).
|
||||
$subcategory_order = [
|
||||
// Publications et productions (parent: 4)
|
||||
15 => 0, // Ouvrages
|
||||
16 => 1, // Articles
|
||||
65 => 2, // Revues et collections
|
||||
17 => 3, // Multimédia
|
||||
// Activités (parent: 3)
|
||||
11 => 0, // Séminaires
|
||||
10 => 1, // Colloques et journées d'études
|
||||
13 => 2, // Communications
|
||||
14 => 3, // Soutenances
|
||||
// Publications et productions
|
||||
'ouvrages' => 0,
|
||||
'articles' => 1,
|
||||
'revues-et-collections' => 2,
|
||||
'multimedia' => 3,
|
||||
// Manifestations scientifiques
|
||||
'seminaires' => 0,
|
||||
'colloques-et-journees-detudes' => 1,
|
||||
'communications' => 2,
|
||||
'soutenances' => 3,
|
||||
];
|
||||
usort($children, function($a, $b) use ($subcategory_order) {
|
||||
$pos_a = $subcategory_order[$a->term_id] ?? 999;
|
||||
$pos_b = $subcategory_order[$b->term_id] ?? 999;
|
||||
$pos_a = $subcategory_order[$a->slug] ?? 999;
|
||||
$pos_b = $subcategory_order[$b->slug] ?? 999;
|
||||
return $pos_a - $pos_b;
|
||||
});
|
||||
|
||||
@@ -188,7 +145,6 @@ if (!$is_direct && !empty($children)) {
|
||||
'posts_per_page' => 3,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'lang' => '',
|
||||
'thalim_event_date_order' => true,
|
||||
], $extra_query_args);
|
||||
$posts = $sort_with_pinned( Timber::get_posts($query_args) );
|
||||
@@ -212,7 +168,6 @@ if (!$is_direct && !empty($children)) {
|
||||
'posts_per_page' => 3,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'lang' => '',
|
||||
'thalim_event_date_order' => true,
|
||||
], $extra_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,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'lang' => '',
|
||||
'thalim_event_date_order' => true,
|
||||
], $extra_query_args);
|
||||
if ( $pinned_ids ) {
|
||||
@@ -259,7 +213,6 @@ if (!$is_direct && !empty($children)) {
|
||||
'post__in' => $pinned_ids,
|
||||
'orderby' => 'post__in',
|
||||
'posts_per_page' => -1,
|
||||
'lang' => '',
|
||||
], $extra_query_args ) ) : [];
|
||||
|
||||
$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();
|
||||
$pres_fr = get_term_meta( $category->term_id, 'presentation', 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);
|
||||
|
||||
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)
|
||||
: $v;
|
||||
})(),
|
||||
'bio' => wpautop( make_clickable( get_user_meta($user_id, 'biographie', true) ?: '' ) ),
|
||||
'bio_en' => wpautop( make_clickable( get_user_meta($user_id, 'biographie_en', true) ?: '' ) ),
|
||||
// wp_kses_post: ces champs sont éditables par les contributeurs (profil)
|
||||
// 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' => wpautop( make_clickable( 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) ?: '' ) ),
|
||||
'recherches' => wpautop( get_user_meta($user_id, 'recherches_en_cours', true) ?: '' ),
|
||||
'recherches_en' => wpautop( get_user_meta($user_id, 'recherches_en_cours_en', true) ?: '' ),
|
||||
'domaines' => wpautop( make_clickable( wp_kses_post( get_user_meta($user_id, 'autres_domaines_de_recherches', true) ?: '' ) ) ),
|
||||
'domaines_en' => wpautop( make_clickable( wp_kses_post( get_user_meta($user_id, 'autres_domaines_de_recherches_en', true) ?: '' ) ) ),
|
||||
'recherches' => wpautop( wp_kses_post( get_user_meta($user_id, 'recherches_en_cours', true) ?: '' ) ),
|
||||
'recherches_en' => wpautop( wp_kses_post( get_user_meta($user_id, 'recherches_en_cours_en', true) ?: '' ) ),
|
||||
'axes' => $axes,
|
||||
'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) ?: '',
|
||||
'directeur_thalim'=> $directeur_thalim,
|
||||
'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_en' => wpautop( get_user_meta($user_id, 'resume_de_la_these_en', true) ?: '' ),
|
||||
'resume_these' => wpautop( wp_kses_post( get_user_meta($user_id, 'resume_de_la_these', 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 : '',
|
||||
'liens_externes' => $liens_externes,
|
||||
'documents' => $documents,
|
||||
@@ -148,7 +150,8 @@ function thalim_get_author_data($user_id) {
|
||||
* Returns an array sorted by post count (descending).
|
||||
*/
|
||||
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();
|
||||
|
||||
$posts = Timber::get_posts([
|
||||
@@ -166,7 +169,6 @@ function thalim_get_author_posts_by_category($user_id) {
|
||||
],
|
||||
],
|
||||
'thalim_event_date_order' => true,
|
||||
'lang' => '',
|
||||
]);
|
||||
|
||||
$groups = [];
|
||||
@@ -205,29 +207,28 @@ function thalim_get_author_posts_by_category($user_id) {
|
||||
$groups[$cat_id]['posts'][] = $post;
|
||||
}
|
||||
|
||||
// Séances de séminaire — dedicated group. Posts in cat 12 where the member
|
||||
// is listed in `membres`/`autre_membres`. Cards use the parent séminaire
|
||||
// permalink with a #seance-{ID} hash (see thalim_get_card_data).
|
||||
// Séances de séminaire — dedicated group. Posts in the séance category
|
||||
// where the member is listed in `membres`/`autre_membres`. Cards use the
|
||||
// parent séminaire permalink with a #seance-{ID} hash (see thalim_get_card_data).
|
||||
$seances = Timber::get_posts([
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => -1,
|
||||
'category__in' => [12],
|
||||
'category__in' => [ $seance_cat ],
|
||||
'meta_query' => [
|
||||
'relation' => 'OR',
|
||||
[ 'key' => 'membres', 'value' => $user_id ],
|
||||
[ 'key' => 'autre_membres', 'value' => $user_id ],
|
||||
],
|
||||
'thalim_event_date_order' => true,
|
||||
'lang' => '',
|
||||
]);
|
||||
if (count($seances) > 0) {
|
||||
$seance_cat = get_term(12, 'category');
|
||||
$groups[12] = [
|
||||
'cat_id' => 12,
|
||||
'cat_name' => $seance_cat && !is_wp_error($seance_cat)
|
||||
? thalim_cat_name($seance_cat, $lang)
|
||||
$seance_term = get_term($seance_cat, 'category');
|
||||
$groups[$seance_cat] = [
|
||||
'cat_id' => $seance_cat,
|
||||
'cat_name' => $seance_term && !is_wp_error($seance_term)
|
||||
? thalim_cat_name($seance_term, $lang)
|
||||
: ($lang === 'en' ? 'Seminar sessions' : 'Séances de séminaire'),
|
||||
'cat_url' => get_category_link(12),
|
||||
'cat_url' => get_category_link($seance_cat),
|
||||
'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)
|
||||
$member_cache = [];
|
||||
$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 ) {
|
||||
if ( ! isset( $member_cache[ $user->ID ] ) ) {
|
||||
$member_cache[ $user->ID ] = thalim_build_membre_data( $user );
|
||||
|
||||
@@ -11,8 +11,6 @@ function thalim_get_card_data($post_id) {
|
||||
$data = [
|
||||
'card_image' => null,
|
||||
'card_membres' => [],
|
||||
'card_axes' => [],
|
||||
'card_etiquettes' => [],
|
||||
'parent_slug' => '',
|
||||
'card_category_name' => '',
|
||||
'card_category_url' => '',
|
||||
@@ -23,12 +21,13 @@ function thalim_get_card_data($post_id) {
|
||||
];
|
||||
|
||||
// Category-based date formatting:
|
||||
// - Séminaire (cat 11, not cat 12): "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)
|
||||
// - Séminaire (hors séances): "Du X au Y" from first/last linked séance dates
|
||||
// - 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'))
|
||||
$seance_cat = thalim_cat_id('seance');
|
||||
$cat_ids = wp_get_post_categories($post_id);
|
||||
$is_seminaire = in_array(11, $cat_ids, true) && !in_array(12, $cat_ids, true);
|
||||
$is_ouvrage = in_array(15, $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(thalim_cat_id('ouvrages'), $cat_ids, true);
|
||||
|
||||
if ($is_seminaire) {
|
||||
// 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
|
||||
$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;
|
||||
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) {
|
||||
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
|
||||
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']) {
|
||||
$seance_cat = get_category(12);
|
||||
if ($seance_cat) {
|
||||
$data['card_category_name'] = thalim_cat_name($seance_cat);
|
||||
$data['card_category_url'] = get_category_link(12);
|
||||
$seance_term = get_category($seance_cat);
|
||||
if ($seance_term) {
|
||||
$data['card_category_name'] = thalim_cat_name($seance_term);
|
||||
$data['card_category_url'] = get_category_link($seance_cat);
|
||||
}
|
||||
}
|
||||
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) $post_id
|
||||
));
|
||||
$parent_id = thalim_get_seance_parent_id($post_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
|
||||
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;
|
||||
$ancestor_ids = get_ancestors($cat->term_id, 'category');
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build card data map for a collection of posts.
|
||||
* 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) {
|
||||
$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 = [];
|
||||
foreach ($posts as $post) {
|
||||
$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 = [
|
||||
// Text fields
|
||||
'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) ?: '',
|
||||
'journal' => get_post_meta($post_id, 'journal', true) ?: '',
|
||||
'lieu' => thalim_bilingual( get_post_meta($post_id, 'lieu', true) ?: '', $lang ),
|
||||
'adresse' => nl2br( esc_html( get_post_meta($post_id, 'adresse', true) ?: '' ) ),
|
||||
'autrepersonnes' => get_post_meta($post_id, '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)
|
||||
'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_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
|
||||
// (`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_fin'] = thalim_format_date($raw_fin, $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 ---
|
||||
$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) {
|
||||
if (in_array($cat->term_id, $excluded_ids)) continue;
|
||||
$ancestor_ids = get_ancestors($cat->term_id, 'category');
|
||||
@@ -256,7 +258,6 @@ function thalim_get_single_data($post_id) {
|
||||
'post_type' => 'post',
|
||||
'post__in' => array_map('intval', $related_ids),
|
||||
'posts_per_page' => -1,
|
||||
'lang' => '',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -272,7 +273,6 @@ function thalim_get_single_data($post_id) {
|
||||
'orderby' => 'meta_value',
|
||||
'meta_key' => 'date_de_debut',
|
||||
'order' => 'ASC',
|
||||
'lang' => '',
|
||||
'post_status' => ['publish', 'future'],
|
||||
]);
|
||||
$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 ),
|
||||
'lieu' => thalim_bilingual( get_post_meta($seance->ID, 'lieu', true) ?: '', $lang ),
|
||||
'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' => [],
|
||||
'images' => [],
|
||||
'documents' => [],
|
||||
@@ -372,7 +372,6 @@ function thalim_get_single_data($post_id) {
|
||||
'post_type' => 'post',
|
||||
'post__in' => array_map('intval', $s_related_ids),
|
||||
'posts_per_page' => -1,
|
||||
'lang' => '',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
54
index.php
54
index.php
@@ -30,7 +30,6 @@ $annonces_raw = Timber::get_posts([
|
||||
]],
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'lang' => '',
|
||||
'thalim_event_date_order' => true,
|
||||
]);
|
||||
$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,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'lang' => '',
|
||||
'tax_query' => [
|
||||
'relation' => 'AND',
|
||||
[
|
||||
'taxonomy' => 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => [4],
|
||||
'terms' => [ thalim_cat_id('publications') ],
|
||||
'operator' => 'IN',
|
||||
'include_children' => true,
|
||||
],
|
||||
[
|
||||
'taxonomy' => 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => [16],
|
||||
'terms' => [ thalim_cat_id('articles') ],
|
||||
'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_cards'] = thalim_get_cards_data($publications_raw);
|
||||
$context['publications_link'] = thalim_en_url( get_category_link(4) );
|
||||
$context['annonces_link'] = thalim_en_url( get_permalink(29100) );
|
||||
$context['publications_link'] = thalim_en_url( get_category_link( thalim_cat_id('publications') ) );
|
||||
// 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 ---
|
||||
$messages_labo = Timber::get_posts([
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => 5,
|
||||
'cat' => 268,
|
||||
'cat' => thalim_cat_id('message-labo'),
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'lang' => '',
|
||||
]);
|
||||
$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_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_en = ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'Jun.', 'Jul.', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.'];
|
||||
$agenda_type_fields = [
|
||||
@@ -123,7 +131,6 @@ $mediation_upcoming = Timber::get_posts([
|
||||
'posts_per_page' => 8,
|
||||
'category__in' => $mediation_cat_ids,
|
||||
'orderby' => ['date_clause' => 'ASC'],
|
||||
'lang' => '',
|
||||
'meta_query' => [
|
||||
'date_clause' => [
|
||||
'key' => 'date_de_debut',
|
||||
@@ -150,13 +157,12 @@ foreach ($mediation_upcoming as $mpost) {
|
||||
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([
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => 8,
|
||||
'category__in' => [12],
|
||||
'category__in' => [ thalim_cat_id('seance') ],
|
||||
'orderby' => ['date_clause' => 'ASC'],
|
||||
'lang' => '',
|
||||
'meta_query' => [
|
||||
'date_clause' => [
|
||||
'key' => 'date_de_debut',
|
||||
@@ -169,19 +175,7 @@ $seances_upcoming = Timber::get_posts([
|
||||
foreach ($seances_upcoming as $seance) {
|
||||
$raw_date = get_post_meta($seance->ID, 'date_de_debut', true);
|
||||
if (!$raw_date) continue;
|
||||
// Direct DB lookup — bypasses Polylang and other hook filters
|
||||
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);
|
||||
$link = thalim_get_seance_link($seance->ID);
|
||||
$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);
|
||||
if ($item) $agenda_items[] = $item;
|
||||
@@ -198,7 +192,6 @@ if (empty($agenda_items)) {
|
||||
'posts_per_page' => 5,
|
||||
'category__in' => $mediation_cat_ids,
|
||||
'orderby' => ['date_clause' => 'DESC'],
|
||||
'lang' => '',
|
||||
'meta_query' => [
|
||||
'date_clause' => ['key' => 'date_de_debut', 'type' => 'DATETIME'],
|
||||
],
|
||||
@@ -222,7 +215,7 @@ if (empty($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 ---
|
||||
$newsletter_cat = get_category_by_slug('newsletter');
|
||||
@@ -247,7 +240,7 @@ if ( ! $newsletter_url ) {
|
||||
);
|
||||
}
|
||||
$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/')),
|
||||
'newsletter' => $newsletter_url,
|
||||
];
|
||||
@@ -257,7 +250,6 @@ $context['has_tags'] = !empty(get_terms([
|
||||
'taxonomy' => 'post_tag',
|
||||
'hide_empty' => true,
|
||||
'number' => 1,
|
||||
'lang' => '',
|
||||
]));
|
||||
|
||||
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 () {
|
||||
// ── Row navigation (replaces inline onclick) ──────────────
|
||||
// Delegated listener: rows carry their target in data-url.
|
||||
document.querySelectorAll('.membres-table tbody').forEach(function (tbody) {
|
||||
tbody.addEventListener('click', function (e) {
|
||||
var row = e.target.closest('tr[data-url]');
|
||||
if (row && row.dataset.url) {
|
||||
window.location.href = row.dataset.url;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ── Filters toggle ────────────────────────────────────────
|
||||
var membresToggleBtn = document.getElementById('membres-filters-toggle');
|
||||
var membresFiltersEl = document.getElementById('membres-filters');
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<?php
|
||||
$context = Timber::context();
|
||||
|
||||
$excluded_cat_ids = [12, 31]; // Séance de séminaire, Non classé
|
||||
if ( ! is_user_logged_in() ) $excluded_cat_ids[] = 9; // Vie du labo
|
||||
// Séance de séminaire, Non classé (+ Vie du labo pour les non-connectés)
|
||||
$excluded_cat_ids = thalim_archive_excluded_cat_ids();
|
||||
|
||||
// Read filter query params
|
||||
$active_axe = isset($_GET['axe']) ? intval($_GET['axe']) : 0;
|
||||
$active_date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : '';
|
||||
$active_date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : '';
|
||||
$active_cat_id = isset($_GET['filter_cat']) ? intval($_GET['filter_cat']) : 0;
|
||||
$filter_autres = isset($_GET['filter_autres']) ? 1 : 0;
|
||||
$f = thalim_archive_read_filters();
|
||||
$active_axe = $f['axe'];
|
||||
$active_date_from = $f['date_from'];
|
||||
$active_date_to = $f['date_to'];
|
||||
$active_cat_id = $f['cat_id'];
|
||||
$filter_autres = $f['filter_autres'];
|
||||
|
||||
$context['active_axe'] = $active_axe;
|
||||
$context['active_date_from'] = $active_date_from;
|
||||
@@ -36,13 +37,7 @@ if ( $active_cat_id && ! $filter_autres ) {
|
||||
}
|
||||
|
||||
// Determine active rubrique
|
||||
$active_rubrique_id = 0;
|
||||
if ($active_cat_id) {
|
||||
$active_cat_obj = get_category($active_cat_id);
|
||||
$active_rubrique_id = ($active_cat_obj && $active_cat_obj->parent)
|
||||
? $active_cat_obj->parent
|
||||
: $active_cat_id;
|
||||
}
|
||||
$active_rubrique_id = thalim_archive_active_rubrique($active_cat_id);
|
||||
$context['active_rubrique'] = $active_rubrique_id;
|
||||
|
||||
// Base filter params preserved across all filter links
|
||||
@@ -76,7 +71,6 @@ $query_args = [
|
||||
'posts_per_page' => 12,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'lang' => '',
|
||||
'tax_query' => $tax_query,
|
||||
'thalim_event_date_order' => true,
|
||||
];
|
||||
@@ -100,61 +94,20 @@ $context['filter_axes'] = $current_axes;
|
||||
$page_url = get_permalink();
|
||||
$all_cats = get_categories(['taxonomy' => 'category', 'hide_empty' => false, 'exclude' => $excluded_cat_ids]);
|
||||
|
||||
$filter_parents = [];
|
||||
foreach ($all_cats as $cat) {
|
||||
if ($cat->parent == 0) {
|
||||
$filter_parents[] = [
|
||||
'id' => $cat->term_id,
|
||||
'name' => thalim_cat_name($cat),
|
||||
'slug' => $cat->slug,
|
||||
'link' => $base_filter_params
|
||||
? add_query_arg($base_filter_params, get_category_link($cat->term_id))
|
||||
: get_category_link($cat->term_id),
|
||||
];
|
||||
}
|
||||
}
|
||||
$context['filter_parents'] = $filter_parents;
|
||||
// Liens de filtre : navigation vers la page de catégorie, en conservant axe/dates
|
||||
$make_cat_link = function ($cat) use ($base_filter_params) {
|
||||
return $base_filter_params
|
||||
? add_query_arg($base_filter_params, get_category_link($cat->term_id))
|
||||
: get_category_link($cat->term_id);
|
||||
};
|
||||
$context['filter_parents'] = thalim_archive_filter_parents($all_cats, $make_cat_link);
|
||||
|
||||
$filter_categories = thalim_archive_filter_children($all_cats, $active_rubrique_id, $make_cat_link);
|
||||
|
||||
$filter_categories = [];
|
||||
if ($active_rubrique_id) {
|
||||
foreach ($all_cats as $cat) {
|
||||
if ($cat->parent == $active_rubrique_id) {
|
||||
$filter_categories[] = [
|
||||
'id' => $cat->term_id,
|
||||
'name' => thalim_cat_name($cat),
|
||||
'slug' => $cat->slug,
|
||||
'link' => $base_filter_params
|
||||
? add_query_arg($base_filter_params, get_category_link($cat->term_id))
|
||||
: get_category_link($cat->term_id),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add "Autres" entry if active rubrique has posts directly assigned to it
|
||||
if ($active_rubrique_id && !empty($filter_categories)) {
|
||||
$lang = thalim_current_language();
|
||||
$direct_check = new WP_Query([
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => 1,
|
||||
'fields' => 'ids',
|
||||
'no_found_rows' => true,
|
||||
'lang' => '',
|
||||
'tax_query' => [[
|
||||
'taxonomy' => 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => [$active_rubrique_id],
|
||||
'include_children' => false,
|
||||
]],
|
||||
]);
|
||||
if ($direct_check->have_posts()) {
|
||||
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $active_rubrique_id, 'filter_autres' => 1]));
|
||||
$filter_categories[] = [
|
||||
'id' => 'autres',
|
||||
'name' => $lang === 'en' ? 'Other' : 'Autres',
|
||||
'slug' => 'autres',
|
||||
'link' => add_query_arg($params, $page_url),
|
||||
];
|
||||
}
|
||||
if ($active_rubrique_id && !empty($filter_categories) && thalim_rubrique_has_direct_posts($active_rubrique_id)) {
|
||||
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $active_rubrique_id, 'filter_autres' => 1]));
|
||||
$filter_categories[] = thalim_archive_autres_entry(add_query_arg($params, $page_url));
|
||||
}
|
||||
$context['filter_categories'] = $filter_categories;
|
||||
|
||||
|
||||
@@ -91,18 +91,19 @@ unset( $group );
|
||||
$context['axes_groups'] = array_values( $axes_map );
|
||||
|
||||
// ── Body (English override) ──────────────────────────────────
|
||||
$context['body_en'] = apply_filters( 'the_content', get_post_meta( $page_id, 'body_en', true ) ?: '' );
|
||||
$context['body_en'] = apply_filters( 'the_content', wp_kses_post( get_post_meta( $page_id, 'body_en', true ) ?: '' ) );
|
||||
|
||||
// ── WYSIWYG fields ────────────────────────────────────────────
|
||||
$context['partenaires_internationaux'] = wpautop( ( $labo_lang === 'en' && get_post_meta( $page_id, 'partenaires_internationaux_en', true ) )
|
||||
// wp_kses_post: rendus en |raw dans le template (autoescape off)
|
||||
$context['partenaires_internationaux'] = wpautop( wp_kses_post( ( $labo_lang === 'en' && get_post_meta( $page_id, 'partenaires_internationaux_en', true ) )
|
||||
? get_post_meta( $page_id, 'partenaires_internationaux_en', true )
|
||||
: ( get_post_meta( $page_id, 'partenaires_internationaux', true ) ?: '' ) );
|
||||
$context['partenaires_nationaux'] = wpautop( ( $labo_lang === 'en' && get_post_meta( $page_id, 'partenaires_nationaux_en', true ) )
|
||||
: ( get_post_meta( $page_id, 'partenaires_internationaux', true ) ?: '' ) ) );
|
||||
$context['partenaires_nationaux'] = wpautop( wp_kses_post( ( $labo_lang === 'en' && get_post_meta( $page_id, 'partenaires_nationaux_en', true ) )
|
||||
? get_post_meta( $page_id, 'partenaires_nationaux_en', true )
|
||||
: ( get_post_meta( $page_id, 'partenaires_nationaux', true ) ?: '' ) );
|
||||
$context['bibliotheques'] = wpautop( ( $labo_lang === 'en' && get_post_meta( $page_id, 'bibliotheques_en', true ) )
|
||||
: ( get_post_meta( $page_id, 'partenaires_nationaux', true ) ?: '' ) ) );
|
||||
$context['bibliotheques'] = wpautop( wp_kses_post( ( $labo_lang === 'en' && get_post_meta( $page_id, 'bibliotheques_en', true ) )
|
||||
? get_post_meta( $page_id, 'bibliotheques_en', true )
|
||||
: ( get_post_meta( $page_id, 'bibliotheques', true ) ?: '' ) );
|
||||
: ( get_post_meta( $page_id, 'bibliotheques', true ) ?: '' ) ) );
|
||||
|
||||
// ── Edit link ─────────────────────────────────────────────────
|
||||
$context['page_edit_link'] = current_user_can( 'edit_page', $page_id ) ? get_edit_post_link( $page_id ) : '';
|
||||
|
||||
@@ -16,7 +16,7 @@ foreach ( $terms as $term ) {
|
||||
|
||||
$item = [
|
||||
'name' => $term->name,
|
||||
'description' => wpautop( $term->description ),
|
||||
'description' => wpautop( wp_kses_post( $term->description ) ),
|
||||
'url' => get_term_link( $term ),
|
||||
'annee_fin' => (int) get_term_meta( $term->term_id, 'annee_fin', true ),
|
||||
];
|
||||
|
||||
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
|
||||
$context = Timber::context();
|
||||
|
||||
// Séances de séminaire (cat 12) are included: post-card-helpers rewrites their
|
||||
// link to the parent séminaire + #seance-{ID} hash.
|
||||
$excluded_cat_ids = [31]; // Non classé
|
||||
if ( ! is_user_logged_in() ) $excluded_cat_ids[] = 9; // Vie du labo
|
||||
// Les séances de séminaire sont incluses : post-card-helpers réécrit leur
|
||||
// lien vers le séminaire parent + ancre #seance-{ID}.
|
||||
$excluded_cat_ids = thalim_archive_excluded_cat_ids( false ); // Non classé (+ Vie du labo non connectés)
|
||||
$search_query = get_search_query();
|
||||
|
||||
// Read filter query params
|
||||
$active_axe = isset($_GET['axe']) ? intval($_GET['axe']) : 0;
|
||||
$active_date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : '';
|
||||
$active_date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : '';
|
||||
$active_cat_id = isset($_GET['filter_cat']) ? intval($_GET['filter_cat']) : 0;
|
||||
$filter_autres = isset($_GET['filter_autres']) ? 1 : 0;
|
||||
$f = thalim_archive_read_filters();
|
||||
$active_axe = $f['axe'];
|
||||
$active_date_from = $f['date_from'];
|
||||
$active_date_to = $f['date_to'];
|
||||
$active_cat_id = $f['cat_id'];
|
||||
$filter_autres = $f['filter_autres'];
|
||||
|
||||
$context['search_query'] = $search_query;
|
||||
$context['active_axe'] = $active_axe;
|
||||
@@ -23,13 +23,7 @@ $context['active_cat_id'] = $active_cat_id;
|
||||
$context['filter_autres'] = $filter_autres;
|
||||
|
||||
// Determine active rubrique
|
||||
$active_rubrique_id = 0;
|
||||
if ($active_cat_id) {
|
||||
$active_cat_obj = get_category($active_cat_id);
|
||||
$active_rubrique_id = ($active_cat_obj && $active_cat_obj->parent)
|
||||
? $active_cat_obj->parent
|
||||
: $active_cat_id;
|
||||
}
|
||||
$active_rubrique_id = thalim_archive_active_rubrique($active_cat_id);
|
||||
$context['active_rubrique'] = $active_rubrique_id;
|
||||
|
||||
// Base URL for search filter links (language-aware)
|
||||
@@ -72,7 +66,6 @@ $query_args = [
|
||||
'posts_per_page' => 12,
|
||||
'orderby' => 'relevance',
|
||||
'order' => 'DESC',
|
||||
'lang' => '',
|
||||
'tax_query' => $tax_query,
|
||||
];
|
||||
if ($active_axe) {
|
||||
@@ -98,59 +91,19 @@ $context['axe_stay_on_page'] = true;
|
||||
// Rubrique/catégorie filter links (all preserve search term)
|
||||
$all_cats = get_categories(['taxonomy' => 'category', 'hide_empty' => false, 'exclude' => $excluded_cat_ids]);
|
||||
|
||||
$filter_parents = [];
|
||||
foreach ($all_cats as $cat) {
|
||||
if ($cat->parent == 0) {
|
||||
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id]));
|
||||
$filter_parents[] = [
|
||||
'id' => $cat->term_id,
|
||||
'name' => thalim_cat_name($cat),
|
||||
'slug' => $cat->slug,
|
||||
'link' => add_query_arg($params, $search_base),
|
||||
];
|
||||
}
|
||||
}
|
||||
$context['filter_parents'] = $filter_parents;
|
||||
// Liens de filtre : on reste sur la recherche avec un paramètre filter_cat
|
||||
$make_filter_link = function ($cat) use ($base_filter_params, $search_base) {
|
||||
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id]));
|
||||
return add_query_arg($params, $search_base);
|
||||
};
|
||||
$context['filter_parents'] = thalim_archive_filter_parents($all_cats, $make_filter_link);
|
||||
|
||||
$filter_categories = thalim_archive_filter_children($all_cats, $active_rubrique_id, $make_filter_link);
|
||||
|
||||
$filter_categories = [];
|
||||
if ($active_rubrique_id) {
|
||||
foreach ($all_cats as $cat) {
|
||||
if ($cat->parent == $active_rubrique_id) {
|
||||
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id]));
|
||||
$filter_categories[] = [
|
||||
'id' => $cat->term_id,
|
||||
'name' => thalim_cat_name($cat),
|
||||
'slug' => $cat->slug,
|
||||
'link' => add_query_arg($params, $search_base),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add "Autres" entry if active rubrique has posts directly assigned to it
|
||||
if ($active_rubrique_id && !empty($filter_categories)) {
|
||||
$lang = thalim_current_language();
|
||||
$direct_check = new WP_Query([
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => 1,
|
||||
'fields' => 'ids',
|
||||
'no_found_rows' => true,
|
||||
'lang' => '',
|
||||
'tax_query' => [[
|
||||
'taxonomy' => 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => [$active_rubrique_id],
|
||||
'include_children' => false,
|
||||
]],
|
||||
]);
|
||||
if ($direct_check->have_posts()) {
|
||||
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $active_rubrique_id, 'filter_autres' => 1]));
|
||||
$filter_categories[] = [
|
||||
'id' => 'autres',
|
||||
'name' => $lang === 'en' ? 'Other' : 'Autres',
|
||||
'slug' => 'autres',
|
||||
'link' => add_query_arg($params, $search_base),
|
||||
];
|
||||
}
|
||||
if ($active_rubrique_id && !empty($filter_categories) && thalim_rubrique_has_direct_posts($active_rubrique_id)) {
|
||||
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $active_rubrique_id, 'filter_autres' => 1]));
|
||||
$filter_categories[] = thalim_archive_autres_entry(add_query_arg($params, $search_base));
|
||||
}
|
||||
$context['filter_categories'] = $filter_categories;
|
||||
|
||||
@@ -161,21 +114,24 @@ $context['posts'] = $posts;
|
||||
// Search users (members) by display_name
|
||||
$author_cards = [];
|
||||
if ( $search_query ) {
|
||||
$excluded_role_ids = [ 600, 598 ]; // "À ranger", "Archive"
|
||||
$user_query = new WP_User_Query([
|
||||
$excluded_role_ids = thalim_excluded_role_ids(); // « À ranger », « Archive » (résolus par slug)
|
||||
$user_query_args = [
|
||||
'search' => '*' . $search_query . '*',
|
||||
'search_columns' => ['display_name'],
|
||||
'number' => 6,
|
||||
'orderby' => 'display_name',
|
||||
'order' => 'ASC',
|
||||
'meta_query' => [
|
||||
];
|
||||
if ( $excluded_role_ids ) {
|
||||
$user_query_args['meta_query'] = [
|
||||
[
|
||||
'key' => 'role_1',
|
||||
'value' => $excluded_role_ids,
|
||||
'compare' => 'NOT IN',
|
||||
],
|
||||
],
|
||||
]);
|
||||
];
|
||||
}
|
||||
$user_query = new WP_User_Query( $user_query_args );
|
||||
$lang = thalim_current_language();
|
||||
|
||||
// Direction IDs (same source as membres page and author page)
|
||||
|
||||
100
taxonomy.php
100
taxonomy.php
@@ -10,15 +10,16 @@ $context['parent_slug'] = '';
|
||||
$tax_object = get_taxonomy($taxonomy);
|
||||
$context['taxonomy_label'] = $tax_object ? $tax_object->labels->singular_name : $taxonomy;
|
||||
|
||||
$excluded_ids = [12, 31]; // Séance de séminaire, Non classé
|
||||
if ( ! is_user_logged_in() ) $excluded_ids[] = 9; // Vie du labo
|
||||
// Séance de séminaire, Non classé (+ Vie du labo pour les non-connectés)
|
||||
$excluded_ids = thalim_archive_excluded_cat_ids();
|
||||
|
||||
// Read filter query params
|
||||
$active_axe = isset($_GET['axe']) ? intval($_GET['axe']) : 0;
|
||||
$active_date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : '';
|
||||
$active_date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : '';
|
||||
$active_cat_id = isset($_GET['filter_cat']) ? intval($_GET['filter_cat']) : 0;
|
||||
$filter_autres = isset($_GET['filter_autres']) ? 1 : 0;
|
||||
$f = thalim_archive_read_filters();
|
||||
$active_axe = $f['axe'];
|
||||
$active_date_from = $f['date_from'];
|
||||
$active_date_to = $f['date_to'];
|
||||
$active_cat_id = $f['cat_id'];
|
||||
$filter_autres = $f['filter_autres'];
|
||||
|
||||
$context['active_axe'] = $active_axe;
|
||||
$context['active_date_from'] = $active_date_from;
|
||||
@@ -28,13 +29,7 @@ $context['active_cat_id'] = $active_cat_id;
|
||||
$context['filter_autres'] = $filter_autres;
|
||||
|
||||
// Determine active rubrique from active category (parent if subcategory, itself if top-level)
|
||||
$active_rubrique_id = 0;
|
||||
if ($active_cat_id) {
|
||||
$active_cat_obj = get_category($active_cat_id);
|
||||
$active_rubrique_id = ($active_cat_obj && $active_cat_obj->parent)
|
||||
? $active_cat_obj->parent
|
||||
: $active_cat_id;
|
||||
}
|
||||
$active_rubrique_id = thalim_archive_active_rubrique($active_cat_id);
|
||||
$context['active_rubrique'] = $active_rubrique_id;
|
||||
|
||||
// Base params shared across all filter links (preserves active filters when navigating)
|
||||
@@ -53,11 +48,11 @@ $tax_query = [
|
||||
'field' => 'term_id',
|
||||
'terms' => [$term->term_id],
|
||||
],
|
||||
// Exclure les séances de séminaire (catégorie 12)
|
||||
// Exclure les séances de séminaire
|
||||
[
|
||||
'taxonomy' => 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => [12],
|
||||
'terms' => [ thalim_cat_id('seance') ],
|
||||
'operator' => 'NOT IN',
|
||||
],
|
||||
];
|
||||
@@ -101,66 +96,28 @@ $context['axe_stay_on_page'] = !$axe_taxonomy_mode;
|
||||
$current_term_url = get_term_link($term);
|
||||
$all_cats = get_categories(['taxonomy' => 'category', 'hide_empty' => false, 'exclude' => $excluded_ids]);
|
||||
|
||||
$filter_parents = [];
|
||||
foreach ($all_cats as $cat) {
|
||||
if ($cat->parent == 0) {
|
||||
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id]));
|
||||
$filter_parents[] = [
|
||||
'id' => $cat->term_id,
|
||||
'name' => thalim_cat_name($cat),
|
||||
'slug' => $cat->slug,
|
||||
'link' => add_query_arg($params, $current_term_url),
|
||||
];
|
||||
}
|
||||
}
|
||||
$context['filter_parents'] = $filter_parents;
|
||||
// Liens de filtre : on reste sur l'URL du terme courant avec un paramètre filter_cat
|
||||
$make_filter_link = function ($cat) use ($base_filter_params, $current_term_url) {
|
||||
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id]));
|
||||
return add_query_arg($params, $current_term_url);
|
||||
};
|
||||
$context['filter_parents'] = thalim_archive_filter_parents($all_cats, $make_filter_link);
|
||||
|
||||
$filter_categories = thalim_archive_filter_children($all_cats, $active_rubrique_id, $make_filter_link);
|
||||
|
||||
$filter_categories = [];
|
||||
if ($active_rubrique_id) {
|
||||
foreach ($all_cats as $cat) {
|
||||
if ($cat->parent == $active_rubrique_id) {
|
||||
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $cat->term_id]));
|
||||
$filter_categories[] = [
|
||||
'id' => $cat->term_id,
|
||||
'name' => thalim_cat_name($cat),
|
||||
'slug' => $cat->slug,
|
||||
'link' => add_query_arg($params, $current_term_url),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add "Autres" entry if active rubrique has posts directly assigned to it
|
||||
// (contraints au terme de taxonomie courant)
|
||||
if ($active_rubrique_id && !empty($filter_categories)) {
|
||||
$lang = thalim_current_language();
|
||||
$direct_check = new WP_Query([
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => 1,
|
||||
'fields' => 'ids',
|
||||
'no_found_rows' => true,
|
||||
'lang' => '',
|
||||
'tax_query' => [
|
||||
'relation' => 'AND',
|
||||
[
|
||||
'taxonomy' => $taxonomy,
|
||||
'field' => 'term_id',
|
||||
'terms' => [$term->term_id],
|
||||
],
|
||||
[
|
||||
'taxonomy' => 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => [$active_rubrique_id],
|
||||
'include_children' => false,
|
||||
],
|
||||
$has_direct = thalim_rubrique_has_direct_posts($active_rubrique_id, [
|
||||
[
|
||||
'taxonomy' => $taxonomy,
|
||||
'field' => 'term_id',
|
||||
'terms' => [$term->term_id],
|
||||
],
|
||||
]);
|
||||
if ($direct_check->have_posts()) {
|
||||
if ($has_direct) {
|
||||
$params = array_filter(array_merge($base_filter_params, ['filter_cat' => $active_rubrique_id, 'filter_autres' => 1]));
|
||||
$filter_categories[] = [
|
||||
'id' => 'autres',
|
||||
'name' => $lang === 'en' ? 'Other' : 'Autres',
|
||||
'slug' => 'autres',
|
||||
'link' => add_query_arg($params, $current_term_url),
|
||||
];
|
||||
$filter_categories[] = thalim_archive_autres_entry(add_query_arg($params, $current_term_url));
|
||||
}
|
||||
}
|
||||
$context['filter_categories'] = $filter_categories;
|
||||
@@ -171,7 +128,6 @@ $posts = Timber::get_posts(array_merge([
|
||||
'posts_per_page' => 12,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'lang' => '',
|
||||
'thalim_event_date_order' => true,
|
||||
], $extra_query_args));
|
||||
$context['cards'] = thalim_get_cards_data($posts);
|
||||
@@ -181,6 +137,6 @@ $context['posts'] = $posts;
|
||||
$tax_lang = thalim_current_language();
|
||||
$pres_fr = get_term_meta($term->term_id, 'presentation', true) ?: '';
|
||||
$pres_en = get_term_meta($term->term_id, 'presentation_en', true) ?: '';
|
||||
$context['term_presentation'] = wpautop( ( $tax_lang === 'en' && $pres_en ) ? $pres_en : $pres_fr );
|
||||
$context['term_presentation'] = wpautop( wp_kses_post( ( $tax_lang === 'en' && $pres_en ) ? $pres_en : $pres_fr ) );
|
||||
|
||||
Timber::render('taxonomy.twig', $context);
|
||||
|
||||
@@ -22,13 +22,13 @@
|
||||
{% if author.avatar_url %}
|
||||
<div class="author-header">
|
||||
<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 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 %}
|
||||
<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>
|
||||
{% endif %}
|
||||
<p class="maj">{{ current_language == 'en' ? 'Updated on' : 'Mis à jour le' }} {{ author.user_since }}</p>
|
||||
@@ -63,10 +63,10 @@
|
||||
|
||||
{% if not author.avatar_url %}
|
||||
<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 %}
|
||||
<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>
|
||||
{% endif %}
|
||||
<p class="maj">{{ current_language == 'en' ? 'Updated on' : 'Mis à jour le' }} {{ author.user_since }}</p>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<ul>
|
||||
<li data-role="">{{ current_language == 'en' ? 'All statuses' : 'Tous les statuts' }}</li>
|
||||
{% 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 %}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -81,18 +81,20 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for member in group.members %}
|
||||
<tr onclick="window.location.href='{{ member.url }}'"
|
||||
data-name="{{ member.display_name }}"
|
||||
data-sort-name="{{ member.sort_key }}"
|
||||
data-roles="{{ member.role_names|join('|') }}"
|
||||
data-avatar="{{ member.avatar_url }}"
|
||||
data-status="{{ member.status }}"
|
||||
data-affiliation="{{ member.affiliation }}"
|
||||
data-domaines="{{ member.domaines|join(', ') }}"
|
||||
data-autres-domaines="{{ member.autres_domaines }}">
|
||||
<td>{{ member.display_name }}</td>
|
||||
<td>{{ member.status }}</td>
|
||||
<td>{{ member.affiliation }}</td>
|
||||
{# data-url + listener délégué (membresFilters.js) au lieu d'un onclick inline ;
|
||||
tout passe par esc_attr/esc_html : ces valeurs viennent des profils utilisateurs #}
|
||||
<tr data-url="{{ member.url|esc_url }}"
|
||||
data-name="{{ member.display_name|esc_attr }}"
|
||||
data-sort-name="{{ member.sort_key|esc_attr }}"
|
||||
data-roles="{{ member.role_names|join('|')|esc_attr }}"
|
||||
data-avatar="{{ member.avatar_url|esc_url }}"
|
||||
data-status="{{ member.status|esc_attr }}"
|
||||
data-affiliation="{{ member.affiliation|esc_attr }}"
|
||||
data-domaines="{{ member.domaines|join(', ')|esc_attr }}"
|
||||
data-autres-domaines="{{ member.autres_domaines|esc_attr }}">
|
||||
<td>{{ member.display_name|esc_html }}</td>
|
||||
<td>{{ member.status|esc_html }}</td>
|
||||
<td>{{ member.affiliation|esc_html }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
@@ -20,6 +20,6 @@
|
||||
{% if type_label %}<span>{{ type_label }}</span>{% endif %}
|
||||
{% if lieu %}<span>{{ lieu }}</span>{% endif %}
|
||||
</div>
|
||||
<p class="agenda-card__title">{{ post.title }}</p>
|
||||
<p class="agenda-card__title">{{ post.title|bilingual }}</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<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 %}
|
||||
<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 %}
|
||||
<span class="author-card__initials">{{ author.initials }}</span>
|
||||
<span class="author-card__initials">{{ author.initials|esc_html }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
<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 %}
|
||||
<p class="author-card__role">{{ author.role_label }}</p>
|
||||
<p class="author-card__role">{{ author.role_label|esc_html }}</p>
|
||||
{% endif %}
|
||||
{% if author.affiliation %}
|
||||
<p class="author-card__affiliation">{{ author.affiliation }}</p>
|
||||
<p class="author-card__affiliation">{{ author.affiliation|esc_html }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</article>
|
||||
|
||||
@@ -40,77 +40,3 @@
|
||||
</h2>
|
||||
{% endif %}
|
||||
</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 %}
|
||||
<span class="breadcrumb__separator">→</span>
|
||||
<a class="breadcrumb__cat" href="{{ article.category_link }}">{{ article.category_name }}</a>
|
||||
{% endif %}{#
|
||||
<span class="breadcrumb__separator">→</span>
|
||||
<span class="breadcrumb__current">{{ post.title }}</span> #}
|
||||
{% endif %}
|
||||
</nav>
|
||||
{% if post.edit_link %}
|
||||
<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