Initial commit
This commit is contained in:
550
includes/class-admin-page.php
Normal file
550
includes/class-admin-page.php
Normal file
@@ -0,0 +1,550 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin Page Class — Tools > 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));
|
||||
});
|
||||
|
||||
?>
|
||||
<div class="wrap thalim-nl-wrap">
|
||||
<h1>Newsletter THALIM</h1>
|
||||
|
||||
<?php if ($save_message): ?>
|
||||
<div class="notice notice-<?php echo esc_attr($save_message[0]); ?> is-dismissible">
|
||||
<p><?php echo esc_html($save_message[1]); ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="thalim-nl-layout">
|
||||
|
||||
<!-- ============================================================
|
||||
SECTION 1 — COMPOSE
|
||||
============================================================ -->
|
||||
<div class="thalim-nl-compose">
|
||||
<h2>Composer</h2>
|
||||
|
||||
<form method="post" id="thalim-nl-form">
|
||||
<?php wp_nonce_field('thalim_nl_save'); ?>
|
||||
<input type="hidden" name="thalim_nl_save" value="1">
|
||||
|
||||
<div class="thalim-nl-month-row">
|
||||
<label for="thalim-nl-month"><strong>Mois :</strong></label>
|
||||
<input
|
||||
type="month"
|
||||
id="thalim-nl-month"
|
||||
name="nl_month"
|
||||
value="<?php echo esc_attr($year_month); ?>"
|
||||
>
|
||||
<span class="thalim-nl-spinner spinner"></span>
|
||||
</div>
|
||||
|
||||
<div class="thalim-nl-intro">
|
||||
<label><strong>Introduction :</strong></label>
|
||||
<?php
|
||||
wp_editor($intro_content, 'thalim_nl_intro', [
|
||||
'textarea_name' => '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,
|
||||
]);
|
||||
?>
|
||||
</div>
|
||||
|
||||
<div id="thalim-nl-sections-wrap">
|
||||
<?php echo $this->render_sections_html($month_data, $existing_sections); ?>
|
||||
</div>
|
||||
|
||||
<div class="thalim-nl-conclusion">
|
||||
<label><strong>Conclusion :</strong></label>
|
||||
<?php
|
||||
wp_editor($conclusion_content, 'thalim_nl_conclusion', [
|
||||
'textarea_name' => '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,
|
||||
]);
|
||||
?>
|
||||
</div>
|
||||
|
||||
<div class="thalim-nl-links-row">
|
||||
<div class="thalim-nl-link-field">
|
||||
<label for="thalim-nl-subscribe"><strong>Lien d'abonnement :</strong></label>
|
||||
<input type="url" id="thalim-nl-subscribe" name="nl_subscribe_url"
|
||||
value="<?php echo esc_attr($subscribe_url); ?>"
|
||||
placeholder="https://…" class="regular-text">
|
||||
</div>
|
||||
<div class="thalim-nl-link-field">
|
||||
<label for="thalim-nl-unsubscribe"><strong>Lien de désabonnement :</strong></label>
|
||||
<input type="url" id="thalim-nl-unsubscribe" name="nl_unsubscribe_url"
|
||||
value="<?php echo esc_attr($unsubscribe_url); ?>"
|
||||
placeholder="https://…" class="regular-text">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="submit">
|
||||
<button type="submit" class="button button-primary">Enregistrer</button>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- ============================================================
|
||||
SECTION 2 — PAST NEWSLETTERS
|
||||
============================================================ -->
|
||||
<div class="thalim-nl-past">
|
||||
<h2>Newsletters enregistrées</h2>
|
||||
|
||||
<?php if (empty($past_newsletters)): ?>
|
||||
<p>Aucune newsletter enregistrée.</p>
|
||||
<?php else: ?>
|
||||
<table class="widefat thalim-nl-past-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mois</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($past_newsletters as $nl): ?>
|
||||
<?php
|
||||
$nl_month = get_post_meta($nl->ID, '_newsletter_month', true);
|
||||
$label = $this->format_month_label($nl_month);
|
||||
$edit_url = get_edit_post_link($nl->ID);
|
||||
?>
|
||||
<tr>
|
||||
<td><?php echo esc_html($label); ?></td>
|
||||
<td>
|
||||
<a href="<?php echo esc_url($edit_url); ?>" class="button button-small">Modifier</a>
|
||||
<button
|
||||
type="button"
|
||||
class="button button-small thalim-nl-preview"
|
||||
data-post-id="<?php echo esc_attr($nl->ID); ?>"
|
||||
>Prévisualisation</button>
|
||||
<button
|
||||
type="button"
|
||||
class="button button-small thalim-nl-download"
|
||||
data-post-id="<?php echo esc_attr($nl->ID); ?>"
|
||||
data-filename="newsletter-THALIM-<?php echo esc_attr($this->format_month_label($nl_month)); ?>.html"
|
||||
>Télécharger</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
</div><!-- .thalim-nl-layout -->
|
||||
</div><!-- .wrap -->
|
||||
<?php
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Sections HTML (shared between initial render and AJAX)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function render_sections_html(array $month_data, array $checked_ids = []): string {
|
||||
$groups = Thalim_NL_Post_Query::get_eligible_categories();
|
||||
|
||||
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;
|
||||
?>
|
||||
<div class="thalim-nl-group">
|
||||
<h3 class="thalim-nl-group-title"><?php echo esc_html($group['name']); ?></h3>
|
||||
<?php foreach ($cats_in_group as $cat_id => $label):
|
||||
$posts = $month_data[$cat_id] ?? [];
|
||||
if (empty($posts)) {
|
||||
continue;
|
||||
}
|
||||
$count = count($posts);
|
||||
$checked_in_section = (array) ($checked_ids[$cat_id] ?? $checked_ids[(string) $cat_id] ?? []);
|
||||
$has_checked = !empty($checked_in_section);
|
||||
?>
|
||||
<details class="thalim-nl-section" <?php if ($has_checked) echo 'open'; ?>>
|
||||
<summary class="thalim-nl-section-summary">
|
||||
<?php echo esc_html($label); ?>
|
||||
<span class="thalim-nl-count">(<?php echo $count; ?>)</span>
|
||||
</summary>
|
||||
<div class="thalim-nl-section-body">
|
||||
<?php foreach ($posts as $post): ?>
|
||||
<label class="thalim-nl-post-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="nl_sections[<?php echo (int) $cat_id; ?>][]"
|
||||
value="<?php echo (int) $post['id']; ?>"
|
||||
<?php checked(in_array((int) $post['id'], $checked_in_section)); ?>
|
||||
>
|
||||
<span class="thalim-nl-post-title"><?php echo esc_html($post['title']); ?></span>
|
||||
<a href="<?php echo esc_url($post['permalink']); ?>" target="_blank" rel="noopener"
|
||||
class="thalim-nl-post-view" title="Voir sur le site">
|
||||
<span class="dashicons dashicons-visibility"></span>
|
||||
</a>
|
||||
<span class="thalim-nl-post-date"><?php echo esc_html($this->format_post_date_hint($post)); ?></span>
|
||||
</label>
|
||||
<?php if (!empty($post['seances'])): ?>
|
||||
<ul class="thalim-nl-seances-list">
|
||||
<?php foreach ($post['seances'] as $seance): ?>
|
||||
<li class="thalim-nl-seance-item">
|
||||
<span class="thalim-nl-seance-title"><?php echo esc_html($seance['title']); ?></span>
|
||||
<span class="thalim-nl-seance-meta">
|
||||
<?php
|
||||
$parts = [];
|
||||
if ($seance['date_debut']) {
|
||||
$ts = strtotime($seance['date_debut']);
|
||||
$parts[] = $ts ? date_i18n('j M Y', $ts) : '';
|
||||
}
|
||||
if ($seance['heure_de_debut']) {
|
||||
$time = $seance['heure_de_debut'];
|
||||
if ($seance['heure_de_fin']) $time .= '–' . $seance['heure_de_fin'];
|
||||
$parts[] = $time;
|
||||
}
|
||||
if ($seance['lieu']) {
|
||||
$parts[] = $seance['lieu'];
|
||||
}
|
||||
echo esc_html(implode(' · ', array_filter($parts)));
|
||||
?>
|
||||
</span>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</details>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
$html = ob_get_clean();
|
||||
if (!$has_content) {
|
||||
$html = '<p class="thalim-nl-empty">Aucun contenu trouvé pour ce mois.</p>';
|
||||
}
|
||||
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 '';
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 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_sections);
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user