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:
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' );
|
||||
Reference in New Issue
Block a user