Newsletter */ if (!defined('ABSPATH')) { exit; } class Thalim_NL_Admin_Page { // ------------------------------------------------------------------------- // Main render // ------------------------------------------------------------------------- public function render() { if (!current_user_can('edit_others_posts')) { wp_die('Unauthorized'); } // Determine active month $year_month = sanitize_text_field($_GET['nl_month'] ?? ''); if (!preg_match('/^\d{4}-\d{2}$/', $year_month)) { $year_month = date('Y-m'); } // Handle synchronous save $save_message = null; if (isset($_POST['thalim_nl_save']) && wp_verify_nonce($_POST['_wpnonce'] ?? '', 'thalim_nl_save')) { $save_message = $this->handle_save_newsletter(); // Re-read month after save (may have been changed in form) $posted_month = sanitize_text_field($_POST['nl_month'] ?? ''); if (preg_match('/^\d{4}-\d{2}$/', $posted_month)) { $year_month = $posted_month; } } // Load existing newsletter for this month (if any) $existing = $this->get_newsletter_post_for_month($year_month); $existing_sections = []; $intro_content = ''; $conclusion_content = ''; $subscribe_url = ''; $unsubscribe_url = ''; if ($existing) { $existing_sections = json_decode(get_post_meta($existing->ID, '_newsletter_sections', true), true) ?: []; $intro_content = get_post_meta($existing->ID, '_newsletter_intro', true) ?: ''; $conclusion_content = get_post_meta($existing->ID, '_newsletter_conclusion', true) ?: ''; $subscribe_url = get_post_meta($existing->ID, '_newsletter_subscribe_url', true) ?: ''; $unsubscribe_url = get_post_meta($existing->ID, '_newsletter_unsubscribe_url', true) ?: ''; } // Query posts for current month $query = new Thalim_NL_Post_Query(); $month_data = $query->get_posts_for_month($year_month); // Past newsletters $past_newsletters = get_posts([ 'post_type' => 'post', 'post_status' => ['draft', 'publish', 'pending'], 'posts_per_page' => -1, 'meta_key' => '_newsletter_month', 'orderby' => 'meta_value', 'order' => 'DESC', 'lang' => '', ]); $past_newsletters = array_filter($past_newsletters, function ($p) { return !empty(get_post_meta($p->ID, '_newsletter_month', true)); }); ?>

Newsletter THALIM

Composer

'nl_intro', 'media_buttons' => false, 'tinymce' => [ 'toolbar1' => 'bold,italic,bullist,numlist,blockquote,link,unlink', 'toolbar2' => '', ], 'quicktags' => ['buttons' => 'strong,em,ul,ol,li,link,close'], 'editor_height' => 200, ]); ?>
render_sections_html($month_data, $existing ? $existing_sections : null); ?>
'nl_conclusion', 'media_buttons' => false, 'tinymce' => [ 'toolbar1' => 'bold,italic,bullist,numlist,blockquote,link,unlink', 'toolbar2' => '', ], 'quicktags' => ['buttons' => 'strong,em,ul,ol,li,link,close'], 'editor_height' => 200, ]); ?>

Newsletters enregistrées

Aucune newsletter enregistrée.

ID, '_newsletter_month', true); $label = $this->format_month_label($nl_month); $edit_url = get_edit_post_link($nl->ID); ?>
Mois Actions
Modifier
[post_id,...]) for an * existing newsletter. Pass null for a fresh month: * every item is then checked by default. */ public function render_sections_html(array $month_data, ?array $checked_ids = null): string { $groups = Thalim_NL_Post_Query::get_eligible_categories(); // No saved newsletter yet → everything checked by default. $check_all = ($checked_ids === null); ob_start(); $has_content = false; foreach ($groups as $parent_id => $group) { // Collect all cat IDs in this group: parent + children $cats_in_group = [$parent_id => $group['name']]; foreach ($group['children'] as $cid => $cname) { $cats_in_group[$cid] = $cname; } // Check if any cat in this group has posts $group_has_posts = false; foreach ($cats_in_group as $cid => $cname) { if (!empty($month_data[$cid])) { $group_has_posts = true; break; } } if (!$group_has_posts) { continue; } $has_content = true; ?>

$label): $posts = $month_data[$cat_id] ?? []; if (empty($posts)) { continue; } $count = count($posts); $checked_in_section = $check_all ? [] : (array) ($checked_ids[$cat_id] ?? $checked_ids[(string) $cat_id] ?? []); $has_checked = $check_all || !empty($checked_in_section); // Restore the saved drag-and-drop order ($checked_in_section is // stored in submit order). For seminars this also reorders the // seminar groups, since grouping below follows first appearance. if (!$check_all) { $posts = $this->reorder_posts_by_saved($posts, $checked_in_section); } ?>
> ()
$seance['seminar_title'], 'permalink' => $seance['seminar_permalink'], 'seances' => [], ]; } $by_seminar[$sem_id]['seances'][] = $seance; } ?>
Aucun contenu trouvé pour ce mois.

'; } return $html; } /** * Small date hint shown next to each post title in the checklist. */ private function format_post_date_hint(array $post): string { $candidates = [$post['date_debut'], $post['datetime'], $post['post_date']]; foreach ($candidates as $raw) { if ($raw && !str_starts_with($raw, '0000-00-00')) { $ts = strtotime($raw); if ($ts) { return date_i18n('j M Y', $ts); } } } return ''; } /** * Reorder $posts so items whose 'id' appears in $order come first, in * $order's sequence; the rest keep their query order, appended after. * Restores the editor's saved drag-and-drop order in the admin UI. */ private function reorder_posts_by_saved(array $posts, array $order): array { if (empty($order)) { return $posts; } $rank = array_flip(array_map('intval', array_values($order))); $keyed = []; foreach ($posts as $idx => $post) { $keyed[] = [ 'post' => $post, 'rank' => $rank[(int) $post['id']] ?? PHP_INT_MAX, 'idx' => $idx, ]; } // Sort by saved rank, then original index (stable for un-ranked items). usort($keyed, fn($a, $b) => [$a['rank'], $a['idx']] <=> [$b['rank'], $b['idx']]); return array_map(fn($x) => $x['post'], $keyed); } /** * Date · heure · lieu hint shown next to each séance checkbox. */ private function format_seance_hint(array $seance): string { $parts = []; if (!empty($seance['date_debut']) && !str_starts_with($seance['date_debut'], '0000-00-00')) { $ts = strtotime($seance['date_debut']); if ($ts) { $parts[] = date_i18n('j M Y', $ts); } } if (!empty($seance['heure_de_debut'])) { $time = $seance['heure_de_debut']; if (!empty($seance['heure_de_fin'])) { $time .= '–' . $seance['heure_de_fin']; } $parts[] = $time; } if (!empty($seance['lieu'])) { $parts[] = $seance['lieu']; } return implode(' · ', $parts); } // ------------------------------------------------------------------------- // Save // ------------------------------------------------------------------------- private function handle_save_newsletter(): array { $year_month = sanitize_text_field($_POST['nl_month'] ?? ''); if (!preg_match('/^\d{4}-\d{2}$/', $year_month)) { return ['error', 'Mois invalide.']; } $intro = wp_kses_post(wp_unslash($_POST['nl_intro'] ?? '')); $conclusion = wp_kses_post(wp_unslash($_POST['nl_conclusion'] ?? '')); $subscribe_url = esc_url_raw(wp_unslash($_POST['nl_subscribe_url'] ?? '')); $unsubscribe_url = esc_url_raw(wp_unslash($_POST['nl_unsubscribe_url'] ?? '')); // Build sections: cat_id => [post_id, ...] $raw_sections = $_POST['nl_sections'] ?? []; $sections = []; foreach ($raw_sections as $cat_id => $post_ids) { $cat_id = (int) $cat_id; $clean_ids = array_map('intval', (array) $post_ids); $clean_ids = array_filter($clean_ids); if (!empty($clean_ids)) { $sections[$cat_id] = array_values($clean_ids); } } $post_id = $this->save_newsletter_post($year_month, $intro, $conclusion, $sections, $subscribe_url, $unsubscribe_url); if (is_wp_error($post_id)) { return ['error', $post_id->get_error_message()]; } $label = $this->format_month_label($year_month); return ['success', "Newsletter «\u{00A0}{$label}\u{00A0}» enregistrée et publiée (ID\u{00A0}: {$post_id})."]; } private function save_newsletter_post(string $year_month, string $intro, string $conclusion, array $sections, string $subscribe_url = '', string $unsubscribe_url = ''): int|WP_Error { global $wpdb; $label = 'Newsletter — ' . $this->format_month_label($year_month); $existing = $this->get_newsletter_post_for_month($year_month); // First pass: insert/update with placeholder content (meta must exist before exporter runs) $post_args = [ 'post_title' => $label, 'post_content' => '', 'post_status' => 'publish', 'post_type' => 'post', ]; if ($existing) { $post_args['ID'] = $existing->ID; $post_id = wp_update_post($post_args, true); } else { $post_id = wp_insert_post($post_args, true); } if (is_wp_error($post_id)) { return $post_id; } // Save all meta so the exporter can read them update_post_meta($post_id, '_newsletter_month', $year_month); update_post_meta($post_id, '_newsletter_intro', $intro); update_post_meta($post_id, '_newsletter_conclusion', $conclusion); update_post_meta($post_id, '_newsletter_sections', wp_json_encode($sections)); update_post_meta($post_id, '_newsletter_subscribe_url', $subscribe_url); update_post_meta($post_id, '_newsletter_unsubscribe_url', $unsubscribe_url); // Generate full rendered HTML and store as post_content $exporter = new Thalim_NL_HTML_Exporter(); $full_html = $exporter->generate(get_post($post_id)); wp_update_post(['ID' => $post_id, 'post_content' => $full_html]); $this->do_triple_storage_category($post_id, THALIM_NL_CAT_NEWSLETTER); if (function_exists('pll_set_post_language')) { pll_set_post_language($post_id, 'fr'); } return $post_id; } /** * Assign a category using the Pods triple-storage pattern (same as HAL importer). */ private function do_triple_storage_category(int $post_id, int $cat_id): void { global $wpdb; // 1. Native WP category assignment wp_set_post_categories($post_id, [$cat_id]); // 2. Pods postmeta: single integer update_post_meta($post_id, 'categorie', $cat_id); // 3. Pods _pods_ meta: serialized array update_post_meta($post_id, '_pods_categorie', [$cat_id]); // 4. wp_podsrel row $wpdb->delete( $wpdb->prefix . 'podsrel', [ 'pod_id' => THALIM_NL_POD_ID_POST, 'field_id' => THALIM_NL_FIELD_ID_CAT, 'item_id' => $post_id, ], ['%d', '%d', '%d'] ); $wpdb->insert( $wpdb->prefix . 'podsrel', [ 'pod_id' => THALIM_NL_POD_ID_POST, 'field_id' => THALIM_NL_FIELD_ID_CAT, 'item_id' => $post_id, 'related_pod_id' => 0, 'related_field_id' => 0, 'related_item_id' => $cat_id, 'weight' => 0, ], ['%d', '%d', '%d', '%d', '%d', '%d', '%d'] ); } // ------------------------------------------------------------------------- // AJAX handlers // ------------------------------------------------------------------------- public function handle_ajax_load_month(): void { check_ajax_referer('thalim_newsletter_ajax', '_wpnonce'); if (!current_user_can('edit_others_posts')) { wp_send_json_error('Unauthorized', 403); } $year_month = sanitize_text_field($_POST['month'] ?? ''); if (!preg_match('/^\d{4}-\d{2}$/', $year_month)) { wp_send_json_error('Invalid month format'); } $query = new Thalim_NL_Post_Query(); $month_data = $query->get_posts_for_month($year_month); $existing = $this->get_newsletter_post_for_month($year_month); $existing_sections = []; $intro_content = ''; $conclusion_content = ''; $subscribe_url = ''; $unsubscribe_url = ''; $has_existing = false; if ($existing) { $has_existing = true; $existing_sections = json_decode(get_post_meta($existing->ID, '_newsletter_sections', true), true) ?: []; $intro_content = get_post_meta($existing->ID, '_newsletter_intro', true) ?: ''; $conclusion_content = get_post_meta($existing->ID, '_newsletter_conclusion', true) ?: ''; $subscribe_url = get_post_meta($existing->ID, '_newsletter_subscribe_url', true) ?: ''; $unsubscribe_url = get_post_meta($existing->ID, '_newsletter_unsubscribe_url', true) ?: ''; } $html = $this->render_sections_html($month_data, $existing ? $existing_sections : null); wp_send_json_success([ 'html' => $html, 'intro' => $intro_content, 'conclusion' => $conclusion_content, 'subscribe_url' => $subscribe_url, 'unsubscribe_url' => $unsubscribe_url, 'has_existing' => $has_existing, ]); } public function handle_ajax_export_html(): void { check_ajax_referer('thalim_newsletter_ajax', '_wpnonce'); if (!current_user_can('edit_others_posts')) { wp_send_json_error('Unauthorized', 403); } $post_id = (int) ($_POST['post_id'] ?? 0); if (!$post_id) { wp_send_json_error('Missing post_id'); } $post = get_post($post_id); if (!$post) { wp_send_json_error('Post not found'); } // post_content already holds the full rendered HTML (set on save) wp_send_json_success(['html' => $post->post_content]); } // ------------------------------------------------------------------------- // Helpers // ------------------------------------------------------------------------- public function get_newsletter_post_for_month(string $year_month): ?WP_Post { $posts = get_posts([ 'post_type' => 'post', 'post_status' => ['draft', 'publish', 'pending'], 'posts_per_page' => 1, 'meta_key' => '_newsletter_month', 'meta_value' => $year_month, 'lang' => '', // Polylang: all languages ]); return $posts[0] ?? null; } private function format_month_label(string $year_month): string { $ts = strtotime($year_month . '-01'); if (!$ts) { return $year_month; } // e.g. "Mars 2026" return ucfirst(date_i18n('F Y', $ts)); } }