<?php
declare(strict_types=1);
namespace MeinWeimar;
use WP_Post;
final class DetailRenderer
{
public static function place_markup(int $post_id = 0): string
{
return self::resource_markup($post_id, 'places');
}
public static function event_markup(int $post_id = 0): string
{
return self::resource_markup($post_id, 'events');
}
public static function resource_markup(int $post_id = 0, string $route = ''): string
{
$post_type = $route ? ContentTypes::post_type_for_route($route) : null;
$post = $post_type ? self::resolve_post($post_id, $post_type) : self::resolve_resource($post_id);
return $post ? self::markup($post, 'mw_event' === $post->post_type) : '';
}
public static function markup(WP_Post $post, bool $is_event): string
{
Renderer::enqueue_assets();
$item = RestApi::public_item($post);
$address = array_filter([
$item['address']['line1'] ?? '',
$item['address']['line2'] ?? '',
trim(implode(' ', array_filter([
$item['address']['postal_code'] ?? '',
$item['address']['locality'] ?? '',
]))),
$item['address']['region'] ?? '',
]);
$location = implode(', ', $address);
$navigation_url = self::navigation_url((array) ($item['geo'] ?? []), $location);
$place_details = is_array($item['place_details'] ?? null) ? $item['place_details'] : [];
$opening_hours = self::opening_hours_rows((array) ($place_details['opening_hours'] ?? []));
$event = is_array($item['event'] ?? null) ? $item['event'] : [];
$listing = is_array($item['listing'] ?? null) ? $item['listing'] : [];
$resource = ContentTypes::route_for_post_type($post->post_type);
$resource_definition = ContentTypes::definitions()[$resource] ?? [];
$category_taxonomy = ContentTypes::category_taxonomy($post->post_type);
$category_slugs = $category_taxonomy
? array_values(array_filter(array_map('sanitize_title', (array) (($item['terms'] ?? [])[$category_taxonomy] ?? []))))
: [];
$condition_values = FieldSchemas::values_for_post($post, $resource);
ob_start();
?>
<article class="mw-detail" data-mw-detail data-type="<?php echo esc_attr($resource); ?>" data-post-id="<?php echo esc_attr((string) $post->ID); ?>">
<header class="mw-detail__header">
<p class="mw-detail__eyebrow"><?php echo esc_html((string) ($resource_definition['singular'] ?? __('Eintrag', 'meinweimar-core'))); ?></p>
<h1 class="mw-detail__title"><?php echo esc_html($item['title']); ?></h1>
<?php if ($location) : ?><p class="mw-detail__location"><?php echo esc_html($location); ?></p><?php endif; ?>
<?php if (is_user_logged_in()) : ?>
<div class="mw-detail__actions" data-mw-favorite data-object-type="<?php echo esc_attr($resource); ?>" data-object-id="<?php echo esc_attr((string) $post->ID); ?>">
<button type="button" class="mw-detail__favorite" data-mw-favorite-toggle aria-pressed="false"><?php esc_html_e('Speichern', 'meinweimar-core'); ?></button>
<span class="mw-detail__favorite-status" data-mw-favorite-status role="status" aria-live="polite"></span>
</div>
<?php else : ?>
<p class="mw-detail__favorite-login"><a href="<?php echo esc_url(wp_login_url(get_permalink($post->ID) ?: home_url('/'))); ?>"><?php esc_html_e('Anmelden, um Inhalte zu speichern', 'meinweimar-core'); ?></a></p>
<?php endif; ?>
</header>
<div class="mw-detail__layout">
<div class="mw-detail__main">
<?php $primary_media = $item['media'][0] ?? []; ?>
<?php $media_placeholder = !empty($item['media_placeholder']); ?>
<?php if (!empty($primary_media['url']) || !empty($item['image_url'])) : ?>
<figure class="mw-detail__primary-media">
<img class="mw-detail__image" src="<?php echo esc_url((string) ($primary_media['url'] ?? $item['image_url'])); ?>" alt="<?php echo esc_attr((string) ($primary_media['alt'] ?? $item['title'] ?? '')); ?>">
<?php if (($media_placeholder || !empty($primary_media['is_placeholder'])) && ImageSuggestions::supports_target_type($resource)) : ?>
<?php echo self::image_suggestion_markup($resource, $post->ID, (string) $item['title'], true); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php endif; ?>
</figure>
<?php else : ?>
<?php if (ImageSuggestions::supports_target_type($resource)) : ?>
<?php echo self::image_suggestion_markup($resource, $post->ID, (string) $item['title'], $media_placeholder); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php endif; ?>
<?php endif; ?>
<div class="mw-detail__content">
<?php echo wp_kses_post((string) ($item['content']['html'] ?? wpautop($post->post_content))); ?>
</div>
<?php if (count((array) ($item['media'] ?? [])) > 1) : ?>
<section class="mw-detail__gallery" aria-label="<?php esc_attr_e('Weitere Bilder', 'meinweimar-core'); ?>">
<?php foreach (array_slice((array) $item['media'], 1) as $media) : ?>
<?php if (empty($media['url'])) { continue; } ?>
<figure class="mw-detail__gallery-item">
<img src="<?php echo esc_url((string) $media['url']); ?>" alt="<?php echo esc_attr((string) ($media['alt'] ?? '')); ?>">
<?php if (!empty($media['caption'])) : ?><figcaption><?php echo esc_html((string) $media['caption']); ?></figcaption><?php endif; ?>
</figure>
<?php endforeach; ?>
</section>
<?php endif; ?>
</div>
<aside class="mw-detail__aside">
<?php if ($location) : ?>
<section class="mw-detail__section"><h2><?php esc_html_e('Adresse', 'meinweimar-core'); ?></h2><p><?php echo esc_html($location); ?></p><?php if ($navigation_url) : ?><p><a class="mw-detail__navigate" href="<?php echo esc_url($navigation_url); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e('Navigieren', 'meinweimar-core'); ?></a></p><?php endif; ?></section>
<?php endif; ?>
<?php if (!empty($item['contact']['phone']) || !empty($item['contact']['email']) || !empty($item['contact']['website_url']) || !empty($item['contact']['facebook_url']) || !empty($item['contact']['twitter_url']) || !empty($item['contact']['video_url'])) : ?>
<section class="mw-detail__section">
<h2><?php esc_html_e('Kontakt', 'meinweimar-core'); ?></h2>
<?php if (!empty($item['contact']['phone'])) : ?><p><a href="tel:<?php echo esc_attr($item['contact']['phone']); ?>"><?php echo esc_html($item['contact']['phone']); ?></a></p><?php endif; ?>
<?php if (!empty($item['contact']['email'])) : ?><p><a href="mailto:<?php echo esc_attr($item['contact']['email']); ?>"><?php echo esc_html($item['contact']['email']); ?></a></p><?php endif; ?>
<?php if (!empty($item['contact']['website_url'])) : ?><p><a href="<?php echo esc_url($item['contact']['website_url']); ?>" rel="noopener"><?php esc_html_e('Website öffnen', 'meinweimar-core'); ?></a></p><?php endif; ?>
<?php foreach (['facebook_url' => __('Facebook öffnen', 'meinweimar-core'), 'twitter_url' => __('Twitter / X öffnen', 'meinweimar-core'), 'video_url' => __('Video öffnen', 'meinweimar-core')] as $social_key => $social_label) : ?>
<?php if (!empty($item['contact'][$social_key])) : ?><p><a href="<?php echo esc_url((string) $item['contact'][$social_key]); ?>" rel="noopener"><?php echo esc_html($social_label); ?></a></p><?php endif; ?>
<?php endforeach; ?>
</section>
<?php endif; ?>
<?php if ($opening_hours) : ?>
<section class="mw-detail__section">
<h2><?php esc_html_e('Öffnungszeiten', 'meinweimar-core'); ?></h2>
<?php foreach ($opening_hours as $opening_row) : ?><p><?php echo esc_html($opening_row); ?></p><?php endforeach; ?>
</section>
<?php endif; ?>
<?php if ($place_details && !$is_event) : ?>
<section class="mw-detail__section">
<h2><?php esc_html_e('Weitere Informationen', 'meinweimar-core'); ?></h2>
<?php if (!empty($place_details['featured'])) : ?><p class="mw-detail__status"><?php esc_html_e('Empfohlen', 'meinweimar-core'); ?></p><?php endif; ?>
<?php if (isset($place_details['rating_average']) && null !== $place_details['rating_average'] && '' !== (string) $place_details['rating_average']) : ?>
<p><strong><?php esc_html_e('Bewertung', 'meinweimar-core'); ?>:</strong> <?php echo esc_html(number_format_i18n((float) $place_details['rating_average'], 1)); ?> / 5<?php if (!empty($place_details['rating_count'])) : ?> (<?php echo esc_html(number_format_i18n((int) $place_details['rating_count'])); ?>)<?php endif; ?></p>
<?php endif; ?>
<?php if (!empty($place_details['special_offers'])) : ?><p><strong><?php esc_html_e('Sonderangebote', 'meinweimar-core'); ?>:</strong> <?php echo esc_html((string) $place_details['special_offers']); ?></p><?php endif; ?>
<?php if (!empty($place_details['neighbourhood'])) : ?><p><strong><?php esc_html_e('Umgebung', 'meinweimar-core'); ?>:</strong> <?php echo esc_html((string) $place_details['neighbourhood']); ?></p><?php endif; ?>
<?php if (!empty($place_details['expire_date'])) : ?><p><strong><?php esc_html_e('Ablaufdatum', 'meinweimar-core'); ?>:</strong> <?php echo esc_html(self::format_date((string) $place_details['expire_date'])); ?></p><?php endif; ?>
</section>
<?php endif; ?>
<?php if ($is_event && $event) : ?>
<section class="mw-detail__section">
<h2><?php esc_html_e('Termin', 'meinweimar-core'); ?></h2>
<?php if (!empty($event['occurrences'])) : ?>
<?php foreach ((array) $event['occurrences'] as $occurrence) : ?>
<?php if (!is_array($occurrence) || empty($occurrence['starts_at'])) { continue; } ?>
<p><strong><?php esc_html_e('Termin', 'meinweimar-core'); ?>:</strong> <?php echo esc_html(self::format_datetime((string) $occurrence['starts_at'])); ?><?php if (!empty($occurrence['ends_at'])) : ?>–<?php echo esc_html(self::format_datetime((string) $occurrence['ends_at'])); ?><?php endif; ?><?php if (!empty($occurrence['timezone'])) : ?> (<?php echo esc_html((string) $occurrence['timezone']); ?>)<?php endif; ?></p>
<?php if (!empty($occurrence['status']) && 'scheduled' !== $occurrence['status']) : ?><p class="mw-detail__status"><strong><?php esc_html_e('Status', 'meinweimar-core'); ?>:</strong> <?php echo esc_html(self::event_status_label((string) $occurrence['status'])); ?></p><?php endif; ?>
<?php endforeach; ?>
<?php else : ?>
<?php if (!empty($event['start'])) : ?><p><strong><?php esc_html_e('Beginn', 'meinweimar-core'); ?>:</strong> <?php echo esc_html(self::format_datetime((string) $event['start'])); ?></p><?php endif; ?>
<?php if (!empty($event['end'])) : ?><p><strong><?php esc_html_e('Ende', 'meinweimar-core'); ?>:</strong> <?php echo esc_html(self::format_datetime((string) $event['end'])); ?></p><?php endif; ?>
<?php endif; ?>
<?php if (!empty($event['status'])) : ?><p class="mw-detail__status"><strong><?php esc_html_e('Status', 'meinweimar-core'); ?>:</strong> <?php echo esc_html(self::event_status_label((string) $event['status'])); ?></p><?php endif; ?>
<?php if (!empty($event['organizer_name'])) : ?><p><strong><?php esc_html_e('Veranstalter', 'meinweimar-core'); ?>:</strong> <?php echo esc_html((string) $event['organizer_name']); ?></p><?php endif; ?>
<?php if (!empty($event['organizer_url'])) : ?><p><a href="<?php echo esc_url((string) $event['organizer_url']); ?>" rel="noopener"><?php esc_html_e('Veranstalter-Website öffnen', 'meinweimar-core'); ?></a></p><?php endif; ?>
<?php if (!empty($event['ticket_mode'])) : ?><p><strong><?php esc_html_e('Tickets / Anmeldung', 'meinweimar-core'); ?>:</strong> <?php echo esc_html(self::event_ticket_label((string) $event['ticket_mode'])); ?></p><?php endif; ?>
<?php if (!empty($event['ticket_url'])) : ?><p><a href="<?php echo esc_url((string) $event['ticket_url']); ?>" rel="noopener"><?php esc_html_e('Tickets / Anmeldung öffnen', 'meinweimar-core'); ?></a></p><?php endif; ?>
<?php if (isset($event['ticket_price_from']) && '' !== (string) $event['ticket_price_from']) : ?><p><strong><?php esc_html_e('Ticketpreis ab', 'meinweimar-core'); ?>:</strong> <?php echo esc_html(number_format_i18n((float) $event['ticket_price_from'], 2)); ?> €</p><?php endif; ?>
<?php if (isset($event['ticket_price_to']) && '' !== (string) $event['ticket_price_to']) : ?><p><strong><?php esc_html_e('Ticketpreis bis', 'meinweimar-core'); ?>:</strong> <?php echo esc_html(number_format_i18n((float) $event['ticket_price_to'], 2)); ?> €</p><?php endif; ?>
<?php if (!empty($event['calendar_url'])) : ?><p><a href="<?php echo esc_url((string) $event['calendar_url']); ?>" download><?php esc_html_e('Kalenderdatei speichern', 'meinweimar-core'); ?></a></p><?php endif; ?>
</section>
<?php endif; ?>
<?php if ($listing) : ?>
<section class="mw-detail__section">
<h2><?php esc_html_e('Angebot', 'meinweimar-core'); ?></h2>
<?php if ('real-estate' === $resource && 'no' === (string) ($listing['commission_applicable'] ?? '')) : ?>
<p class="mw-detail__status"><strong><?php esc_html_e('Provisionsfrei', 'meinweimar-core'); ?></strong></p>
<?php endif; ?>
<?php foreach (FieldSchemas::for_route($resource) as $meta_key => $field) : ?>
<?php
$field_group = (string) ($field['api_group'] ?? '');
$field_key = (string) ($field['api_key'] ?? '');
$is_listing_field = 'listing' === $field_group || ('lifecycle' === $field_group && 'expire_date' === $field_key);
if (!$is_listing_field || !array_key_exists($field_key, $listing) || !FieldSchemas::field_applies($resource, $meta_key, $category_slugs, $condition_values)) {
continue;
}
$raw_value = $listing[$field_key];
if (null === $raw_value || '' === (string) $raw_value || in_array((string) $raw_value, ['unknown', 'no'], true)) {
continue;
}
if ('energy_source' === $field_key &&
strtolower(trim((string) $raw_value)) === strtolower(trim((string) ($listing['heating_type'] ?? '')))) {
continue;
}
$display_value = 'date' === ($field['input'] ?? '')
? self::format_date((string) $raw_value)
: self::listing_value_label($field_key, (string) $raw_value);
?>
<p><strong><?php echo esc_html(self::listing_label($field_key)); ?>:</strong> <?php echo esc_html($display_value); ?></p>
<?php endforeach; ?>
</section>
<?php endif; ?>
<?php if (!empty($item['geo']['available'])) : ?>
<section class="mw-detail__section mw-detail__map" data-lat="<?php echo esc_attr((string) $item['geo']['lat']); ?>" data-lng="<?php echo esc_attr((string) $item['geo']['lng']); ?>">
<h2><?php esc_html_e('Karte', 'meinweimar-core'); ?></h2>
<p><?php esc_html_e('Dieser Ort ist für die gemeinsame Kartenansicht verfügbar.', 'meinweimar-core'); ?></p>
</section>
<?php endif; ?>
</aside>
</div>
</article>
<?php
return (string) ob_get_clean();
}
/**
* Build the same external navigation hand-off used by the app.
*
* Google Maps URLs need no API key. On supported devices the URL is
* claimed by the installed Google Maps app; otherwise the browser opens
* the directions page. A configured custom template remains available
* for an explicitly selected system provider.
*/
private static function navigation_url(array $geo, string $address): string
{
if (empty($geo['available']) || !is_numeric($geo['lat']) || !is_numeric($geo['lng'])) {
return '';
}
$latitude = (float) $geo['lat'];
$longitude = (float) $geo['lng'];
if ($latitude < -90 || $latitude > 90 || $longitude < -180 || $longitude > 180) {
return '';
}
$config = (array) (Settings::public_config()['geo'] ?? []);
$provider = (string) ($config['navigation_provider'] ?? 'google_maps');
$template = trim((string) ($config['external_navigation_url_template'] ?? ''));
if ('system' === $provider && '' === $template) {
return '';
}
if ('' !== $template && 'google_maps' !== $provider) {
return strtr($template, [
'{lat}' => (string) $latitude,
'{lng}' => (string) $longitude,
'{coordinates}' => $latitude . ',' . $longitude,
'{destination}' => $latitude . ',' . $longitude,
'{address}' => rawurlencode($address),
]);
}
return add_query_arg([
'api' => '1',
'destination' => $latitude . ',' . $longitude,
'travelmode' => 'driving',
'dir_action' => 'navigate',
], 'https://www.google.com/maps/dir/');
}
/**
* Translate a canonical listing API key for the Website/Elementor view.
* Field membership and ordering come from FieldSchemas; this method only
* supplies human-facing labels and therefore cannot expose foreign CPT
* fields by itself.
*/
private static function listing_label(string $key): string
{
$labels = [
'status' => __('Status', 'meinweimar-core'),
'offer_type' => __('Angebotsart', 'meinweimar-core'),
'provider_type' => __('Anbieter', 'meinweimar-core'),
'price_type' => __('Preisangabe', 'meinweimar-core'),
'price_amount' => __('Betrag', 'meinweimar-core'),
'price_currency' => __('Währung', 'meinweimar-core'),
'price_interval' => __('Preisintervall', 'meinweimar-core'),
'available_from' => __('Verfügbar ab', 'meinweimar-core'),
'price_label' => __('Preis / Vergütung', 'meinweimar-core'),
'company_name' => __('Organisation', 'meinweimar-core'),
'property_kind' => __('Immobilienart', 'meinweimar-core'),
'transaction_type' => __('Vermarktungsart', 'meinweimar-core'),
'commission_applicable' => __('Provision fällig?', 'meinweimar-core'),
'commission_calculation' => __('Berechnungsart', 'meinweimar-core'),
'commission_total' => __('Gesamtprovision', 'meinweimar-core'),
'commission_months' => __('Anzahl Kaltmieten', 'meinweimar-core'),
'commission_buyer_share' => __('Anteil Käufer', 'meinweimar-core'),
'commission_seller_share' => __('Anteil Verkäufer', 'meinweimar-core'),
'commission_vat' => __('Umsatzsteuer', 'meinweimar-core'),
'commission_payer' => __('Zahlungspflichtig', 'meinweimar-core'),
'area' => __('Fläche', 'meinweimar-core'),
'property_rooms' => __('Zimmer', 'meinweimar-core'),
'bedrooms' => __('Schlafzimmer', 'meinweimar-core'),
'bathrooms' => __('Bäder', 'meinweimar-core'),
'floor' => __('Etage', 'meinweimar-core'),
'total_floors' => __('Etagen insgesamt', 'meinweimar-core'),
'year_built' => __('Baujahr', 'meinweimar-core'),
'last_renovated' => __('Letzte Modernisierung', 'meinweimar-core'),
'condition' => __('Zustand', 'meinweimar-core'),
'cold_rent' => __('Kaltmiete', 'meinweimar-core'),
'warm_rent' => __('Warmmiete', 'meinweimar-core'),
'additional_costs' => __('Nebenkosten', 'meinweimar-core'),
'heating_costs_included' => __('Heizkosten in Nebenkosten enthalten', 'meinweimar-core'),
'deposit' => __('Kaution', 'meinweimar-core'),
'house_fee' => __('Hausgeld', 'meinweimar-core'),
'commission_type' => __('Provisionsart', 'meinweimar-core'),
'commission_amount' => __('Provisionsbetrag', 'meinweimar-core'),
'energy_certificate_type' => __('Energieausweis', 'meinweimar-core'),
'energy_value' => __('Endenergiekennwert', 'meinweimar-core'),
'energy_class' => __('Energieeffizienzklasse', 'meinweimar-core'),
'heating_type' => __('Heizungsart', 'meinweimar-core'),
'energy_source' => __('Energieträger', 'meinweimar-core'),
'parking_spaces' => __('Stellplätze', 'meinweimar-core'),
'parking_price' => __('Stellplatzpreis', 'meinweimar-core'),
'brand' => __('Marke', 'meinweimar-core'),
'model' => __('Modell / Typ', 'meinweimar-core'),
'color' => __('Farbe', 'meinweimar-core'),
'size' => __('Größe / Maße', 'meinweimar-core'),
'material' => __('Material', 'meinweimar-core'),
'item_age' => __('Alter', 'meinweimar-core'),
'original_price' => __('Originalpreis', 'meinweimar-core'),
'payment_mode' => __('Zahlungsart', 'meinweimar-core'),
'employment_type' => __('Beschäftigungsart', 'meinweimar-core'),
'work_mode' => __('Arbeitsmodell', 'meinweimar-core'),
'work_hours' => __('Arbeitszeit', 'meinweimar-core'),
'salary_min' => __('Vergütung von', 'meinweimar-core'),
'salary_max' => __('Vergütung bis', 'meinweimar-core'),
'reference_code' => __('Referenznummer', 'meinweimar-core'),
'department' => __('Abteilung', 'meinweimar-core'),
'start_date' => __('Startdatum', 'meinweimar-core'),
'application_url' => __('Bewerbung', 'meinweimar-core'),
'application_email' => __('Bewerbungs-E-Mail', 'meinweimar-core'),
'application_method' => __('Bewerbungsweg', 'meinweimar-core'),
'application_deadline' => __('Bewerbungsfrist', 'meinweimar-core'),
'requirements' => __('Anforderungen', 'meinweimar-core'),
'responsibilities' => __('Aufgaben', 'meinweimar-core'),
'benefits' => __('Vorteile', 'meinweimar-core'),
'languages' => __('Sprachen', 'meinweimar-core'),
'education' => __('Ausbildung', 'meinweimar-core'),
'experience_years' => __('Berufserfahrung', 'meinweimar-core'),
'contact_preference' => __('Kontakt', 'meinweimar-core'),
'expire_date' => __('Ablaufdatum', 'meinweimar-core'),
'amenity_kitchen' => __('Einbauküche', 'meinweimar-core'),
'amenity_balcony' => __('Balkon / Loggia', 'meinweimar-core'),
'amenity_terrace' => __('Terrasse', 'meinweimar-core'),
'amenity_garden' => __('Garten', 'meinweimar-core'),
'amenity_basement' => __('Keller', 'meinweimar-core'),
'amenity_attic' => __('Dachboden', 'meinweimar-core'),
'amenity_elevator' => __('Aufzug', 'meinweimar-core'),
'amenity_guest_toilet' => __('Gäste-WC', 'meinweimar-core'),
'amenity_air_conditioning' => __('Klimaanlage', 'meinweimar-core'),
'amenity_floor_heating' => __('Fußbodenheizung', 'meinweimar-core'),
'amenity_fireplace' => __('Kamin / Ofen', 'meinweimar-core'),
'amenity_furnished' => __('Möbliert', 'meinweimar-core'),
'amenity_barrier_free' => __('Barrierearm / rollstuhlgerecht', 'meinweimar-core'),
'amenity_pets_allowed' => __('Haustiere erlaubt', 'meinweimar-core'),
'amenity_garage' => __('Garage / Tiefgarage', 'meinweimar-core'),
'amenity_carport' => __('Carport', 'meinweimar-core'),
'amenity_e_charging' => __('E-Ladepunkt', 'meinweimar-core'),
];
return $labels[$key] ?? __('Weitere Angaben', 'meinweimar-core');
}
private static function listing_value_label(string $key, string $value): string
{
if (('heating_costs_included' === $key || str_starts_with($key, 'amenity_')) && in_array(strtolower($value), ['1', 'true', 'yes'], true)) {
return __('Ja', 'meinweimar-core');
}
return match ($key) {
'property_kind' => FieldSchemas::property_kind_options()[$value] ?? __('Weitere', 'meinweimar-core'),
'property_rooms' => 'custom' === $value ? __('Eigene Anzahl', 'meinweimar-core') : $value,
'bathrooms', 'bedrooms' => 'more_than_6' === $value ? __('Mehr als 6', 'meinweimar-core') : $value,
'floor' => 'other' === $value ? __('Andere', 'meinweimar-core') : $value,
'offer_type' => match ($value) {
'offer' => __('Angebot', 'meinweimar-core'),
'search' => __('Gesuch', 'meinweimar-core'),
'employment' => __('Stelle / Beschäftigung', 'meinweimar-core'),
default => __('Weitere', 'meinweimar-core'),
},
'provider_type' => match ($value) {
'private' => __('Privat', 'meinweimar-core'),
'business' => __('Unternehmen', 'meinweimar-core'),
'organization' => __('Organisation', 'meinweimar-core'),
'broker' => __('Makler / Broker', 'meinweimar-core'),
'employer' => __('Arbeitgeber', 'meinweimar-core'),
default => __('Weitere', 'meinweimar-core'),
},
'price_type' => match ($value) {
'fixed' => __('Festpreis', 'meinweimar-core'),
'negotiable' => __('Verhandelbar', 'meinweimar-core'),
'free' => __('Zu verschenken', 'meinweimar-core'),
'on_request' => __('Auf Anfrage', 'meinweimar-core'),
'range' => __('Preisspanne', 'meinweimar-core'),
default => __('Weitere', 'meinweimar-core'),
},
'price_currency' => match ($value) {
'EUR' => __('Euro', 'meinweimar-core'),
'USD' => __('US-Dollar', 'meinweimar-core'),
'GBP' => __('Britische Pfund', 'meinweimar-core'),
default => __('Weitere', 'meinweimar-core'),
},
'price_interval' => match ($value) {
'once' => __('Einmalig', 'meinweimar-core'),
'hour' => __('Pro Stunde', 'meinweimar-core'),
'month' => __('Pro Monat', 'meinweimar-core'),
'year' => __('Pro Jahr', 'meinweimar-core'),
default => __('Weitere', 'meinweimar-core'),
},
'condition' => match ($value) {
'new' => __('Neu', 'meinweimar-core'),
'used' => __('Gebraucht', 'meinweimar-core'),
'defective' => __('Defekt', 'meinweimar-core'),
'first_occupancy' => __('Erstbezug', 'meinweimar-core'),
'renovated' => __('Renoviert', 'meinweimar-core'),
'well_kept' => __('Gepflegt', 'meinweimar-core'),
'needs_renovation' => __('Renovierungsbedürftig', 'meinweimar-core'),
default => __('Weitere', 'meinweimar-core'),
},
'delivery_mode' => match ($value) {
'pickup' => __('Abholung', 'meinweimar-core'),
'shipping' => __('Versand', 'meinweimar-core'),
'both' => __('Abholung oder Versand', 'meinweimar-core'),
'digital' => __('Digital', 'meinweimar-core'),
'none' => __('Keine Angabe', 'meinweimar-core'),
default => __('Weitere', 'meinweimar-core'),
},
'employment_type' => match ($value) {
'full_time' => __('Vollzeit', 'meinweimar-core'),
'part_time' => __('Teilzeit', 'meinweimar-core'),
'temporary' => __('Befristet', 'meinweimar-core'),
'freelance' => __('Freiberuflich', 'meinweimar-core'),
'apprenticeship' => __('Ausbildung', 'meinweimar-core'),
'internship' => __('Praktikum', 'meinweimar-core'),
default => __('Weitere', 'meinweimar-core'),
},
'work_mode' => match ($value) {
'onsite' => __('Vor Ort', 'meinweimar-core'),
'hybrid' => __('Hybrid', 'meinweimar-core'),
'remote' => __('Fernarbeit', 'meinweimar-core'),
default => __('Weitere', 'meinweimar-core'),
},
'payment_mode' => match ($value) {
'cash' => __('Barzahlung', 'meinweimar-core'),
'bank_transfer' => __('Überweisung', 'meinweimar-core'),
'secure_payment' => __('Sichere Zahlung', 'meinweimar-core'),
'other' => __('Andere', 'meinweimar-core'),
default => __('Weitere', 'meinweimar-core'),
},
'commission_applicable' => 'yes' === $value ? __('Provisionspflichtig', 'meinweimar-core') : __('Provisionsfrei', 'meinweimar-core'),
'commission_calculation' => match ($value) {
'percentage' => __('Prozentsatz', 'meinweimar-core'),
'fixed' => __('Fester Betrag', 'meinweimar-core'),
'monthly_rent' => __('Monatliche Kaltmieten', 'meinweimar-core'),
default => __('Weitere', 'meinweimar-core'),
},
'transaction_type' => match ($value) {
'sale' => __('Kaufen', 'meinweimar-core'),
'rent' => __('Mieten', 'meinweimar-core'),
// Legacy transaction values are normalized to the current
// public rent choice by FieldSchemas before rendering.
'lease' => __('Mieten', 'meinweimar-core'),
'temporary' => __('Mieten', 'meinweimar-core'),
default => __('Weitere', 'meinweimar-core'),
},
'commission_vat' => 'included' === $value ? __('Enthalten', 'meinweimar-core') : ('excluded' === $value ? __('Nicht enthalten', 'meinweimar-core') : __('Weitere', 'meinweimar-core')),
'commission_payer' => match ($value) {
'buyer' => __('Käufer', 'meinweimar-core'),
'seller' => __('Verkäufer', 'meinweimar-core'),
'both' => __('Beide Parteien', 'meinweimar-core'),
'tenant' => __('Mieter', 'meinweimar-core'),
'landlord' => __('Vermieter', 'meinweimar-core'),
default => __('Weitere', 'meinweimar-core'),
},
'energy_certificate_type' => match ($value) {
'demand' => __('Bedarfsausweis', 'meinweimar-core'),
'consumption' => __('Verbrauchsausweis', 'meinweimar-core'),
'none' => __('Kein Energieausweis', 'meinweimar-core'),
default => __('Weitere', 'meinweimar-core'),
},
default => 'yes' === $value ? __('Ja', 'meinweimar-core') : __('Weitere', 'meinweimar-core'),
};
}
private static function opening_hours_rows(array $hours): array
{
$day_labels = [
'mon' => 'Mo', 'tue' => 'Di', 'wed' => 'Mi', 'thu' => 'Do',
'fri' => 'Fr', 'sat' => 'Sa', 'sun' => 'So',
];
$rows = [];
foreach ((array) ($hours['entries'] ?? []) as $entry) {
$days = array_values(array_filter(array_map(static fn ($day): string => $day_labels[(string) $day] ?? (string) $day, (array) ($entry['days'] ?? []))));
$day_label = implode(', ', $days);
if (count($days) >= 2) {
$day_label = $days[0] . '–' . $days[count($days) - 1];
}
if (!empty($entry['closed'])) {
$rows[] = trim($day_label . ': Geschlossen');
continue;
}
$slots = [];
foreach ((array) ($entry['slots'] ?? []) as $slot) {
if (!empty($slot['open']) && !empty($slot['close'])) {
$slots[] = (string) $slot['open'] . '–' . (string) $slot['close'];
}
}
if ($slots) {
$rows[] = trim($day_label . ': ' . implode(', ', $slots));
}
}
if (!$rows && !empty($hours['raw'])) {
$rows[] = (string) $hours['raw'];
}
return $rows;
}
private static function format_date(string $value): string
{
$timestamp = strtotime($value);
if (!$timestamp) {
return $value;
}
return wp_date((string) get_option('date_format'), $timestamp);
}
private static function format_datetime(string $value): string
{
$timestamp = strtotime($value);
if (!$timestamp) {
return $value;
}
$format = trim((string) get_option('date_format')) . ', ' . trim((string) get_option('time_format'));
return wp_date($format ?: 'c', $timestamp);
}
private static function event_status_label(string $status): string
{
return match ($status) {
'scheduled' => __('Geplant', 'meinweimar-core'),
'cancelled' => __('Abgesagt', 'meinweimar-core'),
'postponed' => __('Verschoben', 'meinweimar-core'),
'expired' => __('Beendet', 'meinweimar-core'),
default => __('Weitere', 'meinweimar-core'),
};
}
private static function event_ticket_label(string $mode): string
{
return match ($mode) {
'free' => __('Kostenlos', 'meinweimar-core'),
'external' => __('Externe Buchung', 'meinweimar-core'),
'internal' => __('Interne Buchung', 'meinweimar-core'),
'registration' => __('Anmeldung erforderlich', 'meinweimar-core'),
default => __('Weitere', 'meinweimar-core'),
};
}
private static function image_suggestion_markup(string $resource, int $post_id, string $title, bool $placeholder = false): string
{
$logged_in = is_user_logged_in();
$login_return_url = get_permalink($post_id) ?: home_url('/');
$panel_id = wp_unique_id('mw-image-suggestion-panel-');
ob_start();
?>
<section class="mw-image-suggestion" data-image-suggestion data-target-type="<?php echo esc_attr($resource); ?>" data-target-id="<?php echo esc_attr((string) $post_id); ?>">
<div class="mw-image-suggestion__empty">
<span class="mw-image-suggestion__icon" aria-hidden="true">⌁</span>
<h2><?php echo esc_html($placeholder ? __('Platzhalterbild vorhanden', 'meinweimar-core') : __('Kein Bild vorhanden', 'meinweimar-core')); ?></h2>
<p><?php echo esc_html($placeholder ? __('Dieses Bild ist vorläufig. Hilf mit, ein passenderes Bild vorzuschlagen.', 'meinweimar-core') : __('Hilf mit, diesen Eintrag sichtbar zu machen.', 'meinweimar-core')); ?></p>
<button type="button" class="mw-image-suggestion__toggle" data-image-suggestion-toggle aria-expanded="false" aria-controls="<?php echo esc_attr($panel_id); ?>"><?php esc_html_e('Bild vorschlagen', 'meinweimar-core'); ?></button>
</div>
<div id="<?php echo esc_attr($panel_id); ?>" class="mw-image-suggestion__panel" data-image-suggestion-panel hidden>
<?php if (!$logged_in) : ?>
<p><?php esc_html_e('Zum Vorschlagen eines Bildes musst du angemeldet sein.', 'meinweimar-core'); ?></p>
<a class="mw-image-suggestion__login" href="<?php echo esc_url(wp_login_url($login_return_url)); ?>"><?php esc_html_e('Anmelden', 'meinweimar-core'); ?></a>
<?php else : ?>
<form data-image-suggestion-form enctype="multipart/form-data">
<p class="mw-image-suggestion__target"><?php esc_html_e('Vorschlag für', 'meinweimar-core'); ?>: <strong><?php echo esc_html($title); ?></strong></p>
<label class="mw-image-suggestion__file-label">
<span><?php esc_html_e('Bild auswählen', 'meinweimar-core'); ?></span>
<input type="file" name="file" accept="image/jpeg,image/png,image/webp" required data-image-suggestion-file>
</label>
<figure class="mw-image-suggestion__preview" data-image-suggestion-preview hidden>
<img alt="<?php esc_attr_e('Vorschau des ausgewählten Bildes', 'meinweimar-core'); ?>" data-image-suggestion-preview-image>
</figure>
<fieldset>
<legend><?php esc_html_e('Rechte und Bestätigung', 'meinweimar-core'); ?></legend>
<label><input type="checkbox" name="rights_holder_confirmed" value="1" required> <?php esc_html_e('Ich bin Urheber des Bildes oder besitze ausreichende Nutzungsrechte.', 'meinweimar-core'); ?></label>
<label><input type="checkbox" name="lawful_source_confirmed" value="1" required> <?php esc_html_e('Das Bild wurde nicht unrechtmäßig aus dem Internet übernommen.', 'meinweimar-core'); ?></label>
<label><input type="checkbox" name="persons_consent_confirmed" value="1" required> <?php esc_html_e('Abgebildete Personen haben zugestimmt oder sind nicht identifizierbar.', 'meinweimar-core'); ?></label>
<label><input type="checkbox" name="platform_license_confirmed" value="1" required> <?php esc_html_e('Ich räume MeinWeimar die für Anzeige, Speicherung und Verarbeitung erforderlichen Nutzungsrechte ein.', 'meinweimar-core'); ?></label>
</fieldset>
<details>
<summary><?php esc_html_e('Optionale Angaben', 'meinweimar-core'); ?></summary>
<label><?php esc_html_e('Quelle', 'meinweimar-core'); ?><input type="text" name="source" maxlength="1000"></label>
<label><?php esc_html_e('Lizenz', 'meinweimar-core'); ?><input type="text" name="license" maxlength="500"></label>
<label><?php esc_html_e('Fotograf / Urheber', 'meinweimar-core'); ?><input type="text" name="photographer" maxlength="300"></label>
<label><?php esc_html_e('Bildbeschreibung', 'meinweimar-core'); ?><textarea name="description" maxlength="2000" rows="3"></textarea></label>
</details>
<button type="submit" class="mw-image-suggestion__submit"><?php esc_html_e('Bildvorschlag einreichen', 'meinweimar-core'); ?></button>
<p class="mw-image-suggestion__status" data-image-suggestion-status role="status" aria-live="polite"></p>
<div class="mw-image-suggestion__history" data-image-suggestion-history hidden aria-live="polite"></div>
</form>
<?php endif; ?>
</div>
</section>
<?php
return (string) ob_get_clean();
}
private static function resolve_post(int $post_id, string $post_type): ?WP_Post
{
$post = get_post($post_id ?: get_the_ID());
if (!$post instanceof WP_Post || $post->post_type !== $post_type) {
return null;
}
if ('publish' !== $post->post_status && !current_user_can('edit_post', $post->ID)) {
return null;
}
return $post;
}
private static function resolve_resource(int $post_id): ?WP_Post
{
$post = get_post($post_id ?: get_the_ID());
if (!$post instanceof WP_Post || !in_array($post->post_type, array_values(ContentTypes::public_post_types()), true)) {
return null;
}
if ('publish' !== $post->post_status && !current_user_can('edit_post', $post->ID)) {
return null;
}
return $post;
}
}
