Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions assets/js/src/components/admin/AnnouncementEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { __ } from '@wordpress/i18n';
import { useState, useEffect, useRef, useCallback } from '@wordpress/element';
import { apiFetch } from '../../util';
import { useEventProvider } from '../providers/EventProvider';
import { getTimezoneOptions, getUserTimezone } from '../../timezones';

// Event Search Modal Component with infinite scroll
const EventSearchModal = ({ isOpen, onClose, onSelectEvent, onRemoveEvent, linkedEventRefs, getRefKey }) => {
Expand Down Expand Up @@ -575,6 +576,17 @@ const AnnouncementEditor = () => {
fetchSubscriptionSettings();
}, [postType]);

// Determine if this is a new announcement
const isNewAnnouncement = postStatus === 'auto-draft';

// Auto-set timezone for new announcements based on browser timezone
useEffect(() => {
if (isNewAnnouncement && !meta.display_timezone) {
const detectedTimezone = getUserTimezone();
editPost({ meta: { ...meta, display_timezone: detectedTimezone } });
}
}, [isNewAnnouncement, meta.display_timezone]);

if (postType !== 'mayo_announcement') return null;

// Filter service bodies by subscription settings
Expand Down Expand Up @@ -898,6 +910,19 @@ const AnnouncementEditor = () => {
Leave empty to show indefinitely
</p>
</div>
<div style={{ marginTop: '12px' }}>
<SelectControl
label="Timezone"
value={meta.display_timezone || ''}
options={[
{ label: '-- No timezone set --', value: '' },
...getTimezoneOptions()
]}
onChange={value => updateMetaValue('display_timezone', value)}
__nextHasNoMarginBottom={true}
__next40pxDefaultSize={true}
/>
</div>
</PanelBody>

<PanelBody title="Priority" initialOpen={true}>
Expand Down
10 changes: 9 additions & 1 deletion assets/js/src/components/admin/EventBlockEditorSidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ const EventBlockEditorSidebar = () => {
editPost({ meta: { ...meta, [key]: value } });
};

// Auto-set timezone for new events based on browser timezone
useEffect(() => {
if (isNewEvent && !meta.timezone) {
const detectedTimezone = getUserTimezone();
updateMetaValue('timezone', detectedTimezone);
}
}, [isNewEvent, meta.timezone]);

const recurringPattern = meta.recurring_pattern || {
type: 'none',
interval: 1,
Expand Down Expand Up @@ -219,7 +227,7 @@ const EventBlockEditorSidebar = () => {
</div>
<SelectControl
label="Timezone"
value={meta.timezone || (isNewEvent ? getUserTimezone() : '')}
value={meta.timezone || ''}
options={[
{ label: '-- No timezone set --', value: '' },
...getTimezoneOptions()
Expand Down
10 changes: 9 additions & 1 deletion assets/js/src/components/public/EventAnnouncement.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import AnnouncementBanner from './AnnouncementBanner';
import AnnouncementModal from './AnnouncementModal';
import AnnouncementBellIcon from './AnnouncementBellIcon';
import { apiFetch } from '../../util';
import { getUserTimezone } from '../../timezones';

const EventAnnouncement = ({ settings = {} }) => {
const [announcements, setAnnouncements] = useState([]);
Expand Down Expand Up @@ -40,6 +41,9 @@ const EventAnnouncement = ({ settings = {} }) => {
return Date.now() - timestamp < twentyFourHours;
}, [getDismissalKey]);

// Get user's timezone for accurate time filtering
const userTimezone = getUserTimezone();

// Fetch announcements from the new announcements API
useEffect(() => {
const fetchAnnouncements = async () => {
Expand All @@ -48,6 +52,10 @@ const EventAnnouncement = ({ settings = {} }) => {
// Use the new announcements endpoint which handles date filtering server-side
let endpoint = '/announcements?per_page=20';

// Add timezone and current time for accurate end time filtering
endpoint += `&timezone=${encodeURIComponent(userTimezone)}`;
endpoint += `&current_time=${encodeURIComponent(new Date().toISOString())}`;

if (categories) {
endpoint += `&categories=${encodeURIComponent(categories)}`;
}
Expand Down Expand Up @@ -87,7 +95,7 @@ const EventAnnouncement = ({ settings = {} }) => {
};

fetchAnnouncements();
}, [categories, categoryRelation, tags, priority, orderBy, order, checkDismissed]);
}, [categories, categoryRelation, tags, priority, orderBy, order, userTimezone, checkDismissed]);

// Handle dismiss
const handleDismiss = useCallback(() => {
Expand Down
9 changes: 8 additions & 1 deletion assets/js/src/components/public/EventArchive.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import { useState, useEffect } from '@wordpress/element';
import { formatTimezone, apiFetch } from '../../util'; // Import the helper function
import { useEventProvider } from '../providers/EventProvider';
import { getUserTimezone } from '../../timezones';

const EventArchive = () => {
const [events, setEvents] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const { getServiceBodyName } = useEventProvider();

// Get user's current timezone
const userTimezone = getUserTimezone();

useEffect(() => {
const fetchEvents = async () => {
try {
const response = await apiFetch('/events?archive=true');
const endpoint = `/events?archive=true`
+ `&timezone=${encodeURIComponent(userTimezone)}`
+ `&current_time=${encodeURIComponent(new Date().toISOString())}`;
const response = await apiFetch(endpoint);

// Ensure we have a valid response and it's an array
if (response && Array.isArray(response)) {
Expand Down
2 changes: 2 additions & 0 deletions assets/js/src/components/public/EventList.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ const EventList = ({ widget = false, settings = {} }) => {
+ `&page=${page}`
+ `&per_page=${perPage}`
+ `&timezone=${encodeURIComponent(userTimezone)}`
+ `&current_time=${encodeURIComponent(new Date().toISOString())}`
+ `&archive=${archive}`
+ `&order=${order}`;

Expand Down Expand Up @@ -368,6 +369,7 @@ const EventList = ({ widget = false, settings = {} }) => {
+ `&tags=${tags}`
+ `&source_ids=${sourceIds}`
+ `&timezone=${encodeURIComponent(userTimezone)}`
+ `&current_time=${encodeURIComponent(new Date().toISOString())}`
+ `&order=${order}`
+ `&start_date=${startDate}`
+ `&end_date=${endDate}`
Expand Down
4 changes: 2 additions & 2 deletions assets/js/src/components/public/EventModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,10 @@ const EventModal = ({ event, timeFormat, onClose }) => {
</div>
)}

{event.source_id && event.source_id !== 'local' && (
{event.source && event.source.type === 'external' && (
<div className="mayo-event-modal-source">
<span className="dashicons dashicons-admin-site"></span>
<span>External Event</span>
<span>{event.source.name}</span>
</div>
)}
</div>
Expand Down
6 changes: 3 additions & 3 deletions assets/js/src/components/public/cards/EventCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,9 @@ const EventCard = ({ event, timeFormat, forceExpanded }) => {
{formatDateTimeDisplay(event, timeFormat)}
</span>
)}
{event.source_id && event.source_id !== 'local' && (
<span className="mayo-event-source">
External Event
{event.source && event.source.type === 'external' && (
<span className="mayo-event-source" title={`From ${event.source.name}`}>
{event.source.name}
</span>
)}
{event.meta.service_body && (
Expand Down
11 changes: 11 additions & 0 deletions includes/Announcement.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,17 @@ public static function register_meta_fields() {
}
]);

register_post_meta('mayo_announcement', 'display_timezone', [
'show_in_rest' => true,
'single' => true,
'type' => 'string',
'default' => '',
'sanitize_callback' => 'sanitize_text_field',
'auth_callback' => function() {
return current_user_can('edit_posts');
}
]);

register_post_meta('mayo_announcement', 'priority', [
'show_in_rest' => true,
'single' => true,
Expand Down
105 changes: 97 additions & 8 deletions includes/Rest/AnnouncementsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,19 @@ public static function get_announcements($request) {
$active_only = !isset($params['active']) || $params['active'] !== 'false';
$orderby = isset($params['orderby']) ? sanitize_text_field($params['orderby']) : 'date';
$order = isset($params['order']) ? strtoupper(sanitize_text_field($params['order'])) : '';
$timezone = isset($params['timezone']) ? urldecode(sanitize_text_field(wp_unslash($params['timezone']))) : wp_timezone_string();
$current_time = isset($params['current_time']) ? sanitize_text_field(wp_unslash($params['current_time'])) : null;

$today = current_time('Y-m-d');

// Create DateTime object for current time with proper timezone
if ($current_time) {
$now = new \DateTime($current_time);
$now->setTimezone(new \DateTimeZone($timezone));
} else {
$now = new \DateTime('now', new \DateTimeZone($timezone));
}

$args = [
'post_type' => 'mayo_announcement',
'post_status' => 'publish',
Expand Down Expand Up @@ -144,9 +154,46 @@ public static function get_announcements($request) {

$posts = get_posts($args);

// Post-filter based on end time if active_only is enabled
// The meta_query only checks dates, so we need to filter by time here
if ($active_only) {
$posts = array_filter($posts, function($post) use ($now, $timezone) {
$end_date_str = get_post_meta($post->ID, 'display_end_date', true);

// If no end date, the announcement is considered active
if (empty($end_date_str)) {
return true;
}

$end_time_str = get_post_meta($post->ID, 'display_end_time', true);

// Use the announcement's own timezone if set, otherwise fall back to request timezone
$announcement_timezone = get_post_meta($post->ID, 'display_timezone', true);
$effective_timezone = !empty($announcement_timezone) ? $announcement_timezone : $timezone;

// Create end DateTime with proper time and timezone
$tz = new \DateTimeZone($effective_timezone);
$end_datetime = new \DateTime($end_date_str, $tz);
if (!empty($end_time_str)) {
$time_parts = explode(':', $end_time_str);
$end_datetime->setTime(
(int)($time_parts[0] ?? 23),
(int)($time_parts[1] ?? 59),
(int)($time_parts[2] ?? 59)
);
} else {
// If no end time specified, use end of day
$end_datetime->setTime(23, 59, 59);
}

// Announcement is active if end datetime is in the future
return $end_datetime >= $now;
});
}

$announcements = [];
foreach ($posts as $post) {
$announcements[] = self::format_announcement($post);
$announcements[] = self::format_announcement($post, $now);
}

// Sort announcements
Expand Down Expand Up @@ -377,9 +424,10 @@ private static function send_submission_email($post_id, $params) {
* Format announcement data for API response
*
* @param \WP_Post $post
* @param \DateTime|null $now Current DateTime for is_active calculation
* @return array
*/
public static function format_announcement($post) {
public static function format_announcement($post, $now = null) {
$linked_refs = Announcement::get_linked_event_refs($post->ID);
$linked_event_data = [];

Expand Down Expand Up @@ -423,17 +471,57 @@ public static function format_announcement($post) {
}
}

// Calculate is_active based on display dates
$today = current_time('Y-m-d');
// Calculate is_active based on display dates and times
$display_start_date = get_post_meta($post->ID, 'display_start_date', true);
$display_start_time = get_post_meta($post->ID, 'display_start_time', true);
$display_end_date = get_post_meta($post->ID, 'display_end_date', true);
$display_end_time = get_post_meta($post->ID, 'display_end_time', true);
$display_timezone = get_post_meta($post->ID, 'display_timezone', true);

// Use provided $now or fall back to current time
if (!$now) {
$now = new \DateTime('now', new \DateTimeZone(wp_timezone_string()));
}

// Use the announcement's own timezone if set, otherwise get timezone from $now
$tz = !empty($display_timezone) ? new \DateTimeZone($display_timezone) : $now->getTimezone();

$is_active = true;
if ($display_start_date && $display_start_date > $today) {
$is_active = false;

// Check start date/time
if ($display_start_date) {
$start_datetime = new \DateTime($display_start_date, $tz);
if (!empty($display_start_time)) {
$time_parts = explode(':', $display_start_time);
$start_datetime->setTime(
(int)($time_parts[0] ?? 0),
(int)($time_parts[1] ?? 0),
(int)($time_parts[2] ?? 0)
);
} else {
$start_datetime->setTime(0, 0, 0);
}
if ($start_datetime > $now) {
$is_active = false;
}
}
if ($display_end_date && $display_end_date < $today) {
$is_active = false;

// Check end date/time
if ($display_end_date && $is_active) {
$end_datetime = new \DateTime($display_end_date, $tz);
if (!empty($display_end_time)) {
$time_parts = explode(':', $display_end_time);
$end_datetime->setTime(
(int)($time_parts[0] ?? 23),
(int)($time_parts[1] ?? 59),
(int)($time_parts[2] ?? 59)
);
} else {
$end_datetime->setTime(23, 59, 59);
}
if ($end_datetime < $now) {
$is_active = false;
}
}

$permalink = get_permalink($post->ID);
Expand All @@ -453,6 +541,7 @@ public static function format_announcement($post) {
'display_start_time' => get_post_meta($post->ID, 'display_start_time', true),
'display_end_date' => $display_end_date,
'display_end_time' => get_post_meta($post->ID, 'display_end_time', true),
'display_timezone' => $display_timezone,
'priority' => get_post_meta($post->ID, 'priority', true) ?: 'normal',
'service_body' => get_post_meta($post->ID, 'service_body', true) ?: '',
'is_active' => $is_active,
Expand Down
Loading