$attributes */ public function render(array $attributes = []): string { wp_enqueue_style('pcwp-calendar-blocks'); $attributes = $this->normalize($attributes); $view = $attributes['view']; [$rangeStart, $rangeEnd] = $this->range($view, $attributes['start']); $query = array_merge($attributes, [ 'start' => $rangeStart->format(DATE_ATOM), 'end' => $rangeEnd->format(DATE_ATOM), ]); $paged = in_array($view, ['list', 'photo', 'map'], true); if ($paged) { $query['limit'] = $attributes['limit'] + 1; $query['offset'] = ($attributes['page'] - 1) * $attributes['limit']; } if (in_array($view, ['month', 'day', 'week'], true)) { $query['schedule_local_start'] = $rangeStart->format('Y-m-d H:i:s'); $query['schedule_local_end'] = $rangeEnd->format('Y-m-d H:i:s'); } $occurrences = $this->events->occurrences($query); $hasMore = $paged && count($occurrences) > $attributes['limit']; if ($paged) { $occurrences = array_slice($occurrences, 0, $attributes['limit']); } if (in_array($view, ['month', 'day', 'week'], true)) { $occurrences = \ProCalendarWP\Events\ScheduleQuery::calendarIntervals($occurrences, $query); } $context = [ 'attributes' => $attributes, 'events' => $this->events, 'occurrences' => apply_filters('pcwp_view_occurrences', $occurrences, $attributes), 'rangeStart' => $rangeStart, 'rangeEnd' => $rangeEnd, 'previousUrl' => $this->navigationUrl($view, $rangeStart, -1, $attributes['instance']), 'nextUrl' => $this->navigationUrl($view, $rangeStart, 1, $attributes['instance']), ]; do_action('pcwp_before_view', $view, $context); $html = TemplateLoader::render('calendar-' . $view, $context); do_action('pcwp_after_view', $view, $context); $pagination = ''; if ($paged && ($attributes['page'] > 1 || $hasMore)) { $pagination = ''; } return '
' . $this->filterForm($attributes) . $html . $pagination . '
'; } /** * @param array $input Input attributes. * @return array */ public function normalize(array $input): array { $view = sanitize_key((string) ($input['view'] ?? get_option('pcwp_default_view', 'list'))); $allowedViews = apply_filters('pcwp_allowed_calendar_views', ['list', 'photo', 'month', 'day', 'week', 'map']); if (! is_array($allowedViews) || ! in_array($view, $allowedViews, true)) { $view = 'list'; } $start = sanitize_text_field((string) ($input['start'] ?? '')); try { $start = ($start !== '' ? new DateTimeImmutable($start, wp_timezone()) : new DateTimeImmutable('now', wp_timezone()))->format('Y-m-d'); } catch (\Exception) { $start = current_time('Y-m-d'); } $spec = EventQuerySpec::from($input, true); if (($instance = QueryIntegration::sanitizeInstance((string) ($input['instance'] ?? ''))) !== 'events' || isset($input['instance'])) { $spec = $spec->withOverrides(QueryIntegration::requestOverrides($instance)); } $spec = $spec->toRepositoryQuery(); return array_merge($spec, [ 'view' => $view, 'start' => $spec['start'] !== '' ? substr((string) $spec['start'], 0, 10) : $start, 'filter_bar' => (bool) filter_var($input['filter_bar'] ?? true, FILTER_VALIDATE_BOOLEAN), 'instance' => QueryIntegration::sanitizeInstance((string) ($input['instance'] ?? 'events')), 'page' => max(1, min(100000, absint($_GET[QueryIntegration::parameterName($instance, 'page')] ?? $input['page'] ?? 1))), 'limit' => min(499, $spec['limit']), 'show_featured_image' => (bool) filter_var($input['show_featured_image'] ?? true, FILTER_VALIDATE_BOOLEAN), 'wrap_titles' => (bool) filter_var($input['wrap_titles'] ?? true, FILTER_VALIDATE_BOOLEAN), 'max_per_day' => min(20, max(1, absint($input['max_per_day'] ?? 4))), ]); } /** @return array{DateTimeImmutable,DateTimeImmutable} */ private function range(string $view, string $start): array { $date = new DateTimeImmutable($start . ' 00:00:00', wp_timezone()); if ($view === 'month') { return [$date->modify('first day of this month'), $date->modify('last day of this month 23:59:59')]; } if ($view === 'day') { return [$date, $date->setTime(23, 59, 59)]; } if ($view === 'week') { $first = $date->modify('monday this week'); return [$first, $first->add(new DateInterval('P6D'))->setTime(23, 59, 59)]; } return [$date, $date->add(new DateInterval('P1Y'))->setTime(23, 59, 59)]; } private function navigationUrl(string $view, DateTimeImmutable $date, int $direction, string $instance): string { $unit = $view === 'month' ? 'month' : ($view === 'day' ? 'day' : ($view === 'week' ? 'week' : 'year')); $target = $date->modify(($direction > 0 ? '+' : '-') . '1 ' . $unit); $params = [QueryIntegration::parameterName($instance, 'start') => $target->format('Y-m-d'), QueryIntegration::parameterName($instance, 'page') => false]; // Keep the original query arguments for legacy shortcode/widget navigation. if ($instance === 'events') { $params['pcwp_view'] = $view; $params['pcwp_start'] = $target->format('Y-m-d'); } return strtok((string) add_query_arg($params), '#') . '#pcwp-calendar-' . $instance; } /** @param array $attributes */ private function filterForm(array $attributes): string { if (! $attributes['filter_bar']) { return ''; } $parameter = static fn (string $field): string => QueryIntegration::parameterName($attributes['instance'], $field); ob_start(); ?>