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' );