Retour du commanditaire après le premier envoi réel (juillet 2026) : des annonces à venir n'étaient pas proposées à la coche, et les dates affichées étaient les dates de publication. - Fenêtre « à venir » de 12 mois (FUTURE_HORIZON_MONTHS), démarrant à la fin du mois de la newsletter ou à aujourd'hui si ce mois est passé, en remplacement du plafond « début de fenêtre <= fin du mois ». Idem pour les séances (SEANCE_WINDOW_MARGIN_DAYS de 5 jours supprimée). Ces items sont regroupés dans un bloc « À venir », décochés par défaut. - event_date_label() centralise les libellés du thème (« Le X de H1 à H2 », « Du X au Y », « Jusqu'au X », « X à H ») sur la convention date_de_debut > datetime > post_date. Utilisée par l'export et par la pastille de la liste à cocher, qui affichent donc la même chose. - Tri par date d'événement (sql_event_date_order) et non par date de publication. - Fenêtres de fin « +35 j » et « +3 mois » recalées sur la date d'événement : un événement annoncé plus de 35 jours à l'avance sortait de la fenêtre avant d'avoir eu lieu et manquait dans la newsletter de son propre mois. - merge_selected_items() : une sélection déjà enregistrée est réinjectée dans la liste même si ses items sont sortis de la fenêtre — sans ça, rouvrir puis réenregistrer une ancienne newsletter l'amputait silencieusement. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w7RzAHv4r1Yahnm2NwDDG
605 lines
24 KiB
PHP
605 lines
24 KiB
PHP
<?php
|
|
/**
|
|
* Post Query Class — fetches posts per category for a given month
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class Thalim_NL_Post_Query {
|
|
|
|
/**
|
|
* Window type per category ID (special cases).
|
|
*
|
|
* La *fin* de fenêtre filtre le passé : un contenu est retenu pour le mois M
|
|
* dès lors qu'il ne s'est pas terminé avant M. Côté futur, le plafond n'est
|
|
* plus la fin du mois mais FUTURE_HORIZON_MONTHS (demande du 2026-08-27 :
|
|
* des annonces d'octobre devaient pouvoir entrer dans la newsletter de
|
|
* juillet). Les items dont la date d'événement dépasse le mois sont marqués
|
|
* `is_future` et présentés à part dans l'UI, décochés par défaut.
|
|
*
|
|
* - datetime_to_fin : fin = date_de_fin ∥ datetime ∥ post_date
|
|
* - debut_minus35_to_fin: fin = date_de_fin ∥ date_de_debut ∥ post_date
|
|
* - datetime_plus3m : fin = (datetime ∥ post_date) + 3 mois
|
|
* - datetime_plus35d : fin = (datetime ∥ post_date) + 35 jours (défaut)
|
|
*/
|
|
private const SPECIAL_WINDOW_TYPES = [
|
|
THALIM_NL_CAT_APPELS => 'datetime_to_fin',
|
|
THALIM_NL_CAT_COLLOQUES => 'debut_minus35_to_fin',
|
|
THALIM_NL_CAT_COMMS => 'debut_minus35_to_fin',
|
|
THALIM_NL_CAT_SOUTENANCES => 'datetime_to_fin',
|
|
THALIM_NL_CAT_OUVRAGES => 'datetime_plus3m',
|
|
THALIM_NL_CAT_ARTICLES => 'datetime_plus3m',
|
|
];
|
|
|
|
private const DEFAULT_WINDOW_TYPE = 'datetime_plus35d';
|
|
|
|
/**
|
|
* Horizon d'affichage des annonces à venir, en mois.
|
|
*
|
|
* Sans plafond, ouvrir la newsletter d'un mois ancien remonterait tout
|
|
* l'historique postérieur (la totalité du site pour un mois de 1999) :
|
|
* requête et rendu ingérables. 12 mois couvrent largement les annonces
|
|
* saisies en avance (séances de la rentrée, colloques annoncés un an à
|
|
* l'avance) tout en bornant la liste.
|
|
*/
|
|
private const FUTURE_HORIZON_MONTHS = 12;
|
|
|
|
/**
|
|
* Bornes du bloc « à venir » : [début, fin] en timestamps.
|
|
*
|
|
* Le bloc commence à la fin du mois de la newsletter, ou à aujourd'hui si
|
|
* ce mois est déjà passé — rouvrir une vieille newsletter ne doit pas
|
|
* proposer une année de contenus entre-temps devenus du passé.
|
|
*/
|
|
private static function future_bounds(int $month_end): array {
|
|
$from = max($month_end, strtotime('today'));
|
|
return [ $from, strtotime('+' . self::FUTURE_HORIZON_MONTHS . ' months', $from) ?: $from ];
|
|
}
|
|
|
|
/**
|
|
* Categories to exclude from the newsletter UI, résolues par slug
|
|
* (fallback sur les IDs historiques si un slug est introuvable).
|
|
*/
|
|
private static function excluded_cats(): array {
|
|
static $ids = null;
|
|
if ($ids === null) {
|
|
$map = [
|
|
'vie-du-labo-intranet' => 9, // Vie du labo (intranet)
|
|
'seance-de-seminaire' => 12, // Séance de séminaire
|
|
'newsletter' => 20, // Newsletter
|
|
'non-classe' => 31, // Non classé
|
|
];
|
|
$ids = [];
|
|
foreach ($map as $slug => $fallback) {
|
|
$term = get_term_by('slug', $slug, 'category');
|
|
$ids[] = $term ? (int) $term->term_id : $fallback;
|
|
}
|
|
}
|
|
return $ids;
|
|
}
|
|
|
|
/**
|
|
* Get all newsletter-eligible categories, grouped by parent.
|
|
* Returns [ parent_id => ['name' => string, 'children' => [cat_id => name, ...]], ... ]
|
|
*/
|
|
public static function get_eligible_categories(): array {
|
|
$all_cats = get_categories([
|
|
'taxonomy' => 'category',
|
|
'hide_empty' => false,
|
|
'orderby' => 'term_id',
|
|
'order' => 'ASC',
|
|
]);
|
|
|
|
$by_parent = [];
|
|
$parents = [];
|
|
|
|
foreach ($all_cats as $cat) {
|
|
if (in_array($cat->term_id, self::excluded_cats(), true)) {
|
|
continue;
|
|
}
|
|
if ($cat->parent == 0) {
|
|
$parents[$cat->term_id] = $cat->name;
|
|
}
|
|
}
|
|
|
|
foreach ($parents as $pid => $pname) {
|
|
$by_parent[$pid] = ['name' => $pname, 'children' => []];
|
|
}
|
|
|
|
foreach ($all_cats as $cat) {
|
|
if (in_array($cat->term_id, self::excluded_cats(), true)) {
|
|
continue;
|
|
}
|
|
if ($cat->parent == 0) {
|
|
continue; // parents handled above
|
|
}
|
|
$p = $cat->parent;
|
|
if (!isset($by_parent[$p])) {
|
|
continue;
|
|
}
|
|
$by_parent[$p]['children'][$cat->term_id] = $cat->name;
|
|
}
|
|
|
|
return $by_parent;
|
|
}
|
|
|
|
/**
|
|
* Flat list of all eligible category IDs (excluding EXCLUDED_CATS).
|
|
*/
|
|
public static function get_all_eligible_cat_ids(): array {
|
|
$groups = self::get_eligible_categories();
|
|
$ids = [];
|
|
foreach ($groups as $pid => $group) {
|
|
$ids[] = $pid;
|
|
foreach ($group['children'] as $cid => $name) {
|
|
$ids[] = $cid;
|
|
}
|
|
}
|
|
return $ids;
|
|
}
|
|
|
|
/**
|
|
* Get the window type for a given category.
|
|
*/
|
|
public static function get_window_type(int $cat_id): string {
|
|
return self::SPECIAL_WINDOW_TYPES[$cat_id] ?? self::DEFAULT_WINDOW_TYPE;
|
|
}
|
|
|
|
/**
|
|
* Get posts grouped by category for the given year-month (e.g. "2026-03").
|
|
*
|
|
* @param string $year_month Format: YYYY-MM
|
|
* @return array ['cat_id' => [post_data, ...], ...]
|
|
*/
|
|
public function get_posts_for_month(string $year_month): array {
|
|
$month_start = strtotime($year_month . '-01 00:00:00');
|
|
if (!$month_start) {
|
|
return [];
|
|
}
|
|
$month_end = strtotime('last day of ' . $year_month . ' 23:59:59');
|
|
|
|
$result = [];
|
|
foreach (self::get_all_eligible_cat_ids() as $cat_id) {
|
|
// Seminars: list individually-selectable séances grouped by seminar.
|
|
if ($cat_id === THALIM_NL_CAT_SEMINAIRES) {
|
|
$seances = $this->query_seminar_seances($month_start, $month_end, self::future_bounds($month_end));
|
|
if (!empty($seances)) {
|
|
$result[$cat_id] = $seances;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
$window_type = self::get_window_type($cat_id);
|
|
$posts = $this->query_category($cat_id, $window_type, $month_start, $month_end, self::future_bounds($month_end));
|
|
if (!empty($posts)) {
|
|
$result[$cat_id] = $posts;
|
|
}
|
|
}
|
|
|
|
// Marque les items dont la date d'événement dépasse le mois : l'UI les
|
|
// présente à part et ne les coche pas par défaut.
|
|
foreach ($result as $cat_id => $items) {
|
|
foreach ($items as $i => $item) {
|
|
$ts = self::event_start_ts($item);
|
|
$result[$cat_id][$i]['is_future'] = ($ts > $month_end);
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Timestamp d'événement d'un item, dans l'ordre de priorité du thème :
|
|
* date_de_debut > datetime > post_date.
|
|
*/
|
|
public static function event_start_ts(array $item): int {
|
|
foreach (['date_debut', 'datetime', 'post_date'] as $key) {
|
|
$raw = $item[$key] ?? '';
|
|
if ($raw && !str_starts_with($raw, '0000-00-00')) {
|
|
$ts = strtotime($raw);
|
|
if ($ts) {
|
|
return $ts;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Réintègre dans les données du mois les items d'une sélection déjà
|
|
* enregistrée qui n'y figureraient plus.
|
|
*
|
|
* Les fenêtres d'éligibilité peuvent évoluer (elles ont changé le
|
|
* 2026-08-27 en passant de la date de publication à la date d'événement) :
|
|
* sans ce filet, rouvrir puis réenregistrer une ancienne newsletter la
|
|
* amputerait silencieusement des items devenus hors fenêtre — le
|
|
* formulaire ne soumet que ce qui est affiché.
|
|
*
|
|
* @param array $month_data Données du mois, par catégorie.
|
|
* @param array $sections Sélection enregistrée : cat_id => [post_id, …].
|
|
*/
|
|
public function merge_selected_items(array $month_data, array $sections): array {
|
|
foreach ($sections as $cat_id => $ids) {
|
|
$cat_id = (int) $cat_id;
|
|
$present = array_map(
|
|
static fn($item) => (int) $item['id'],
|
|
$month_data[$cat_id] ?? []
|
|
);
|
|
|
|
foreach (array_diff(array_map('intval', (array) $ids), $present) as $post_id) {
|
|
$post = get_post($post_id);
|
|
if (!$post || $post->post_status !== 'publish') {
|
|
continue;
|
|
}
|
|
|
|
if ($cat_id === THALIM_NL_CAT_SEMINAIRES) {
|
|
$seminar_id = self::get_seminar_id_for_seance($post_id);
|
|
if (!$seminar_id) {
|
|
continue;
|
|
}
|
|
$item = $this->build_seance_data(
|
|
$post_id,
|
|
$seminar_id,
|
|
get_the_title($seminar_id),
|
|
get_permalink($seminar_id)
|
|
);
|
|
} else {
|
|
$item = $this->build_post_data($post_id, $post->post_title, $post->post_date);
|
|
}
|
|
|
|
// Item repêché : jamais dans le bloc « à venir », il fait
|
|
// partie de la sélection du mois telle qu'elle a été validée.
|
|
$item['is_future'] = false;
|
|
$month_data[$cat_id][] = $item;
|
|
}
|
|
}
|
|
|
|
return $month_data;
|
|
}
|
|
|
|
/**
|
|
* Données d'une séance, avec son séminaire parent (pour le regroupement).
|
|
*/
|
|
private function build_seance_data(int $sid, int $seminar_id, string $seminar_title, string $seminar_permalink): array {
|
|
$s_post = get_post($sid);
|
|
|
|
return [
|
|
'id' => $sid,
|
|
'title' => get_the_title($sid),
|
|
// Links straight to the séance anchor on the seminar page.
|
|
'permalink' => $seminar_permalink . '#seance-' . $sid,
|
|
'datetime' => '',
|
|
'date_debut' => get_post_meta($sid, 'date_de_debut', true) ?: '',
|
|
'date_fin' => get_post_meta($sid, 'date_de_fin', true) ?: '',
|
|
'post_date' => $s_post ? $s_post->post_date : '',
|
|
'heure_de_debut' => substr(get_post_meta($sid, 'heure_de_debut', true) ?: '', 0, 5),
|
|
'heure_de_fin' => substr(get_post_meta($sid, 'heure_de_fin', true) ?: '', 0, 5),
|
|
'lieu' => get_post_meta($sid, 'lieu', true) ?: '',
|
|
'membres' => $this->get_post_membres($sid),
|
|
'autrepersonnes' => get_post_meta($sid, 'autrepersonnes', true) ?: '',
|
|
'seminar_id' => $seminar_id,
|
|
'seminar_title' => $seminar_title,
|
|
'seminar_permalink' => $seminar_permalink,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Libellé de date d'un contenu pour la newsletter.
|
|
*
|
|
* Reprend la convention du thème (date_de_debut > datetime > post_date) et
|
|
* les libellés de thalim_get_agenda_card_data() : « Le X de H1 à H2 »,
|
|
* « Du X au Y », « Jusqu'au X », « X à H ». Le plugin garde sa propre
|
|
* implémentation plutôt que d'appeler le thème, pour rester autonome.
|
|
*
|
|
* Renvoie '' si aucune date exploitable — au caller de retomber sur
|
|
* post_date s'il le souhaite.
|
|
*/
|
|
public static function event_date_label(int $post_id, string $format = 'j F Y'): string {
|
|
$ts_debut = self::valid_ts(get_post_meta($post_id, 'date_de_debut', true) ?: '');
|
|
$ts_fin = self::valid_ts(get_post_meta($post_id, 'date_de_fin', true) ?: '');
|
|
$ts_dt = self::valid_ts(get_post_meta($post_id, 'datetime', true) ?: '');
|
|
|
|
$fmt_debut = $ts_debut ? date_i18n($format, $ts_debut) : '';
|
|
$fmt_fin = $ts_fin ? date_i18n($format, $ts_fin) : '';
|
|
$fmt_dt = $ts_dt ? date_i18n($format, $ts_dt) : '';
|
|
|
|
$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) {
|
|
// Même jour de début et de fin : une seule date, éventuellement horaire.
|
|
if ($ts_debut && $ts_fin && date('Y-m-d', $ts_debut) === date('Y-m-d', $ts_fin)) {
|
|
if ($h_debut && $h_fin) {
|
|
return 'Le ' . $fmt_debut . ' de ' . $h_debut . ' à ' . $h_fin;
|
|
}
|
|
return $h_debut ? $fmt_debut . ' à ' . $h_debut : $fmt_debut;
|
|
}
|
|
if ($fmt_debut && $fmt_fin) {
|
|
return 'Du ' . $fmt_debut . ' au ' . $fmt_fin;
|
|
}
|
|
if ($fmt_debut) {
|
|
return $h_debut ? $fmt_debut . ' à ' . $h_debut : $fmt_debut;
|
|
}
|
|
// Seule une date de fin : cas des appels à contribution (date limite).
|
|
return "Jusqu'au " . $fmt_fin;
|
|
}
|
|
|
|
if ($fmt_dt) {
|
|
return $h_debut ? $fmt_dt . ' à ' . $h_debut : $fmt_dt;
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Timestamp d'une valeur de date Pods, 0 si vide ou invalide (0000-00-00).
|
|
*/
|
|
private static function valid_ts(string $raw): int {
|
|
if (!$raw || str_starts_with($raw, '0000-00-00')) {
|
|
return 0;
|
|
}
|
|
return strtotime($raw) ?: 0;
|
|
}
|
|
|
|
/**
|
|
* Build the flat list of selectable séances for the seminar category.
|
|
*
|
|
* We walk every published seminar (cat 11), read its `seances` meta
|
|
* (array of séance post IDs), and keep every séance dont la date_de_debut
|
|
* tombe dans le mois, ou dans la fenêtre « à venir » (les séances de la
|
|
* rentrée doivent pouvoir entrer dans la newsletter de juillet).
|
|
* Each returned item carries its parent seminar (id/title/permalink) so
|
|
* the UI and the exporter can group séances under their seminar.
|
|
*
|
|
* @return array Flat list of séance items (see shape below).
|
|
*/
|
|
private function query_seminar_seances(int $month_start, int $month_end, array $future): array {
|
|
global $wpdb;
|
|
|
|
$seminar_ids = $wpdb->get_col($wpdb->prepare(
|
|
"SELECT DISTINCT p.ID
|
|
FROM {$wpdb->posts} p
|
|
INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id = p.ID
|
|
INNER JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id
|
|
AND tt.taxonomy = 'category' AND tt.term_id = %d
|
|
WHERE p.post_type = 'post' AND p.post_status = 'publish'",
|
|
THALIM_NL_CAT_SEMINAIRES
|
|
));
|
|
|
|
$items = [];
|
|
foreach ($seminar_ids as $seminar_id) {
|
|
$seminar_id = (int) $seminar_id;
|
|
$seminar_title = get_the_title($seminar_id);
|
|
$seminar_permalink = get_permalink($seminar_id);
|
|
|
|
foreach (get_post_meta($seminar_id, 'seances', false) as $sid) {
|
|
$sid = (int) $sid;
|
|
$s_post = get_post($sid);
|
|
if (!$s_post || $s_post->post_status !== 'publish') {
|
|
continue;
|
|
}
|
|
|
|
$raw_debut = get_post_meta($sid, 'date_de_debut', true) ?: '';
|
|
$ts = $raw_debut ? strtotime($raw_debut) : false;
|
|
$in_month = ($ts >= $month_start && $ts <= $month_end);
|
|
$in_future = ($ts >= $future[0] && $ts <= $future[1]);
|
|
if (!$ts || (!$in_month && !$in_future)) {
|
|
continue;
|
|
}
|
|
|
|
$items[] = $this->build_seance_data($sid, $seminar_id, $seminar_title, $seminar_permalink);
|
|
}
|
|
}
|
|
|
|
// Sort by séance date: seminars naturally order by their earliest séance
|
|
// when grouped by first appearance (see render / export grouping).
|
|
usort($items, fn($a, $b) => strcmp($a['date_debut'], $b['date_debut']));
|
|
|
|
return $items;
|
|
}
|
|
|
|
/**
|
|
* Resolve the parent seminar (cat 11) that lists a given séance in its
|
|
* `seances` meta. Mirrors the reverse lookup used by the theme's
|
|
* #seance-{ID} redirect. Returns 0 when none is found.
|
|
*/
|
|
public static function get_seminar_id_for_seance(int $seance_id): int {
|
|
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
|
|
));
|
|
|
|
return (int) $parent_id;
|
|
}
|
|
|
|
/**
|
|
* Query a single category with the appropriate window expression.
|
|
*/
|
|
private function query_category(int $cat_id, string $window_type, int $month_start, int $month_end, array $future): array {
|
|
global $wpdb;
|
|
|
|
// Fin de fenêtre (UNIX_TIMESTAMP) : seul critère de filtrage restant.
|
|
switch ($window_type) {
|
|
case 'datetime_to_fin':
|
|
$end_expr = $this->sql_datetime_to_fin_end();
|
|
break;
|
|
|
|
case 'debut_minus35_to_fin':
|
|
$end_expr = $this->sql_debut_to_fin_end();
|
|
break;
|
|
|
|
case 'datetime_plus3m':
|
|
$end_expr = $this->sql_datetime_plus3m_end();
|
|
break;
|
|
|
|
case 'datetime_plus35d':
|
|
$end_expr = $this->sql_datetime_plus35d_end();
|
|
break;
|
|
|
|
default:
|
|
return [];
|
|
}
|
|
|
|
// Date d'événement (date_de_debut > datetime > post_date), et non date
|
|
// de publication : sert au tri et au plafond « à venir ».
|
|
$order_expr = $this->sql_event_date_order();
|
|
|
|
$sql = $wpdb->prepare(
|
|
"SELECT DISTINCT p.ID, p.post_title, p.post_date
|
|
FROM {$wpdb->posts} p
|
|
INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id = p.ID
|
|
INNER JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id
|
|
AND tt.taxonomy = 'category' AND tt.term_id = %d
|
|
LEFT JOIN {$wpdb->postmeta} pm_dt ON pm_dt.post_id = p.ID AND pm_dt.meta_key = 'datetime'
|
|
LEFT JOIN {$wpdb->postmeta} pm_deb ON pm_deb.post_id = p.ID AND pm_deb.meta_key = 'date_de_debut'
|
|
LEFT JOIN {$wpdb->postmeta} pm_fin ON pm_fin.post_id = p.ID AND pm_fin.meta_key = 'date_de_fin'
|
|
WHERE p.post_type = 'post'
|
|
AND p.post_status = 'publish'
|
|
AND {$end_expr} >= %d
|
|
AND (
|
|
UNIX_TIMESTAMP({$order_expr}) <= %d
|
|
OR ( UNIX_TIMESTAMP({$order_expr}) >= %d AND UNIX_TIMESTAMP({$order_expr}) <= %d )
|
|
)
|
|
ORDER BY {$order_expr} ASC",
|
|
$cat_id,
|
|
$month_start,
|
|
$month_end,
|
|
$future[0],
|
|
$future[1]
|
|
);
|
|
|
|
$rows = $wpdb->get_results($sql);
|
|
if (!$rows) {
|
|
return [];
|
|
}
|
|
|
|
$posts = [];
|
|
foreach ($rows as $row) {
|
|
$posts[] = $this->build_post_data((int) $row->ID, $row->post_title, $row->post_date);
|
|
}
|
|
return $posts;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// SQL window expression helpers (return raw SQL strings, not prepared)
|
|
// -------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Date d'événement servant au tri : date_de_debut, sinon datetime, sinon
|
|
* post_date — même ordre de priorité que le thème (post-card-helpers.php,
|
|
* thalim_get_agenda_card_data()). Renvoie une chaîne date comparable.
|
|
*/
|
|
private function sql_event_date_order(): string {
|
|
return "CASE
|
|
WHEN pm_deb.meta_value IS NOT NULL
|
|
AND pm_deb.meta_value != ''
|
|
AND LEFT(pm_deb.meta_value, 4) != '0000'
|
|
THEN pm_deb.meta_value
|
|
WHEN pm_dt.meta_value IS NOT NULL
|
|
AND pm_dt.meta_value != ''
|
|
AND LEFT(pm_dt.meta_value, 4) != '0000'
|
|
THEN pm_dt.meta_value
|
|
ELSE p.post_date
|
|
END";
|
|
}
|
|
|
|
/**
|
|
* UNIX_TIMESTAMP of: date_de_fin if valid, else datetime if valid, else post_date
|
|
*/
|
|
private function sql_datetime_to_fin_end(): string {
|
|
return "UNIX_TIMESTAMP(CASE
|
|
WHEN pm_fin.meta_value IS NOT NULL
|
|
AND pm_fin.meta_value != ''
|
|
AND LEFT(pm_fin.meta_value, 4) != '0000'
|
|
THEN pm_fin.meta_value
|
|
WHEN pm_dt.meta_value IS NOT NULL
|
|
AND pm_dt.meta_value != ''
|
|
AND LEFT(pm_dt.meta_value, 4) != '0000'
|
|
THEN pm_dt.meta_value
|
|
ELSE p.post_date
|
|
END)";
|
|
}
|
|
|
|
/**
|
|
* UNIX_TIMESTAMP of: date_de_fin if valid, else date_de_debut if valid, else post_date
|
|
*/
|
|
private function sql_debut_to_fin_end(): string {
|
|
return "UNIX_TIMESTAMP(CASE
|
|
WHEN pm_fin.meta_value IS NOT NULL
|
|
AND pm_fin.meta_value != ''
|
|
AND LEFT(pm_fin.meta_value, 4) != '0000'
|
|
THEN pm_fin.meta_value
|
|
WHEN pm_deb.meta_value IS NOT NULL
|
|
AND pm_deb.meta_value != ''
|
|
AND LEFT(pm_deb.meta_value, 4) != '0000'
|
|
THEN pm_deb.meta_value
|
|
ELSE p.post_date
|
|
END)";
|
|
}
|
|
|
|
/**
|
|
* UNIX_TIMESTAMP de (date d'événement) + 3 mois.
|
|
*
|
|
* La base est la date d'événement, pas la date de publication : sinon un
|
|
* contenu annoncé longtemps à l'avance sort de la fenêtre avant d'avoir
|
|
* eu lieu, et manque dans la newsletter de son propre mois. Pour les
|
|
* ouvrages et articles, qui n'ont pas de date_de_debut, l'expression
|
|
* retombe sur `datetime` — comportement inchangé.
|
|
*/
|
|
private function sql_datetime_plus3m_end(): string {
|
|
return "UNIX_TIMESTAMP(DATE_ADD(" . $this->sql_event_date_order() . ", INTERVAL 3 MONTH))";
|
|
}
|
|
|
|
/**
|
|
* UNIX_TIMESTAMP de (date d'événement) + 35 jours. Même raison que
|
|
* ci-dessus : la fenêtre suit l'événement, pas sa date de publication.
|
|
*/
|
|
private function sql_datetime_plus35d_end(): string {
|
|
return "(UNIX_TIMESTAMP(" . $this->sql_event_date_order() . ") + 3024000)";
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Post data builder
|
|
// -------------------------------------------------------------------------
|
|
|
|
private function build_post_data(int $post_id, string $post_title, string $post_date): array {
|
|
return [
|
|
'id' => $post_id,
|
|
'title' => $post_title,
|
|
'permalink' => get_permalink($post_id),
|
|
'datetime' => get_post_meta($post_id, 'datetime', true) ?: '',
|
|
'date_debut' => get_post_meta($post_id, 'date_de_debut', true) ?: '',
|
|
'date_fin' => get_post_meta($post_id, 'date_de_fin', true) ?: '',
|
|
'post_date' => $post_date,
|
|
'membres' => $this->get_post_membres($post_id),
|
|
'autrepersonnes' => get_post_meta($post_id, 'autrepersonnes', true) ?: '',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Resolve 'membres' postmeta rows to display names.
|
|
*
|
|
* @return string[]
|
|
*/
|
|
public function get_post_membres(int $post_id): array {
|
|
$uids = get_post_meta($post_id, 'membres', false);
|
|
$names = [];
|
|
foreach ($uids as $uid) {
|
|
$user = get_userdata((int) $uid);
|
|
if ($user) {
|
|
$names[] = $user->display_name;
|
|
}
|
|
}
|
|
return $names;
|
|
}
|
|
}
|