✨ 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:
+208
-34
@@ -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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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" ); ?>"
|
||||
>« <?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 ) ) ); ?>">« <?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' ); ?> »</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">←</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">→</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' ); ?> »</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
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user