1376 lines
52 KiB
PHP
Executable File
1376 lines
52 KiB
PHP
Executable File
<?php
|
||
require_once __DIR__ . '/vendor/autoload.php';
|
||
Timber\Timber::init();
|
||
Timber::$dirname = 'templates';
|
||
|
||
add_filter('timber/context', 'add_to_context');
|
||
|
||
// ── 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.
|
||
/**
|
||
* Returns the avatar URL for a user:
|
||
* 1. Simple Local Avatar (media library upload) if set
|
||
* 2. Gravatar if the user has one (result cached 1 week per user)
|
||
* 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 — check existence with a HEAD request, cached per user
|
||
$user = get_userdata( $user_id );
|
||
if ( ! $user ) return '';
|
||
|
||
$cache_key = 'thalim_gravatar_' . $user_id;
|
||
$cached = get_transient( $cache_key );
|
||
if ( $cached !== false ) return $cached;
|
||
|
||
$hash = md5( strtolower( trim( $user->user_email ) ) );
|
||
$response = wp_remote_head(
|
||
'https://www.gravatar.com/avatar/' . $hash . '?d=404',
|
||
[ 'timeout' => 3 ]
|
||
);
|
||
|
||
if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200 ) {
|
||
$url = 'https://www.gravatar.com/avatar/' . $hash . '?s=300';
|
||
set_transient( $cache_key, $url, WEEK_IN_SECONDS );
|
||
return $url;
|
||
}
|
||
|
||
// No Gravatar — cache the negative result too
|
||
set_transient( $cache_key, '', WEEK_IN_SECONDS );
|
||
return '';
|
||
}
|
||
|
||
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 ],
|
||
];
|
||
}
|
||
|
||
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',
|
||
'https://cdn.jsdelivr.net/gh/iconoir-icons/iconoir@main/css/iconoir.css',
|
||
[],
|
||
null
|
||
);
|
||
|
||
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() ) {
|
||
wp_enqueue_style( 'swiper', 'https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.css', [], null );
|
||
wp_enqueue_script( 'swiper', 'https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.js', [], null, true );
|
||
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
|
||
);
|
||
|
||
wp_enqueue_style(
|
||
'swiper',
|
||
'https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.css',
|
||
[],
|
||
null
|
||
);
|
||
|
||
wp_enqueue_script(
|
||
'swiper',
|
||
'https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.js',
|
||
[],
|
||
null,
|
||
true
|
||
);
|
||
|
||
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',
|
||
'lang' => '',
|
||
]);
|
||
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()) {
|
||
wp_enqueue_style(
|
||
'swiper',
|
||
'https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.css',
|
||
[],
|
||
null
|
||
);
|
||
|
||
wp_enqueue_script(
|
||
'swiper',
|
||
'https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.js',
|
||
[],
|
||
null,
|
||
true
|
||
);
|
||
|
||
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(),
|
||
]);
|
||
}
|
||
|
||
if ($is_archive_page) {
|
||
wp_enqueue_script(
|
||
'categoryFilters',
|
||
get_template_directory_uri() . '/js/categoryFilters.js',
|
||
[],
|
||
filemtime(get_template_directory() . '/js/categoryFilters.js'),
|
||
true
|
||
);
|
||
}
|
||
|
||
// wp_enqueue_style('wp-block-library');
|
||
}
|
||
add_action('wp_enqueue_scripts', 'theme_enqueue_assets');
|
||
|
||
// 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 );
|
||
$name = 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' );
|
||
}
|
||
|
||
add_action( 'admin_enqueue_scripts', 'enqueue_admin_js' );
|
||
|
||
function enqueue_admin_js(){
|
||
wp_enqueue_style(
|
||
'adminDashboardStyles',
|
||
get_template_directory_uri() . '/css/admin.css',
|
||
[],
|
||
filemtime(get_template_directory() . '/css/admin.css')
|
||
);
|
||
|
||
wp_enqueue_script(
|
||
'adminDashboardMods',
|
||
get_template_directory_uri() . '/js/adminDashboardMods.js',
|
||
[],
|
||
filemtime(get_template_directory() . '/js/adminDashboardMods.js'),
|
||
true
|
||
);
|
||
$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( 'adminDashboardMods', 'thalimAxesGroups', $axes_groups );
|
||
|
||
// [DÉSACTIVÉ] adminFormRestore — décommenter pour réactiver
|
||
// $screen = get_current_screen();
|
||
// if ( $screen && 'post' === $screen->base ) {
|
||
// wp_enqueue_script(
|
||
// 'adminFormRestore',
|
||
// get_template_directory_uri() . '/js/adminFormRestore.js',
|
||
// [ 'jquery' ],
|
||
// filemtime(get_template_directory() . '/js/adminFormRestore.js'),
|
||
// true
|
||
// );
|
||
// }
|
||
}
|
||
add_theme_support('title-tag');
|
||
|
||
// 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;
|
||
});
|
||
|
||
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;
|
||
}
|
||
|
||
// 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/publish 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.
|
||
$primitive_caps_in_save_flow = [
|
||
'edit_others_posts',
|
||
'edit_others_pages',
|
||
'edit_published_posts',
|
||
'edit_published_pages',
|
||
'publish_posts',
|
||
'publish_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 );
|
||
|
||
// 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 );
|
||
|
||
require_once __DIR__ . '/inc/admin-users-filter.php';
|
||
require_once __DIR__ . '/inc/pods-conditional-required.php';
|
||
require_once __DIR__ . '/inc/pods-save-error-handler.php';
|
||
require_once __DIR__ . '/inc/post-title-required.php';
|
||
require_once __DIR__ . '/inc/post-card-helpers.php';
|
||
require_once __DIR__ . '/inc/single-helpers.php';
|
||
require_once __DIR__ . '/inc/author-helpers.php';
|
||
|
||
// 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' );
|
||
|
||
// ── "Vie du labo" (cat 9) — restricted to logged-in users ────
|
||
add_action( 'pre_get_posts', function( $query ) {
|
||
if ( is_user_logged_in() ) return;
|
||
$excluded = $query->get( 'category__not_in' );
|
||
if ( ! is_array( $excluded ) ) $excluded = $excluded ? [ $excluded ] : [];
|
||
if ( ! in_array( 9, $excluded ) ) {
|
||
$excluded[] = 9;
|
||
$query->set( 'category__not_in', $excluded );
|
||
}
|
||
} );
|
||
|
||
add_action( 'template_redirect', function() {
|
||
if ( ! is_user_logged_in() && is_category( 9 ) ) {
|
||
wp_safe_redirect( home_url( '/' ) );
|
||
exit;
|
||
}
|
||
} );
|
||
|
||
// Séance de séminaire (cat 12): redirect to parent séminaire with #seance-{ID} anchor
|
||
add_action( 'template_redirect', function() {
|
||
if ( ! is_single() ) return;
|
||
if ( ! has_category( 12 ) ) return;
|
||
|
||
global $wpdb;
|
||
$seance_id = get_the_ID();
|
||
$parent_id = $wpdb->get_var( $wpdb->prepare(
|
||
"SELECT pm.post_id FROM {$wpdb->postmeta} pm
|
||
JOIN {$wpdb->posts} p ON p.ID = pm.post_id
|
||
WHERE pm.meta_key = 'seances' AND pm.meta_value = %s
|
||
AND p.post_status = 'publish'
|
||
LIMIT 1",
|
||
(string) $seance_id
|
||
) );
|
||
|
||
if ( $parent_id ) {
|
||
wp_redirect( get_permalink( (int) $parent_id ) . '#seance-' . $seance_id, 301 );
|
||
exit;
|
||
}
|
||
} );
|
||
|
||
add_filter( 'wp_nav_menu_objects', function( $items, $args ) {
|
||
if ( is_user_logged_in() ) return $items;
|
||
return array_values( array_filter( $items, function( $item ) {
|
||
if ( $item->object === 'category' && (int) $item->object_id === 9 ) return false;
|
||
if ( strpos( $item->url, 'vie-du-labo' ) !== false ) return false;
|
||
return true;
|
||
} ) );
|
||
}, 10, 2 );
|
||
|
||
// 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' );
|
||
|
||
// 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 );
|
||
|
||
// 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' );
|
||
|
||
$page = intval($_POST['page']);
|
||
$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']) : '';
|
||
$term_taxonomy = isset($_POST['taxonomy']) ? sanitize_key($_POST['taxonomy']) : '';
|
||
$term_id = isset($_POST['term']) ? intval($_POST['term']) : 0;
|
||
$cat_filter = 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']) : '';
|
||
|
||
$query_args = [
|
||
'post_type' => 'post',
|
||
'posts_per_page' => 12,
|
||
'paged' => $page,
|
||
'orderby' => 'date',
|
||
'order' => 'DESC',
|
||
'lang' => '',
|
||
'thalim_event_date_order' => true,
|
||
];
|
||
if ($search) {
|
||
$query_args['s'] = $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 ($category) {
|
||
$tax_clauses[] = [
|
||
'taxonomy' => 'category',
|
||
'field' => 'term_id',
|
||
'terms' => [$category],
|
||
'include_children' => false,
|
||
];
|
||
}
|
||
if ($term_taxonomy && $term_id) {
|
||
$tax_clauses[] = [
|
||
'taxonomy' => $term_taxonomy,
|
||
'field' => 'term_id',
|
||
'terms' => [$term_id],
|
||
];
|
||
// Exclure les séances de séminaire sur les pages de taxonomie
|
||
$tax_clauses[] = [
|
||
'taxonomy' => 'category',
|
||
'field' => 'term_id',
|
||
'terms' => [12],
|
||
'operator' => 'NOT IN',
|
||
];
|
||
}
|
||
if ($cat_filter) {
|
||
$tax_clauses[] = [
|
||
'taxonomy' => 'category',
|
||
'field' => 'term_id',
|
||
'terms' => [$cat_filter],
|
||
'include_children' => !$filter_autres,
|
||
];
|
||
}
|
||
if ($exclude_cats) {
|
||
$ids = array_filter(array_map('intval', explode(',', $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 ($axe) {
|
||
$query_args['meta_query'] = [[
|
||
'key' => 'axes_thematiques',
|
||
'value' => $axe,
|
||
'type' => 'NUMERIC',
|
||
]];
|
||
}
|
||
|
||
if ($date_from || $date_to) {
|
||
$query_args['thalim_event_date_filter'] = ['from' => $date_from, 'to' => $date_to];
|
||
}
|
||
|
||
// Exclude pinned posts on category pages to avoid duplicates (they already appear at the top)
|
||
if ($category) {
|
||
$today = date( 'Y-m-d' );
|
||
$pinned_query = 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' => [$category],
|
||
'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;
|
||
}
|
||
}
|
||
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' => $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 (cat 12): link to parent séminaire at #seance-{ID}
|
||
$link = get_permalink( $post_id );
|
||
if ( in_array( 12, wp_list_pluck( get_the_category( $post_id ), 'term_id' ) ) ) {
|
||
global $wpdb;
|
||
$parent_id = $wpdb->get_var( $wpdb->prepare(
|
||
"SELECT pm.post_id FROM {$wpdb->postmeta} pm
|
||
JOIN {$wpdb->posts} p ON p.ID = pm.post_id
|
||
WHERE pm.meta_key = 'seances' AND pm.meta_value = %s
|
||
AND p.post_status = 'publish'
|
||
LIMIT 1",
|
||
(string) $post_id
|
||
) );
|
||
if ( $parent_id ) {
|
||
$link = get_permalink( (int) $parent_id ) . '#seance-' . $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;
|
||
$page = intval( $_POST['page'] );
|
||
$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'] ) : '';
|
||
$term_taxonomy = isset( $_POST['taxonomy'] ) ? sanitize_key( $_POST['taxonomy'] ) : '';
|
||
$term_id = isset( $_POST['term'] ) ? intval( $_POST['term'] ) : 0;
|
||
$cat_filter = 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'] ) : '';
|
||
|
||
$query_args = [
|
||
'post_type' => 'post',
|
||
'posts_per_page' => 12,
|
||
'paged' => $page,
|
||
'orderby' => 'date',
|
||
'order' => 'DESC',
|
||
'lang' => '',
|
||
'thalim_event_date_order' => true,
|
||
];
|
||
|
||
$include_children = ! empty( $_POST['include_children'] );
|
||
|
||
$tax_clauses = [];
|
||
if ( $category ) {
|
||
$tax_clauses[] = [ 'taxonomy' => 'category', 'field' => 'term_id', 'terms' => [ $category ], 'include_children' => $include_children ];
|
||
}
|
||
if ( $term_taxonomy && $term_id ) {
|
||
$tax_clauses[] = [ 'taxonomy' => $term_taxonomy, 'field' => 'term_id', 'terms' => [ $term_id ] ];
|
||
$tax_clauses[] = [ 'taxonomy' => 'category', 'field' => 'term_id', 'terms' => [ 12 ], 'operator' => 'NOT IN' ];
|
||
}
|
||
if ( $cat_filter ) {
|
||
$tax_clauses[] = [ 'taxonomy' => 'category', 'field' => 'term_id', 'terms' => [ $cat_filter ], 'include_children' => ! $filter_autres ];
|
||
}
|
||
if ( $exclude_cats ) {
|
||
$ids = array_filter( array_map( 'intval', explode( ',', $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 ( $axe ) {
|
||
$query_args['meta_query'] = [[ 'key' => 'axes_thematiques', 'value' => $axe, 'type' => 'NUMERIC' ]];
|
||
}
|
||
if ( $date_from || $date_to ) {
|
||
$query_args['thalim_event_date_filter'] = [ 'from' => $date_from, 'to' => $date_to ];
|
||
}
|
||
|
||
// On first page, count future events to find today's anchor position
|
||
$today_offset = 0;
|
||
if ( (int) $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) $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) $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' );
|
||
|
||
// Event date ordering: LEFT JOIN + COALESCE(date_de_debut, post_date)
|
||
// Activated by adding 'thalim_event_date_order' => true to WP_Query args.
|
||
// 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";
|
||
}, 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);
|
||
|
||
// ── 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 );
|
||
}
|
||
|