feature: Add calendar layout and occurrence handling functionality

- Introduced `calendar-layout.php` to manage month grid generation, occurrence intersection checks, and segment assignments for events.
- Added `occurrences.php` for normalizing, validating, and displaying event occurrences, including migration from legacy formats.
- Implemented functions for parsing dates, validating occurrence ranges, and formatting occurrences for display.
- Established hooks for synchronizing event occurrence data upon saving posts and validating ACF repeater fields.
This commit is contained in:
Keith Solomon
2026-07-06 10:26:36 -05:00
parent 6ba917bf9b
commit 42ff7754cb
16 changed files with 2205 additions and 344 deletions
+208 -34
View File
@@ -4,10 +4,18 @@
.aa-events-calendar-wrap { margin-bottom: 1.25rem; }
.aa-events-wrap {
margin-left: auto;
margin-right: auto;
max-width: 96rem;
}
.aa-events-calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
display: flex;
flex-wrap: wrap;
gap: 0.625rem;
justify-content: space-between;
margin-bottom: 0.625rem;
}
@@ -20,52 +28,218 @@
padding: 0.5rem 1rem;
text-decoration: none;
&:hover { background: #eee; }
}
.aa-events-calendar {
border-collapse: collapse;
table-layout: fixed;
width: 100%;
.aa-events-calendar-today-btn:hover,
.aa-events-calendar-today-btn:focus-visible {
background: #eee;
}
th, td {
border: 1px solid #aaa;
padding: 0.625rem;
text-align: left;
vertical-align: top;
}
.aa-events-calendar-grid {
border-left: 1px solid #aaa;
border-top: 1px solid #aaa;
max-width: 100%;
min-width: 0;
}
th {
background-color: #f5f5f5;
text-align: center;
}
.aa-events-calendar-weekdays {
display: grid;
grid-template-columns: repeat(7, minmax(0, 1fr));
}
td { height: 9.375rem; }
.aa-events-calendar-weekday {
background: #f5f5f5;
border-bottom: 1px solid #aaa;
border-right: 1px solid #aaa;
font-weight: bold;
min-width: 0;
overflow: hidden;
padding: 0.625rem 0.25rem;
text-align: center;
}
.day-number {
font-weight: bold;
margin-bottom: 0.3125rem;
}
.aa-events-calendar-weekday abbr {
text-decoration: none;
}
.today-cell {
background: #ffe9b3;
border: 2px solid #f7b500;
font-weight: bold;
}
.aa-events-calendar-week {
display: grid;
grid-template-columns: repeat(7, minmax(0, 1fr));
min-height: calc(7.5rem + (var(--aa-event-lanes, 1) * 1.75rem));
position: relative;
}
.events-list {
list-style: none;
margin: 0;
padding: 0;
.aa-events-calendar-day {
border-bottom: 1px solid #aaa;
border-right: 1px solid #aaa;
min-width: 0;
padding: 0.5rem;
}
li { margin-bottom: 0.3125rem; }
}
.aa-events-calendar-day .day-number {
display: block;
font-weight: bold;
margin-bottom: 0.3125rem;
}
.aa-events-calendar-day.is-outside-month {
background: #f7f7f7;
color: #666;
}
.aa-events-calendar-day.today-cell {
background: #fff3cf;
box-shadow: inset 0 0 0 2px #b57900;
font-weight: bold;
}
.aa-events-calendar-event {
align-items: center;
background: #245b87;
border-radius: 0.25rem;
box-sizing: border-box;
color: #fff;
display: flex;
gap: 0.25rem;
height: 1.5rem;
left: calc(((100% / 7) * var(--aa-event-column)) + 0.2rem);
line-height: 1.5rem;
min-width: 0;
overflow: hidden;
padding: 0 0.375rem;
position: absolute;
text-decoration: none;
top: calc(2.25rem + (var(--aa-event-lane) * 1.75rem));
white-space: nowrap;
width: calc(((100% / 7) * var(--aa-event-span)) - 0.4rem);
z-index: 1;
}
.aa-events-calendar-event > span:not(.aa-events-calendar-continuation) {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.aa-events-calendar-event.is-continuing-before {
border-bottom-left-radius: 0;
border-top-left-radius: 0;
}
.aa-events-calendar-event.is-continuing-after {
border-bottom-right-radius: 0;
border-top-right-radius: 0;
}
.aa-events-calendar-continuation {
flex: 0 0 auto;
font-weight: bold;
line-height: 1;
}
.aa-events-calendar-event:hover {
background: #163d5d;
text-decoration: underline;
}
.aa-events-calendar-event:focus-visible {
background: #163d5d;
outline: 3px solid #111;
outline-offset: 2px;
z-index: 2;
}
.aa-events-calendar-agenda {
display: none;
}
.aa-event-item .aa-event-details {
border-top: 1px solid #ddd;
border-top: 1px solid #333;
margin-top: 1.25rem;
padding-top: 1.25rem;
div { margin-bottom: 0.625rem; }
}
/* Events archive layout */
.aa-events-wrap .aa-events-grid {
display: grid;
gap: 1.5rem;
grid-template-columns: repeat(3, minmax(0, 1fr));
}
/* Optional: responsive adjustments */
@media (max-width: 1024px) {
.aa-events-wrap .aa-events-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
.aa-events-wrap .aa-events-grid { grid-template-columns: 1fr; }
.aa-events-calendar-desktop { display: none; }
.aa-events-calendar-agenda { display: block; }
.aa-events-calendar-agenda-list {
list-style: none;
margin: 0;
padding: 0;
}
.aa-events-agenda-item + .aa-events-agenda-item { margin-top: 0.75rem; }
.aa-events-agenda-item > a {
border: 1px solid #aaa;
border-left: 0.25rem solid #245b87;
border-radius: 0.25rem;
display: block;
min-width: 0;
overflow-wrap: anywhere;
padding: 0.75rem;
text-decoration: underline;
}
.aa-events-agenda-item > a:hover { background: #f5f5f5; }
.aa-events-agenda-item > a:focus-visible {
outline: 3px solid currentColor;
outline-offset: 2px;
}
.aa-events-agenda-title,
.aa-events-agenda-item time {
display: block;
overflow-wrap: anywhere;
}
.aa-events-agenda-title { font-weight: bold; }
.aa-events-agenda-item .screen-reader-text {
border: 0;
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
word-wrap: normal !important;
}
}
.aa-events-wrap .entry-header:has(.entry-thumbnail),
.aa-events-wrap .entry-wrap:has(.entry-thumbnail) {
display: flex;
justify-content: space-between;
.entry-thumbnail {
margin-left: 2rem;
width: 100%;
img {
height: auto;
width: 100%;
}
}
}
+9 -10
View File
@@ -30,21 +30,20 @@ get_header(); ?>
<footer class="entry-footer">
<div class="aa-event-details">
<?php $occurrences = aa_events_get_occurrences( get_the_ID() ); ?>
<?php if ( $occurrences ) : ?>
<div class="aa-event-date">
<strong><?php esc_html_e( 'Date:', 'aa-events' ); ?></strong>
<strong><?php echo esc_html( 1 === count( $occurrences ) ? __( 'Date:', 'aa-events' ) : __( 'Dates:', 'aa-events' ) ); ?></strong>
<?php
$event_raw = get_field( 'event_datetime' );
if ( $event_raw ) {
$tz = wp_timezone();
$ts = ( new DateTimeImmutable( $event_raw, $tz ) )->getTimestamp();
$human = wp_date( 'F j, Y, g:i A', $ts );
$iso_attr = wp_date( 'c', $ts );
?>
<time datetime="<?php echo esc_attr( $iso_attr ); ?>"><?php echo esc_html( $human ); ?></time>
<?php
echo '<span class="aa-event-multiple-dates">';
foreach ( $occurrences as $index => $occurrence ) {
echo aa_events_occurrence_time_markup( $occurrence ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Helper returns escaped markup.
echo ( $index < count( $occurrences ) - 1 ) ? '<br />' : '';
}
echo '</span>';
?>
</div>
<?php endif; ?>
<?php if ( get_field( 'event_cost' ) ) { ?>
<div class="aa-event-cost">
+244 -200
View File
@@ -6,226 +6,270 @@
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
exit;
}
// Get current month and year (read-only query vars; validate instead of nonce)
$month_param = 'aa_month';
$year_param = 'aa_year';
$month_input = filter_input(
INPUT_GET,
'month',
FILTER_VALIDATE_INT,
array(
'options' => array(
'min_range' => 1,
'max_range' => 12,
),
)
INPUT_GET,
$month_param,
FILTER_VALIDATE_INT,
array(
'options' => array(
'min_range' => 1,
'max_range' => 12,
),
)
);
if ( false === $month_input || null === $month_input ) {
$month_input = filter_input(
INPUT_GET,
'month',
FILTER_VALIDATE_INT,
array(
'options' => array(
'min_range' => 1,
'max_range' => 12,
),
)
);
}
$year_input = filter_input(
INPUT_GET,
'year',
FILTER_VALIDATE_INT,
array(
'options' => array(
// Choose a sane range for years.
'min_range' => 1970,
'max_range' => 2200,
),
)
INPUT_GET,
$year_param,
FILTER_VALIDATE_INT,
array(
'options' => array(
'min_range' => 1970,
'max_range' => 2200,
),
)
);
$curMonth = ( false !== $month_input && null !== $month_input ) ? (int) $month_input : (int) wp_date( 'n' );
$curYear = ( false !== $year_input && null !== $year_input ) ? (int) $year_input : (int) wp_date( 'Y' );
// Get previous and next month and year.
$prev_month = $curMonth - 1;
$prev_year = $curYear;
if ( $prev_month < 1 ) {
$prev_month = 12;
--$prev_year;
if ( false === $year_input || null === $year_input ) {
$year_input = filter_input(
INPUT_GET,
'year',
FILTER_VALIDATE_INT,
array(
'options' => array(
'min_range' => 1970,
'max_range' => 2200,
),
)
);
}
$next_month = $curMonth + 1;
$next_year = $curYear;
if ( $next_month > 12 ) {
$next_month = 1;
++$next_year;
}
// Get the number of days in the month.
$days_in_month = cal_days_in_month( CAL_GREGORIAN, $curMonth, $curYear );
// Get the first day of the month in site timezone.
$cur_month = ( false !== $month_input && null !== $month_input ) ? (int) $month_input : (int) wp_date( 'n' );
$cur_year = ( false !== $year_input && null !== $year_input ) ? (int) $year_input : (int) wp_date( 'Y' );
$tz = wp_timezone();
$first_ts = ( new DateTimeImmutable( sprintf( '%04d-%02d-01 00:00:00', $curYear, $curMonth ), $tz ) )->getTimestamp();
$first_day = (int) wp_date( 'N', $first_ts );
// Get events for the current month.
$events = new WP_Query(
array(
'post_type' => 'event',
'posts_per_page' => -1,
'meta_query' => array(
array(
'key' => 'event_datetime',
'value' => array( "$curYear-$curMonth-01 00:00:00", "$curYear-$curMonth-$days_in_month 23:59:59" ),
'compare' => 'BETWEEN',
'type' => 'DATETIME',
),
),
)
$start_of_week = (int) get_option( 'start_of_week', 1 );
if ( $start_of_week < 0 || $start_of_week > 6 ) {
$start_of_week = 0;
}
$month = aa_events_calendar_month( $cur_year, $cur_month, $start_of_week );
$weekdays = array(
array(
'abbr' => __( 'Sun', 'aa-events' ),
'label' => __( 'Sunday', 'aa-events' ),
),
array(
'abbr' => __( 'Mon', 'aa-events' ),
'label' => __( 'Monday', 'aa-events' ),
),
array(
'abbr' => __( 'Tue', 'aa-events' ),
'label' => __( 'Tuesday', 'aa-events' ),
),
array(
'abbr' => __( 'Wed', 'aa-events' ),
'label' => __( 'Wednesday', 'aa-events' ),
),
array(
'abbr' => __( 'Thu', 'aa-events' ),
'label' => __( 'Thursday', 'aa-events' ),
),
array(
'abbr' => __( 'Fri', 'aa-events' ),
'label' => __( 'Friday', 'aa-events' ),
),
array(
'abbr' => __( 'Sat', 'aa-events' ),
'label' => __( 'Saturday', 'aa-events' ),
),
);
$ordered_weekdays = array_merge( array_slice( $weekdays, $start_of_week ), array_slice( $weekdays, 0, $start_of_week ) );
$events_by_day = array();
if ( $events->have_posts() ) {
while ( $events->have_posts() ) {
$events->the_post();
$event_date = get_field( 'event_datetime' );
$event_ts = ( new DateTimeImmutable( $event_date, $tz ) )->getTimestamp();
$day = (int) wp_date( 'j', $event_ts );
$events_by_day[ $day ][] = '<a href="' . get_permalink() . '">' . get_the_title() . '</a>';
}
wp_reset_postdata();
$calendar_occurrences = array();
$events = new WP_Query(
array(
'post_type' => 'event',
'posts_per_page' => -1,
'post_status' => 'publish',
'fields' => 'ids',
'no_found_rows' => true,
'update_post_meta_cache' => true,
'update_post_term_cache' => false,
'meta_query' => array(
'relation' => 'OR',
array(
'relation' => 'AND',
array(
'key' => '_aa_events_occurrence_index_version',
'value' => AA_EVENTS_VERSION,
'compare' => '=',
),
array(
'key' => '_aa_events_sort_start',
'value' => $month['month_end']->format( 'Y-m-d 23:59:59' ),
'compare' => '<=',
'type' => 'DATETIME',
),
array(
'key' => '_aa_events_sort_end',
'value' => $month['month_start']->format( 'Y-m-d 00:00:00' ),
'compare' => '>=',
'type' => 'DATETIME',
),
),
array(
'relation' => 'OR',
array(
'key' => '_aa_events_occurrence_index_version',
'compare' => 'NOT EXISTS',
),
array(
'key' => '_aa_events_occurrence_index_version',
'value' => AA_EVENTS_VERSION,
'compare' => '!=',
),
),
),
)
);
foreach ( $events->posts as $event_id ) {
$event_title = get_the_title( $event_id );
$event_permalink = get_permalink( $event_id );
foreach ( aa_events_get_occurrences( $event_id ) as $occurrence ) {
if ( ! is_array( $occurrence ) || ! aa_events_occurrence_intersects_range( $occurrence, $month['month_start'], $month['month_end'] ) ) {
continue;
}
$occurrence['post_id'] = (int) $event_id;
$occurrence['title'] = $event_title;
$occurrence['permalink'] = $event_permalink;
$calendar_occurrences[] = $occurrence;
}
}
// Get today's date.
$todayDay = intval( wp_date( 'j' ) );
$todayMonth = intval( wp_date( 'n' ) );
$todayYear = intval( wp_date( 'Y' ) );
$calendar_occurrences = aa_events_sort_calendar_occurrences( $calendar_occurrences );
$calendar_weeks = aa_events_group_calendar_weeks( $calendar_occurrences, $month );
// Helper to check if today is in current view.
$isTodayInCurrentMonth = ( $curMonth === $todayMonth && $curYear === $todayYear );
?>
<?php
// Modify the day cell output to add highlight for today.
$originalEchoDayCell = function ( $day, $firstDay, $eventsByDay ) use ( $isTodayInCurrentMonth, $todayDay ) {
$cellClass = '';
if ( $day === $todayDay && $curMonth === $todayMonth && $curYear === $todayYear ) {
$cellClass = 'today-cell';
}
echo '<td' . ( $cellClass ? ' class="' . esc_attr( $cellClass ) . '"' : '' ) . '>';
echo '<div class="day-number">' . esc_html( $day ) . '</div>';
if ( isset( $eventsByDay[ $day ] ) ) {
echo '<ul class="events-list">';
foreach ( $eventsByDay[ $day ] as $event ) {
echo '<li>' . wp_kses_post( $event ) . '</li>';
}
echo '</ul>';
}
echo '</td>';
$today = new DateTimeImmutable( wp_date( 'Y-m-d' ) . ' 00:00:00', $tz );
$prev = $month['month_start']->modify( '-1 month' );
$next = $month['month_start']->modify( '+1 month' );
$heading_id = wp_unique_id( 'aa-events-calendar-heading-' );
$agenda_heading_id = wp_unique_id( 'aa-events-calendar-agenda-heading-' );
$month_label = wp_date( 'F Y', $month['month_start']->getTimestamp(), $tz );
$current_url = add_query_arg( array() );
$calendar_base_url = remove_query_arg( array( $month_param, $year_param, 'month', 'year' ), $current_url );
$calendar_url = static function ( DateTimeImmutable $date ) use ( $calendar_base_url, $month_param, $year_param ) {
return add_query_arg(
array(
$month_param => (int) $date->format( 'n' ),
$year_param => (int) $date->format( 'Y' ),
),
$calendar_base_url
);
};
// Overwrite the default day cell output in the table loop.
ob_start();
for ( $day = 1; $day <= $days_in_month; $day++ ) {
if ( ( $day + $first_day - 2 ) % 7 === 0 && $day > 1 ) {
echo '</tr><tr>';
}
$originalEchoDayCell( $day, $first_day, $events_by_day );
}
$calendarRows = ob_get_clean();
// Replace the original loop output with the new one.
$calendarOutput = preg_replace(
'/for\s*\(\s*\$day\s*=\s*1;\s*\$day\s*<=\s*\$days_in_month;\s*\$day\+\+\s*\)\s*\{.*?\}/s',
$calendarRows,
ob_get_contents()
);
?>
<div class="aa-events-calendar-wrap container">
<div class="aa-events-calendar-today-link" style="margin-bottom: 1em;">
<a href="?month=<?php echo esc_attr( $todayMonth ); ?>&year=<?php echo esc_attr( $todayYear ); ?>" class="aa-events-calendar-today-btn">
<?php esc_html_e( 'Today', 'aa-events' ); ?>
</a>
</div>
<div class="aa-events-calendar-today-link">
<a href="<?php echo esc_url( $calendar_url( $today ) ); ?>" class="aa-events-calendar-today-btn"><?php esc_html_e( 'Today', 'aa-events' ); ?></a>
</div>
<div class="aa-events-calendar-header">
<?php
// Calculate previous and next month names and years for aria-labels (site timezone)
$prev_ts = ( new DateTimeImmutable( sprintf( '%04d-%02d-01 00:00:00', $prev_year, $prev_month ), $tz ) )->getTimestamp();
$next_ts = ( new DateTimeImmutable( sprintf( '%04d-%02d-01 00:00:00', $next_year, $next_month ), $tz ) )->getTimestamp();
$prevMonthName = wp_date( 'F', $prev_ts );
$nextMonthName = wp_date( 'F', $next_ts );
?>
<a
href="?month=<?php echo esc_attr( $prev_month ); ?>&year=<?php echo esc_attr( $prev_year ); ?>"
aria-label="<?php echo esc_attr( "Go to $prevMonthName $prev_year" ); ?>"
>&laquo; <?php esc_html_e( 'Previous Month', 'aa-events' ); ?></a>
<div class="aa-events-calendar-header">
<?php /* translators: %s: localized month and year. */ ?>
<a href="<?php echo esc_url( $calendar_url( $prev ) ); ?>" aria-label="<?php echo esc_attr( sprintf( __( 'Go to %s', 'aa-events' ), wp_date( 'F Y', $prev->getTimestamp(), $tz ) ) ); ?>">&laquo; <?php esc_html_e( 'Previous Month', 'aa-events' ); ?></a>
<h2 id="<?php echo esc_attr( $heading_id ); ?>"><?php echo esc_html( $month_label ); ?></h2>
<?php /* translators: %s: localized month and year. */ ?>
<a href="<?php echo esc_url( $calendar_url( $next ) ); ?>" aria-label="<?php echo esc_attr( sprintf( __( 'Go to %s', 'aa-events' ), wp_date( 'F Y', $next->getTimestamp(), $tz ) ) ); ?>"><?php esc_html_e( 'Next Month', 'aa-events' ); ?> &raquo;</a>
</div>
<h2><?php echo esc_html( wp_date( 'F Y', $first_ts ) ); ?></h2>
<div class="aa-events-calendar-desktop">
<div class="aa-events-calendar-grid" role="grid" aria-labelledby="<?php echo esc_attr( $heading_id ); ?>">
<div class="aa-events-calendar-weekdays" role="row">
<?php foreach ( $ordered_weekdays as $weekday ) : ?>
<div class="aa-events-calendar-weekday" role="columnheader"><abbr title="<?php echo esc_attr( $weekday['label'] ); ?>"><?php echo esc_html( $weekday['abbr'] ); ?></abbr></div>
<?php endforeach; ?>
</div>
<?php foreach ( $calendar_weeks as $week ) : ?>
<?php
$segments_by_column = array_fill( 0, 7, array() );
foreach ( $week['segments'] as $segment ) {
$segment_column = (int) $segment['start_column'];
if ( isset( $segments_by_column[ $segment_column ] ) ) {
$segments_by_column[ $segment_column ][] = $segment;
}
}
?>
<div class="aa-events-calendar-week" role="row" style="--aa-event-lanes: <?php echo esc_attr( max( 1, (int) $week['lane_count'] ) ); ?>;">
<?php foreach ( $week['days'] as $day_column => $day ) : ?>
<?php
$day_classes = array( 'aa-events-calendar-day' );
if ( $day < $month['month_start'] || $day > $month['month_end'] ) {
$day_classes[] = 'is-outside-month';
}
if ( $day->format( 'Y-m-d' ) === $today->format( 'Y-m-d' ) ) {
$day_classes[] = 'today-cell';
}
?>
<div class="<?php echo esc_attr( implode( ' ', $day_classes ) ); ?>" role="gridcell" aria-label="<?php echo esc_attr( wp_date( 'F j, Y', $day->getTimestamp(), $tz ) ); ?>">
<span class="day-number"><?php echo esc_html( wp_date( 'j', $day->getTimestamp(), $tz ) ); ?></span>
<?php foreach ( $segments_by_column[ $day_column ] as $segment ) : ?>
<?php
$event_classes = array( 'aa-events-calendar-event' );
if ( ! empty( $segment['continues_before'] ) ) {
$event_classes[] = 'is-continuing-before';
}
if ( ! empty( $segment['continues_after'] ) ) {
$event_classes[] = 'is-continuing-after';
}
/* translators: 1: event title, 2: complete event date and time range. */
$event_label = sprintf( __( '%1$s: %2$s', 'aa-events' ), $segment['title'], aa_events_format_occurrence( $segment ) );
?>
<a class="<?php echo esc_attr( implode( ' ', $event_classes ) ); ?>" style="--aa-event-column: <?php echo esc_attr( (int) $segment['start_column'] ); ?>; --aa-event-span: <?php echo esc_attr( (int) $segment['span'] ); ?>; --aa-event-lane: <?php echo esc_attr( (int) $segment['lane'] ); ?>;" href="<?php echo esc_url( $segment['permalink'] ); ?>" aria-label="<?php echo esc_attr( $event_label ); ?>">
<?php
if ( ! empty( $segment['continues_before'] ) ) :
?>
<span class="aa-events-calendar-continuation" aria-hidden="true">&larr;</span><?php endif; ?>
<span aria-hidden="true"><?php echo esc_html( $segment['title'] ); ?></span>
<?php
if ( ! empty( $segment['continues_after'] ) ) :
?>
<span class="aa-events-calendar-continuation" aria-hidden="true">&rarr;</span><?php endif; ?>
</a>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
</div>
</div>
<a
href="?month=<?php echo esc_attr( $next_month ); ?>&year=<?php echo esc_attr( $next_year ); ?>"
aria-label="<?php echo esc_attr( "Go to $nextMonthName $next_year" ); ?>"
><?php esc_html_e( 'Next Month', 'aa-events' ); ?> &raquo;</a>
</div>
<table class="aa-events-calendar" role="grid" aria-labelledby="calendar-heading">
<caption id="calendar-heading" class="screen-reader-text"><?php echo esc_html( gmdate( 'F Y', strtotime( "$curYear-$curMonth-01" ) ) ); ?> <?php esc_html_e( 'Events Calendar', 'aa-events' ); ?></caption>
<thead>
<tr>
<th scope="col" abbr="<?php esc_attr_e( 'Mon', 'aa-events' ); ?>"><?php esc_html_e( 'Monday', 'aa-events' ); ?></th>
<th scope="col" abbr="<?php esc_attr_e( 'Tue', 'aa-events' ); ?>"><?php esc_html_e( 'Tuesday', 'aa-events' ); ?></th>
<th scope="col" abbr="<?php esc_attr_e( 'Wed', 'aa-events' ); ?>"><?php esc_html_e( 'Wednesday', 'aa-events' ); ?></th>
<th scope="col" abbr="<?php esc_attr_e( 'Thu', 'aa-events' ); ?>"><?php esc_html_e( 'Thursday', 'aa-events' ); ?></th>
<th scope="col" abbr="<?php esc_attr_e( 'Fri', 'aa-events' ); ?>"><?php esc_html_e( 'Friday', 'aa-events' ); ?></th>
<th scope="col" abbr="<?php esc_attr_e( 'Sat', 'aa-events' ); ?>"><?php esc_html_e( 'Saturday', 'aa-events' ); ?></th>
<th scope="col" abbr="<?php esc_attr_e( 'Sun', 'aa-events' ); ?>"><?php esc_html_e( 'Sunday', 'aa-events' ); ?></th>
</tr>
</thead>
<tbody>
<tr>
<?php
// Add empty cells for the first week.
for ( $i = 1; $i < $first_day; $i++ ) {
echo '<td role="gridcell" class="empty-cell"></td>';
}
// Loop through the days of the month.
for ( $day = 1; $day <= $days_in_month; $day++ ) {
if ( ( $day + $first_day - 2 ) % 7 === 0 && $day > 1 ) {
echo '</tr><tr>';
}
$cellClass = '';
if ( $isTodayInCurrentMonth && $day === $todayDay ) {
$cellClass = 'today-cell';
}
$cell_ts = ( new DateTimeImmutable( sprintf( '%04d-%02d-%02d 00:00:00', $curYear, $curMonth, $day ), $tz ) )->getTimestamp();
echo '<td role="gridcell" aria-label="' . esc_attr( wp_date( 'F j, Y', $cell_ts ) ) . '" ' . ( $cellClass ? ' class="' . esc_attr( $cellClass ) . '"' : '' ) . '>';
echo '<div class="day-number">' . esc_html( $day ) . '</div>';
if ( isset( $events_by_day[ $day ] ) ) {
echo '<ul class="events-list">';
foreach ( $events_by_day[ $day ] as $event ) {
echo '<li>' . wp_kses_post( $event ) . '</li>';
}
echo '</ul>';
}
echo '</td>';
}
// Add empty cells for the last week.
$remaining_days = 7 - ( ( $days_in_month + $first_day - 1 ) % 7 );
if ( $remaining_days < 7 ) {
for ( $i = 0; $i < $remaining_days; $i++ ) {
echo '<td class="empty-cell"></td>';
}
}
?>
</tr>
</tbody>
</table>
<section class="aa-events-calendar-agenda" aria-labelledby="<?php echo esc_attr( $agenda_heading_id ); ?>">
<?php /* translators: %s: localized month and year. */ ?>
<h3 id="<?php echo esc_attr( $agenda_heading_id ); ?>"><?php echo esc_html( sprintf( __( 'Events for %s', 'aa-events' ), $month_label ) ); ?></h3>
<?php if ( $calendar_occurrences ) : ?>
<ol class="aa-events-calendar-agenda-list">
<?php foreach ( $calendar_occurrences as $occurrence ) : ?>
<li class="aa-events-agenda-item"><a href="<?php echo esc_url( $occurrence['permalink'] ); ?>"><span class="aa-events-agenda-title"><?php echo esc_html( $occurrence['title'] ); ?></span> <?php echo aa_events_occurrence_time_markup( $occurrence ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Helper returns escaped markup. ?></a></li>
<?php endforeach; ?>
</ol>
<?php else : ?>
<p class="aa-events-calendar-no-events"><?php esc_html_e( 'No events this month.', 'aa-events' ); ?></p>
<?php endif; ?>
</section>
</div>
+16 -10
View File
@@ -35,21 +35,19 @@ get_header(); ?>
<footer class="entry-footer">
<div class="aa-event-details">
<?php $occurrences = aa_events_get_occurrences( get_the_ID() ); ?>
<?php if ( $occurrences ) : ?>
<div class="aa-event-date">
<strong><?php esc_html_e( 'Date:', 'aa-events' ); ?></strong>
<strong><?php echo esc_html( 1 === count( $occurrences ) ? __( 'Date:', 'aa-events' ) : __( 'Dates:', 'aa-events' ) ); ?></strong>
<?php
$event_raw = get_field( 'event_datetime' );
if ( $event_raw ) {
$tz = wp_timezone();
$ts = ( new DateTimeImmutable( $event_raw, $tz ) )->getTimestamp();
$human = wp_date( 'F j, Y, g:i A', $ts );
$iso_attr = wp_date( 'c', $ts );
?>
<time datetime="<?php echo esc_attr( $iso_attr ); ?>"><?php echo esc_html( $human ); ?></time>
<?php
foreach ( $occurrences as $index => $occurrence ) {
echo aa_events_occurrence_time_markup( $occurrence ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Helper returns escaped markup.
echo ( $index < count( $occurrences ) - 1 ) ? '<br />' : '';
}
?>
</div>
<?php endif; ?>
<?php if ( get_field( 'event_cost' ) ) { ?>
<div class="aa-event-cost">
@@ -73,6 +71,14 @@ get_header(); ?>
?>
</div>
<?php $contact_email_markup = aa_events_contact_email_markup( get_field( 'event_contact_email' ) ); ?>
<?php if ( $contact_email_markup ) : ?>
<div class="aa-event-contact-email">
<strong><?php echo esc_html( __( 'Contact Email:', 'aa-events' ) ); ?></strong>
<?php echo wp_kses_post( $contact_email_markup ); ?>
</div>
<?php endif; ?>
<?php if ( get_the_terms( get_the_ID(), 'event_type' ) ) { ?>
<div class="aa-event-type">
<strong><?php esc_html_e( 'Type:', 'aa-events' ); ?></strong>