events->find(get_queried_object_id()); if ($event === null || ! \ProCalendarWP\Events\EventVisibility::isPublic($event->id)) { return; } $status = [ 'scheduled' => 'https://schema.org/EventScheduled', 'canceled' => 'https://schema.org/EventCancelled', 'postponed' => 'https://schema.org/EventPostponed', 'rescheduled' => 'https://schema.org/EventRescheduled', 'moved-online' => 'https://schema.org/EventMovedOnline', ][$event->status]; $mode = [ 'in-person' => 'https://schema.org/OfflineEventAttendanceMode', 'online' => 'https://schema.org/OnlineEventAttendanceMode', 'hybrid' => 'https://schema.org/MixedEventAttendanceMode', ][$event->mode]; $image = get_the_post_thumbnail_url($event->id, 'full'); $data = [ '@context' => 'https://schema.org', '@type' => 'Event', 'name' => $event->title, 'description' => wp_strip_all_tags($event->excerpt !== '' ? $event->excerpt : $event->description), 'startDate' => $event->startLocal->format(DATE_ATOM), 'endDate' => $event->endLocal->format(DATE_ATOM), 'eventStatus' => $status, 'eventAttendanceMode' => $mode, 'url' => get_permalink($event->id), 'image' => is_string($image) && $image !== '' ? [$image] : [], ]; if ($event->virtualUrl !== '') { $data['location'] = ['@type' => 'VirtualLocation', 'url' => $event->virtualUrl]; } $location = \ProCalendarWP\Maps\Location::resolve($event->id); if ($event->mode !== 'online' && ($location['formatted'] !== '' || $location['line'] !== '' || $location['latitude'] !== null)) { $place = ['@type' => 'Place', 'address' => array_filter([ '@type' => 'PostalAddress', 'streetAddress' => $location['line'] ?: $location['formatted'], 'addressLocality' => $location['city'], 'addressRegion' => $location['region'], 'postalCode' => $location['postal_code'], 'addressCountry' => $location['country'], ])]; if ($location['latitude'] !== null && $location['longitude'] !== null) { $place['geo'] = ['@type' => 'GeoCoordinates', 'latitude' => $location['latitude'], 'longitude' => $location['longitude']]; } $data['location'] = isset($data['location']) ? [$place, $data['location']] : $place; } if ($event->cost !== '') { $data['offers'] = ['@type' => 'Offer', 'price' => $event->cost, 'priceCurrency' => $event->currency, 'url' => $event->website ?: get_permalink($event->id), 'availability' => 'https://schema.org/InStock']; } if ($event->organizerIds !== []) { $data['organizer'] = array_map(static fn (int $id): array => array_filter(['@type' => 'Organization', 'name' => get_the_title($id), 'url' => get_post_status($id) === 'publish' && ! post_password_required($id) ? get_permalink($id) : '']), $event->organizerIds); } if ($event->status === 'rescheduled' && ($previous = get_post_meta($event->id, '_pcwp_previous_start', true)) !== '') { try { $data['previousStartDate'] = (new \DateTimeImmutable((string) $previous, $event->startLocal->getTimezone()))->format(DATE_ATOM); } catch (\Exception) { // An incomplete editorial previous date is omitted. } } if (get_post_meta($event->id, '_pcwp_rsvp_enabled', true)) { $capacity = absint(get_post_meta($event->id, '_pcwp_rsvp_capacity', true)); $closed = in_array($event->status, ['canceled', 'postponed'], true) || $event->endLocal->getTimestamp() < time(); $data['offers']['availability'] = $closed ? 'https://schema.org/Discontinued' : ($capacity > 0 && \ProCalendarWP\Rsvp\RsvpService::activeCount($event->id) >= $capacity ? 'https://schema.org/SoldOut' : 'https://schema.org/InStock'); $data['offers']['@type'] = 'Offer'; $data['offers']['url'] = get_permalink($event->id); } if (($event->schedule['type'] ?? '') === 'daily') { $data['@id'] = get_permalink($event->id) . '#event'; // The parent describes the date span; only child events claim timed opening intervals. $data['startDate'] = $event->schedule['startDate']; $data['endDate'] = $event->schedule['endDate']; $data['subEvent'] = []; foreach (\ProCalendarWP\Events\DailySchedule::intervals($event->schedule, $event->timezone) as $slot) { $child = $data; unset($child['@context'], $child['subEvent']); $child['@id'] = get_permalink($event->id) . '#slot-' . $slot['id']; $child['startDate'] = $slot['time']->start->format(DATE_ATOM); $child['endDate'] = $slot['time']->end->format(DATE_ATOM); $child['superEvent'] = ['@id' => $data['@id']]; $data['subEvent'][] = $child; } } $data = apply_filters('pcwp_json_ld_event', $data, $event); $json = wp_json_encode($data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE); if ($json === false) { return; } // Hex-escaped HTML characters prevent event or filter data closing the script element. echo '' . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON is encoded for this script context above. } }