glanzlicht / MeinWeimar_APP

Official MeinWeimar App

Verified secure showcase
204 Files 2 MB Public size 14 Redacted Dart · PHP · XML · GITIGNORE · YAML · PLIST

Project overview

MeinWeimar_APP is a verified code showcase with 204 visible project entries across 2 top-level modules. The primary detected language is Dart; protected files remain visible in the structure while their contents stay private.

README

MeinWeimar — Flutter App & WordPress Plugin

> Working in Progress · Private portfolio repository · Active development

MeinWeimar is a location-based local platform for residents, students and visitors. This repository intentionally contains only two product parts:

  1. the Flutter consumer app;
  2. the meinweimar-core WordPress plugin that provides the editorial/admin
  3. layer and the REST contract consumed by the app.

The complete public website, WordPress core, databases, private infrastructure and unrelated platform modules are not part of this repository.

MeinWeimar consumer app preview

Why this project is interesting

MeinWeimar combines a mobile/web Flutter client with WordPress used as a structured content and administration backend. The goal is a maintainable product boundary: content, branding, navigation, categories, media and configuration are managed in WordPress and delivered through a versioned REST contract instead of being hardcoded in the Flutter UI.

The project demonstrates work across:

  • Flutter/Dart application architecture for iOS, Android and Web;
  • WordPress plugin architecture with custom post types, taxonomies, admin
  • settings and REST endpoints;

  • localized German/English UI and backend-controlled labels;
  • discovery/list/detail flows for places, services, events, classifieds, jobs
  • and real estate;

  • map, GPS, geocoding, marker and navigation contracts;
  • caching, ETag/config-version handling and offline-safe fallbacks;
  • role-aware account, claim, favorites, moderation and messaging boundaries;
  • automated Flutter tests, PHP/REST contract checks and release builds.

Repository structure

flutter-app/       Flutter consumer application
wordpress-plugin/  meinweimar-core WordPress plugin
screenshots/       Curated portfolio preview only

Current status

This is an active Working in Progress project. The core architecture and many end-to-end flows are implemented and continuously tested. Native device polish, production content, final legal/privacy review and further product modules are still being developed. The repository does not claim to be a finished production release.

Local development

Flutter app

cd flutter-app
flutter pub get
flutter analyze
flutter test
flutter run --dart-define=MW_API_BASE_URL=https://example.test/wp-json/meinweimar/v1

The API base URL and other environment-specific values are supplied at build or run time. No credentials are stored in the repository.

WordPress plugin

Copy wordpress-plugin into a WordPress installation's wp-content/plugins/meinweimar-core directory and activate it in the admin area. The plugin owns the MeinWeimar content types, editorial settings, permissions, REST responses, media/configuration contracts and frontend integration points.

Privacy and repository boundary

This repository is private and is prepared for controlled technical review. Secrets, production database exports, user data, WordPress uploads, private keys and infrastructure configuration must remain outside version control. The public-facing portfolio page uses a separate, redacted presentation layer and does not expose the private repository directly.

For technical reviewers

The most relevant entry points are:

  • flutter-app/lib/core — shared configuration, networking, localization,
  • theme and location contracts;

  • flutter-app/lib/features — consumer-facing product surfaces;
  • flutter-app/lib/repositories — API boundary and data access;
  • wordpress-plugin/includes — plugin services, REST, admin and contracts;
  • wordpress-plugin/assets — admin/frontend integration assets.

MeinWeimar is deliberately presented as an evolving product rather than a finished demo: the repository shows the architectural decisions, the working implementation and the remaining engineering work transparently.

wordpress-plugin/includes/class-renderer.phpPHP
Protected before publication 16 protected passages across 4 lines are visibly marked below.
<?php
declare(strict_types=1);
namespace MeinWeimar;
final class Renderer
{
public static function hooks(): void
{
add_action('wp_enqueue_scripts', [self::class, 'register_assets']);
}
public static function register_assets(): void
{
wp_register_style('meinweimar-frontend', URL . 'assets/css/frontend.css', [], VERSION);
wp_register_script('meinweimar-frontend', URL . 'assets/js/frontend.js', [], VERSION, true);
wp_register_script('meinweimar-google-maps', URL . 'assets/js/google-maps-adapter.js', ['meinweimar-frontend'], VERSION, true);
wp_localize_script('meinweimar-frontend', 'MeinWeimarCore', [
'restUrl' => esc_url_raw(rest_url('meinweimar/v1/')),
'isLoggedIn' => is_user_logged_in(),
'restNonce' => is_user_logged_in() ? wp_create_nonce('wp_rest') : '',
'loginUrl' => wp_login_url(wp_get_referer() ?: home_url('/')),
'geo' => Settings::public_config()['geo'],
'googleMaps' => Settings::google_maps_frontend_config(),
'contentSchemas' => Settings::public_config()['content_schemas'],
'contentCategories' => Settings::public_config()['content_categories'],
// The mixed discovery/map surface is deliberately limited to
// geo-enabled community resources. Editorial News/Guides use
// their own list/detail surfaces and must never enter a map call.
'resourceTypes' => ContentTypes::geo_routes(),
'geoResourceTypes' => ContentTypes::geo_routes(),
'resourceLabels' => array_map(static fn (array $definition): string => (string) $definition['label'], ContentTypes::definitions()),
'strings' => [
'loading' => __('Wird geladen …', 'meinweimar-core'),
'noResults' => __('Keine Treffer gefunden.', 'meinweimar-core'),
'error' => __('Die Daten konnten nicht geladen werden.', 'meinweimar-core'),
'map' => __('Kartenübersicht', 'meinweimar-core'),
'list' => __('Liste', 'meinweimar-core'),
'distance' => __('Entfernung', 'meinweimar-core'),
'distanceUnit' => __('km', 'meinweimar-core'),
'radiusLabel' => __('Radius (km)', 'meinweimar-core'),
'radiusMetersUnit' => __('m', 'meinweimar-core'),
'radiusKilometersUnit' => __('km', 'meinweimar-core'),
'noGeo' => __('Für diese Treffer sind keine öffentlichen Positionen verfügbar.', 'meinweimar-core'),
'markerClusterLabel' => __('Marker-Cluster: %d Einträge', 'meinweimar-core'),
'currentLocationLabel' => __('Mein Standort', 'meinweimar-core'),
'searchLocationLabel' => __('Gesuchte Adresse', 'meinweimar-core'),
'close' => __('Schließen', 'meinweimar-core'),
'openDetails' => __('Details öffnen', 'meinweimar-core'),
'listingTransactionSale' => __('Kaufen', 'meinweimar-core'),
'listingTransactionRent' => __('Mieten', 'meinweimar-core'),
'listingPriceFree' => __('Zu verschenken', 'meinweimar-core'),
'listingPriceOnRequest' => __('Auf Anfrage', 'meinweimar-core'),
'listingAreaUnit' => __('m²', 'meinweimar-core'),
'listingRoomsUnit' => __('Zimmer', 'meinweimar-core'),
'useLocation' => __('Meinen Standort verwenden', 'meinweimar-core'),
'locationReady' => __('Standort aktiv', 'meinweimar-core'),
'locationDenied' => __('Standort konnte nicht verwendet werden.', 'meinweimar-core'),
'addressSearchLabel' => __('Adresse suchen', 'meinweimar-core'),
'addressSearchPlaceholder' => __('Adresse oder Ort eingeben …', 'meinweimar-core'),
'addressSearchSubmit' => __('Adresse suchen', 'meinweimar-core'),
'addressSearchNoResults' => __('Keine passenden Adressen gefunden.', 'meinweimar-core'),
'addressSearchUnavailable' => __('Die Adresssuche ist derzeit nicht verfügbar.', 'meinweimar-core'),
'addressSearchChoose' => __('Adresse verwenden', 'meinweimar-core'),
'mapProviderPending' => __('Keine Kartenquelle konfiguriert. Die Karte wird angezeigt, sobald ein freigegebener Anbieter hinterlegt ist.', 'meinweimar-core'),
'mapAdapterPending' => __('Die Kartenquelle ist konfiguriert, aber der Website-Kartenadapter ist noch nicht eingebunden.', 'meinweimar-core'),
'imageSuggestCta' => __('Bild vorschlagen', 'meinweimar-core'),
'imageSuggestBody' => __('Fehlt ein Bild oder ist nur ein Platzhalter vorhanden? Schlage ein passendes Bild vor. Jeder Vorschlag wird vor der Veröffentlichung geprüft.', 'meinweimar-core'),
'imageSuggestLogin' => __('Zum Vorschlagen eines Bildes musst du angemeldet sein.', 'meinweimar-core'),
'imageSuggestLoginAction' => __('Anmelden', 'meinweimar-core'),
'imageSuggestChoose' => __('Bild auswählen', 'meinweimar-core'),
'imageSuggestPreview' => __('Vorschau', 'meinweimar-core'),
'imageSuggestRightsHeading' => __('Rechte und Bestätigung', 'meinweimar-core'),
'imageSuggestRightsHolder' => __('Ich bin Urheber des Bildes oder besitze ausreichende Nutzungsrechte.', 'meinweimar-core'),
'imageSuggestLawfulSource' => __('Das Bild wurde nicht unrechtmäßig aus dem Internet übernommen.', 'meinweimar-core'),
'imageSuggestPersonsConsent' => __('Abgebildete Personen haben zugestimmt oder sind nicht identifizierbar.', 'meinweimar-core'),
'imageSuggestPlatformLicense' => __('Ich räume MeinWeimar die für Anzeige, Speicherung und Verarbeitung erforderlichen Nutzungsrechte ein.', 'meinweimar-core'),
'imageSuggestOptionalHeading' => __('Optionale Angaben', 'meinweimar-core'),
'imageSuggestSource' => __('Quelle', 'meinweimar-core'),
'imageSuggestLicense' => __('Lizenz', 'meinweimar-core'),
'imageSuggestPhotographer' => __('Fotograf / Urheber', 'meinweimar-core'),
'imageSuggestDescription' => __('Bildbeschreibung', 'meinweimar-core'),
'imageSuggestSubmit' => __('Bildvorschlag einreichen', 'meinweimar-core'),
'imageSuggestSubmitting' => __('Bildvorschlag wird eingereicht …', 'meinweimar-core'),
'imageSuggestSubmitted' => __('Bildvorschlag eingereicht. Er wird nun moderiert.', 'meinweimar-core'),
'imageSuggestError' => __('Der Bildvorschlag konnte nicht eingereicht werden.', 'meinweimar-core'),
'imageSuggestErrorAuth' => __('Zum Einreichen eines Bildvorschlags musst du angemeldet sein.', 'meinweimar-core'),
'imageSuggestErrorValidation' => __('Bitte prüfe Bilddatei und Pflichtangaben.', 'meinweimar-core'),
'imageSuggestErrorConflict' => __('Dieser Bildvorschlag steht im Konflikt mit einem bestehenden Vorschlag.', 'meinweimar-core'),
'imageSuggestErrorRateLimit' => __('Zu viele Bildvorschläge. Bitte später erneut versuchen.', 'meinweimar-core'),
'imageSuggestErrorSecurity' => __('Die Sicherheitsprüfung hat diesen Bildvorschlag abgewiesen oder ist noch nicht abgeschlossen.', 'meinweimar-core'),
'imageSuggestErrorGeneric' => __('Der Bildvorschlag konnte nicht verarbeitet werden.', 'meinweimar-core'),
'imageSuggestStatus' => __('Status', 'meinweimar-core'),
'imageSuggestStatusSubmitted' => __('Eingereicht', 'meinweimar-core'),
'imageSuggestStatusInReview' => __('In Prüfung', 'meinweimar-core'),
'imageSuggestStatusAccepted' => __('Akzeptiert', 'meinweimar-core'),
'imageSuggestStatusRejected' => __('Abgelehnt', 'meinweimar-core'),
'imageSuggestStatusNeedsInfo' => __('Rückfrage', 'meinweimar-core'),
'imageSuggestHistory' => __('Meine Bildvorschläge für diesen Eintrag', 'meinweimar-core'),
'imageSuggestSubmittedAt' => __('Eingereicht: {date}', 'meinweimar-core'),
'imageSuggestHistoryError' => __('Die Bildvorschläge konnten nicht geladen werden.', 'meinweimar-core'),
'imageSuggestHistoryRetry' => __('Erneut versuchen', 'meinweimar-core'),
'imageSuggestResponseBody' => __('Bitte ergänze die angeforderten Angaben. Deine Antwort wird erneut geprüft.', 'meinweimar-core'),
'imageSuggestResponseLabel' => __('Deine Antwort', 'meinweimar-core'),
'imageSuggestResponseSubmit' => __('Antwort senden', 'meinweimar-core'),
'imageSuggestResponseSubmitting' => __('Antwort wird gesendet …', 'meinweimar-core'),
'imageSuggestResponseSent' => __('Antwort gesendet. Der Bildvorschlag wird erneut geprüft.', 'meinweimar-core'),
'imageSuggestResponseRequired' => __('Bitte gib eine Antwort ein.', 'meinweimar-core'),
'accountSaving' => __('Profil wird gespeichert …', 'meinweimar-core'),
'accountSaved' => __('Profil gespeichert.', 'meinweimar-core'),
'accountError' => __('Das Profil konnte nicht gespeichert werden.', 'meinweimar-core'),
'accountFavoritesHeading' => __('Meine gespeicherten Inhalte', 'meinweimar-core'),
'accountFavoritesLoading' => __('Gespeicherte Inhalte werden geladen …', 'meinweimar-core'),
'accountFavoritesEmpty' => __('Du hast noch keine Inhalte gespeichert.', 'meinweimar-core'),
'accountFavoritesError' => __('Die gespeicherten Inhalte konnten nicht geladen werden.', 'meinweimar-core'),
'accountFavoritesOpen' => __('Inhalt öffnen', 'meinweimar-core'),
'favoriteSave' => __('Speichern', 'meinweimar-core'),
'favoriteRemove' => __('Aus gespeicherten entfernen', 'meinweimar-core'),
'favoriteSaving' => __('Speicherung wird aktualisiert …', 'meinweimar-core'),
'favoriteSaved' => __('In deinen gespeicherten Inhalten abgelegt.', 'meinweimar-core'),
'favoriteRemoved' => __('Aus deinen gespeicherten Inhalten entfernt.', 'meinweimar-core'),
'favoriteError' => __('Die Speicherung konnte nicht aktualisiert werden.', 'meinweimar-core'),
],
]);
}
public static function enqueue_assets(): void
{
wp_enqueue_style('meinweimar-frontend');
wp_enqueue_script('meinweimar-frontend');
wp_enqueue_script('meinweimar-google-maps');
}
public static function brand_markup(array $attributes = []): string
{
self::enqueue_assets();
$config = Settings::public_config();
$requested_wordmark = trim((string) ($attributes['wordmark'] ?? ''));
$wordmark = $requested_wordmark ?: $config['brand']['wordmark'];
$variant = 'dark' === ($attributes['variant'] ?? '') ? 'dark' : 'light';
$logo_config = $config['brand']['logo_' . $variant] ?? [];
if (empty($logo_config['url'])) {
$logo_config = $config['brand']['logo_light'] ?? [
'url' => $config['brand']['logo_url'],
'srcset' => $config['brand']['logo_srcset'],
'alt' => $config['brand']['logo_alt'],
];
}
$logo = (string) ($logo_config['url'] ?? '');
$logo_srcset = (string) ($logo_config['srcset'] ?? '');
$logo_alt = (string) ($logo_config['alt'] ?? '');
$brand_style = sprintf(
'--mw-primary:%s;--mw-secondary:%s;',
esc_attr($config['brand']['primary_color']),
esc_attr($config['brand']['secondary_color'])
);
ob_start();
?>
<a class="mw-brand mw-brand--<?php echo esc_attr($variant); ?>" style="<?php echo esc_attr($brand_style); ?>" href="<?php echo esc_url(home_url('/')); ?>" aria-label="<?php echo esc_attr($wordmark); ?>">
<?php if ($logo) : ?>
<img class="mw-brand__logo" src="<?php echo esc_url($logo); ?>"<?php if ($logo_srcset) : ?> srcset="<?php echo esc_attr($logo_srcset); ?>" sizes="(max-width: 640px) 160px, 240px"<?php endif; ?> alt="<?php echo esc_attr($logo_alt ?: $wordmark); ?>">
<?php endif; ?>
<span class="mw-brand__wordmark"><?php echo esc_html($wordmark); ?></span>
</a>
<?php
return (string) ob_get_clean();
}
public static function account_markup(array $attributes = []): string
{
self::enqueue_assets();
$config = Settings::public_config();
$brand_style = sprintf(
'--mw-primary:%s;--mw-secondary:%s;',
esc_attr($config['brand']['primary_color']),
esc_attr($config['brand']['secondary_color'])
);
$return_url = wp_get_referer() ?: home_url('/');
$favorites_heading_id = function_exists('wp_unique_id') ? wp_unique_id('mw-account-favorites-') : 'mw-account-favorites';
ob_start();
?>
<section class="mw-account" style="<?php echo esc_attr($brand_style); ?>" data-mw-account>
<?php if (!is_user_logged_in()) : ?>
<div class="mw-account__gate">
<p class="mw-account__eyebrow"><?php esc_html_e('MeinWeimar', 'meinweimar-core'); ?></p>
<h2><?php esc_html_e('Mein Konto', 'meinweimar-core'); ?></h2>
<p><?php esc_html_e('Melde dich an, um dein Profil, deine Favoriten und deine eigenen Aktivitäten zu verwalten.', 'meinweimar-core'); ?></p>
<a class="mw-account__primary" href="<?php echo esc_url(wp_login_url($return_url)); ?>"><?php esc_html_e('Anmelden', 'meinweimar-core'); ?></a>
<?php if (get_option('users_can_register')) : ?>
<a class="mw-account__secondary" href="<?php echo esc_url(wp_registration_url()); ?>"><?php esc_html_e('Konto erstellen', 'meinweimar-core'); ?></a>
<?php endif; ?>
</div>
<?php else :
$profile_response = AccountApi::profile_data(get_current_user_id(), true);
$profile = method_exists($profile_response, 'get_data') ? (array) $profile_response->get_data() : [];
$avatar_url = (string) ($profile['avatar_url'] ?? '');
$available_locales = array_values(array_filter(array_map('sanitize_text_field', (array) ($config['locales'] ?? []))));
$profile_locale = in_array((string) ($profile['locale'] ?? ''), $available_locales, true) ? (string) $profile['locale'] : '';
?>
<div class="mw-account__header">
<?php if ($avatar_url) : ?><img class="mw-account__avatar" src="<?php echo esc_url($avatar_url); ?>" alt="<?php echo esc_attr((string) ($profile['nickname'] ?? '')); ?>"><?php endif; ?>
<div>
<p class="mw-account__eyebrow"><?php esc_html_e('MeinWeimar', 'meinweimar-core'); ?></p>
<h2><?php esc_html_e('Mein Konto', 'meinweimar-core'); ?></h2>
<p class="mw-account__identity"><?php echo esc_html((string) ($profile['nickname'] ?? $profile['display_name'] ?? '')); ?></p>
</div>
</div>
<form class="mw-account__form" data-mw-account-form>
<div class="mw-account__grid">
<label><?php esc_html_e('Anzeigename', 'meinweimar-core'); ?><input type="text" name="nickname" maxlength="80" value="<?php echo esc_attr((string) ($profile['nickname'] ?? '')); ?>"></label>
<label><?php esc_html_e('Status', 'meinweimar-core'); ?><input type="text" name="status" maxlength="160" value="<?php echo esc_attr((string) ($profile['status'] ?? '')); ?>"></label>
<label><?php esc_html_e('Alter', 'meinweimar-core'); ?><input type="number" name="age" min="13" max="120" value="<?php echo esc_attr((string) ($profile['age'] ?? '')); ?>"></label>
<label><?php esc_html_e('Geschlecht', 'meinweimar-core'); ?><input type="text" name="gender" maxlength="40" value="<?php echo esc_attr((string) ($profile['gender'] ?? '')); ?>"></label>
<label><?php esc_html_e('Bevorzugte Sprache', 'meinweimar-core'); ?><select name="locale"><option value=""><?php esc_html_e('Systemstandard', 'meinweimar-core'); ?></option><?php foreach ($available_locales as $locale) : ?><option value="<?php echo esc_attr($locale); ?>" <?php selected($profile_locale, $locale); ?>><?php echo esc_html($locale); ?></option><?php endforeach; ?></select></label>
<label><?php esc_html_e('Profil-Sichtbarkeit', 'meinweimar-core'); ?><select name="profile_visibility"><option value="public" <?php selected((string) ($profile['profile_visibility'] ?? 'public'), 'public'); ?>><?php esc_html_e('Öffentlich', 'meinweimar-core'); ?></option><option value="private" <?php selected((string) ($profile['profile_visibility'] ?? 'public'), 'private'); ?>><?php esc_html_e('Privat', 'meinweimar-core'); ?></option></select></label>
<label><?php esc_html_e('Profilbild-Sichtbarkeit', 'meinweimar-core'); ?><select name="avatar_visibility"><option value="public" <?php selected((string) ($profile['avatar_visibility'] ?? 'private'), 'public'); ?>><?php esc_html_e('Öffentlich', 'meinweimar-core'); ?></option><option value="private" <?php selected((string) ($profile['avatar_visibility'] ?? 'private'), 'private'); ?>><?php esc_html_e('Privat', 'meinweimar-core'); ?></option></select></label>
</div>
<label><?php esc_html_e('Über mich', 'meinweimar-core'); ?><textarea name="bio" maxlength="1000" rows="5"><?php echo esc_textarea((string) ($profile['bio'] ?? '')); ?></textarea></label>
<button class="mw-account__primary" type="submit"><?php esc_html_e('Profil speichern', 'meinweimar-core'); ?></button>
<p class="mw-account__status" data-mw-account-status role="status" aria-live="polite"></p>
</form>
<section class="mw-account__section" data-mw-account-favorites aria-labelledby="<?php echo esc_attr($favorites_heading_id); ?>">
<h3 id="<?php echo esc_attr($favorites_heading_id); ?>"><?php esc_html_e('Meine gespeicherten Inhalte', 'meinweimar-core'); ?></h3>
<p class="mw-account__status" data-mw-account-favorites-status role="status" aria-live="polite"></p>
<div class="mw-account__saved-list" data-mw-account-favorites-list></div>
</section>
<p class="mw-account__footer"><a href="<?php echo esc_url(wp_logout_url($return_url)); ?>"><?php esc_html_e('Abmelden', 'meinweimar-core'); ?></a></p>
<?php endif; ?>
</section>
<?php
return (string) ob_get_clean();
}
public static function discovery_markup(array $attributes = []): string
{
self::enqueue_assets();
$available_types = array_keys(ContentTypes::public_post_types());
$geo_types = ContentTypes::geo_routes();
$requested_type = (string) ($attributes['type'] ?? 'places');
$type = 'all' === $requested_type || in_array($requested_type, $available_types, true) ? $requested_type : 'places';
$map_enabled = 'all' === $type || in_array($type, $geo_types, true);
$per_page = max(1, min(50, absint($attributes['per_page'] ?? 12)));
$default_search = sanitize_text_field((string) ($attributes['search'] ?? ''));
$default_category = sanitize_title((string) ($attributes['category'] ?? ''));
// A mixed discovery surface cannot safely infer which CPT owns a
// shortcode-provided category slug. Category selection on that
// surface is therefore explicit and route-scoped in the UI.
$effective_default_category = 'all' === $type ? '' : $default_category;
$default_region = sanitize_title((string) ($attributes['region'] ?? ''));
$allowed_sorts = ['auto', 'distance', 'newest', 'title'];
$requested_sort = (string) ($attributes['sort'] ?? 'auto');
$default_sort = in_array($requested_sort, $allowed_sorts, true) ? $requested_sort : 'auto';
$config = Settings::public_config();
$default_radius = round(max(0.01, min(50, (float) ($attributes['radius_km'] ?? ($config['geo']['default_radius_km'] ?? 10)))), 2);
$brand_style = sprintf(
'--mw-primary:%s;--mw-secondary:%s;',
esc_attr($config['brand']['primary_color']),
esc_attr($config['brand']['secondary_color'])
);
$search_id = function_exists('wp_unique_id') ? wp_unique_id('mw-discovery-search-') : 'mw-discovery-search';
$sort_id = function_exists('wp_unique_id') ? wp_unique_id('mw-discovery-sort-') : 'mw-discovery-sort';
$category_id = function_exists('wp_unique_id') ? wp_unique_id('mw-discovery-category-') : 'mw-discovery-category';
$region_id = function_exists('wp_unique_id') ? wp_unique_id('mw-discovery-region-') : 'mw-discovery-region';
$radius_id = function_exists('wp_unique_id') ? wp_unique_id('mw-discovery-radius-') : 'mw-discovery-radius';
$address_id = function_exists('wp_unique_id') ? wp_unique_id('mw-discovery-address-') : 'mw-discovery-address';
$address_results_id = function_exists('wp_unique_id') ? wp_unique_id('mw-discovery-address-results-') : 'mw-discovery-address-results';
$category_options = self::discovery_category_options($type, 'all' === $type ? $geo_types : $available_types);
$region_options = self::discovery_region_options();
$schema_filters = self::discovery_schema_filters($type, 'all' === $type ? $geo_types : $available_types, (array) ($config['content_schemas'] ?? []));
$dedicated_listing_route = in_array($type, ['classifieds', 'jobs', 'real-estate'], true);
ob_start();
?>
<section class="mw-discovery" style="<?php echo esc_attr($brand_style); ?>" data-mw-discovery data-type="<?php echo esc_attr($type); ?>" data-map-enabled="<?php echo $map_enabled ? 'true' : 'false'; ?>" data-per-page="<?php echo esc_attr((string) $per_page); ?>" data-default-search="<?php echo esc_attr($default_search); ?>" data-default-category="<?php echo esc_attr($effective_default_category); ?>" data-default-region="<?php echo esc_attr($default_region); ?>" data-default-sort="<?php echo esc_attr($default_sort); ?>" data-default-radius="<?php echo esc_attr((string) $default_radius); ?>">
<div class="mw-discovery__toolbar">
<label class="screen-reader-text" for="<?php echo esc_attr($search_id); ?>"><?php esc_html_e('Suche', 'meinweimar-core'); ?></label>
<input id="<?php echo esc_attr($search_id); ?>" class="mw-discovery__search" type="search" value="<?php echo esc_attr($default_search); ?>" placeholder="<?php esc_attr_e('In der Nähe suchen …', 'meinweimar-core'); ?>">
<button type="button" class="mw-discovery__submit"><?php esc_html_e('Suchen', 'meinweimar-core'); ?></button>
<label class="screen-reader-text" for="<?php echo esc_attr($sort_id); ?>"><?php esc_html_e('Sortierung', 'meinweimar-core'); ?></label>
<select id="<?php echo esc_attr($sort_id); ?>" class="mw-discovery__sort" data-sort>
<option value="auto" <?php selected($default_sort, 'auto'); ?>><?php esc_html_e('Automatisch', 'meinweimar-core'); ?></option>
<option value="distance" <?php selected($default_sort, 'distance'); ?>><?php esc_html_e('Entfernung', 'meinweimar-core'); ?></option>
<option value="newest" <?php selected($default_sort, 'newest'); ?>><?php esc_html_e('Neueste', 'meinweimar-core'); ?></option>
<option value="title" <?php selected($default_sort, 'title'); ?>><?php esc_html_e('Alphabetisch', 'meinweimar-core'); ?></option>
</select>
<label class="screen-reader-text" for="<?php echo esc_attr($category_id); ?>"><?php esc_html_e('Kategorie', 'meinweimar-core'); ?></label>
<select id="<?php echo esc_attr($category_id); ?>" class="mw-discovery__filter<?php echo $dedicated_listing_route ? ' mw-discovery__native-select' : ''; ?>" data-category>
<option value=""><?php esc_html_e('Alle Kategorien', 'meinweimar-core'); ?></option>
<?php if ($effective_default_category && !array_filter($category_options, static fn (array $option): bool => $option['value'] === $effective_default_category)) : ?>
<option value="<?php echo esc_attr($effective_default_category); ?>" selected><?php echo esc_html($effective_default_category); ?></option>
<?php endif; ?>
<?php foreach ($category_options as $option) : ?>
<option value="<?php echo esc_attr($option['value']); ?>" data-category-route="<?php echo esc_attr((string) ($option['route'] ?? '')); ?>" <?php selected($effective_default_category, $option['value']); ?>><?php echo esc_html($option['label']); ?></option>
<?php endforeach; ?>
</select>
<?php if ($dedicated_listing_route && $category_options) : ?>
<div class="mw-discovery__category-choices" role="group" aria-label="<?php esc_attr_e('Kategorie', 'meinweimar-core'); ?>">
<button type="button" class="mw-discovery__choice mw-discovery__choice--all<?php echo '' === $effective_default_category ? ' is-active' : ''; ?>" data-category-choice="" aria-pressed="<?php echo '' === $effective_default_category ? 'true' : 'false'; ?>"><?php esc_html_e('Alle', 'meinweimar-core'); ?></button>
<?php foreach ($category_options as $option) : ?>
<button type="button" class="mw-discovery__choice<?php echo $effective_default_category === $option['value'] ? ' is-active' : ''; ?>" data-category-choice="<?php echo esc_attr($option['value']); ?>" aria-pressed="<?php echo $effective_default_category === $option['value'] ? 'true' : 'false'; ?>"><?php echo esc_html($option['label']); ?></button>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php if ($map_enabled) : ?>
<label class="screen-reader-text" for="<?php echo esc_attr($region_id); ?>"><?php esc_html_e('Region', 'meinweimar-core'); ?></label>
<select id="<?php echo esc_attr($region_id); ?>" class="mw-discovery__filter" data-region>
<option value=""><?php esc_html_e('Alle Regionen', 'meinweimar-core'); ?></option>
<?php if ($default_region && !array_filter($region_options, static fn (array $option): bool => $option['value'] === $default_region)) : ?>
<option value="<?php echo esc_attr($default_region); ?>" selected><?php echo esc_html($default_region); ?></option>
<?php endif; ?>
<?php foreach ($region_options as $option) : ?>
<option value="<?php echo esc_attr($option['value']); ?>" <?php selected($default_region, $option['value']); ?>><?php echo esc_html($option['label']); ?></option>
<?php endforeach; ?>
</select>
<div class="mw-discovery__radius-control">
<label for="<?php echo esc_attr($radius_id); ?>"><?php esc_html_e('Radius', 'meinweimar-core'); ?></label>
<input id="<?php echo esc_attr($radius_id); ?>" class="mw-discovery__radius" type="range" min="0.01" max="50" step="0.01" value="<?php echo esc_attr((string) $default_radius); ?>" data-radius aria-valuemin="0.01" aria-valuemax="50">
<output data-radius-label for="<?php echo esc_attr($radius_id); ?>"><?php echo esc_html($default_radius < 1 ? round($default_radius * 1000) . ' ' . __('m', 'meinweimar-core') : rtrim(rtrim(number_format($default_radius, 2, '.', ''), '0'), '.') . ' ' . __('km', 'meinweimar-core')); ?></output>
</div>
<button type="button" class="mw-discovery__locate" data-geolocate aria-pressed="false"><?php esc_html_e('Meinen Standort verwenden', 'meinweimar-core'); ?></button>
<button type="button" class="mw-discovery__mode is-active" data-mode="list"><?php esc_html_e('Liste', 'meinweimar-core'); ?></button>
<button type="button" class="mw-discovery__mode" data-mode="map"><?php esc_html_e('Karte', 'meinweimar-core'); ?></button>
<?php endif; ?>
</div>
<?php if ($map_enabled) : ?>
<div class="mw-discovery__address-control">
<label class="screen-reader-text" for="<?php echo esc_attr($address_id); ?>"><?php esc_html_e('Adresse suchen', 'meinweimar-core'); ?></label>
<input id="<?php echo esc_attr($address_id); ?>" class="mw-discovery__address" type="search" autocomplete="street-address" aria-autocomplete="list" aria-controls="<?php echo esc_attr($address_results_id); ?>" placeholder="<?php esc_attr_e('Adresse oder Ort eingeben …', 'meinweimar-core'); ?>" data-address-search>
<button type="button" class="mw-discovery__address-submit" data-address-submit><?php esc_html_e('Adresse suchen', 'meinweimar-core'); ?></button>
<div id="<?php echo esc_attr($address_results_id); ?>" class="mw-discovery__address-results" data-address-results role="listbox" hidden></div>
</div>
<?php endif; ?>
<?php if ($schema_filters) : ?>
<div class="mw-discovery__schema-filters" data-schema-filters>
<?php foreach ($schema_filters as $filter) : ?>
<?php
$filter_id = function_exists('wp_unique_id') ? wp_unique_id('mw-discovery-schema-filter-') : 'mw-discovery-schema-filter-' . sanitize_key((string) $filter['key']);
$field_key = (string) $filter['key'];
$field_input = (string) ($filter['input'] ?? 'select');
$category_conditions = array_values(array_filter(array_map('sanitize_title', (array) ($filter['category_conditions'] ?? []))));
$field_conditions = array_values((array) ($filter['conditions'] ?? []));
?>
<?php $is_transaction_choice = 'real-estate' === $type && 'transaction_type' === $field_key && 'select' === $field_input; ?>
<div class="mw-discovery__schema-filter<?php echo 'range' === $field_input ? ' mw-discovery__schema-filter--range' : ''; ?><?php echo $is_transaction_choice ? ' mw-discovery__schema-filter--segments' : ''; ?>" data-schema-filter-wrapper data-schema-filter-key="<?php echo esc_attr($field_key); ?>" data-schema-filter-categories="<?php echo esc_attr((string) wp_json_encode($category_conditions)); ?>" data-schema-filter-conditions="<?php echo esc_attr((string) wp_json_encode($field_conditions)); ?>">
<span><?php echo esc_html((string) $filter['label']); ?></span>
<?php if ('range' === $field_input) : ?>
<div class="mw-discovery__schema-range" role="group" aria-label="<?php echo esc_attr((string) $filter['label']); ?>">
<label class="screen-reader-text" for="<?php echo esc_attr($filter_id . '-min'); ?>"><?php esc_html_e('Von', 'meinweimar-core'); ?></label>
<input id="<?php echo esc_attr($filter_id . '-min'); ?>" type="number" inputmode="decimal" data-schema-filter data-schema-filter-bound="min" data-[REDACTED] placeholder="<?php esc_attr_e('Von', 'meinweimar-core'); ?>"<?php echo !empty($filter['unit']) ? ' aria-label="' . esc_attr(sprintf(__('Von (%s)', 'meinweimar-core'), (string) $filter['unit'])) . '"' : ''; ?>>
<span aria-hidden="true">–</span>
<label class="screen-reader-text" for="<?php echo esc_attr($filter_id . '-max'); ?>"><?php esc_html_e('Bis', 'meinweimar-core'); ?></label>
<input id="<?php echo esc_attr($filter_id . '-max'); ?>" type="number" inputmode="decimal" data-schema-filter data-schema-filter-bound="max" data-[REDACTED] placeholder="<?php esc_attr_e('Bis', 'meinweimar-core'); ?>"<?php echo !empty($filter['unit']) ? ' aria-label="' . esc_attr(sprintf(__('Bis (%s)', 'meinweimar-core'), (string) $filter['unit'])) . '"' : ''; ?>>
</div>
<?php elseif ('date' === $field_input) : ?>
<input id="<?php echo esc_attr($filter_id); ?>" type="date" data-schema-filter data-[REDACTED]>
<?php else : ?>
<select id="<?php echo esc_attr($filter_id); ?>" class="<?php echo $is_transaction_choice ? 'mw-discovery__native-select' : ''; ?>" data-schema-filter data-[REDACTED]>
<option value=""><?php esc_html_e('Alle', 'meinweimar-core'); ?></option>
<?php foreach ((array) ($filter['values'] ?? []) as $value) : ?>
<option value="<?php echo esc_attr((string) $value); ?>"><?php echo esc_html(self::schema_filter_value_label((string) $value, $field_key)); ?></option>
<?php endforeach; ?>
</select>
<?php if ($is_transaction_choice) : ?>
<div class="mw-discovery__segment-group" role="group" aria-label="<?php echo esc_attr((string) $filter['label']); ?>">
<button type="button" class="mw-discovery__segment is-active" data-schema-choice-value="" aria-pressed="true"><?php esc_html_e('Alle', 'meinweimar-core'); ?></button>
<?php foreach ((array) ($filter['values'] ?? []) as $value) : ?>
<button type="button" class="mw-discovery__segment" data-schema-choice-value="<?php echo esc_attr((string) $value); ?>" aria-pressed="false"><?php echo esc_html(self::schema_filter_value_label((string) $value, $field_key)); ?></button>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="mw-discovery__status" role="status" aria-live="polite"></div>
<div class="mw-discovery__body">
<div class="mw-discovery__list" data-results></div>
<?php if ($map_enabled) : ?>
<div class="mw-discovery__map" data-map hidden>
<div class="mw-discovery__map-heading"><?php esc_html_e('Kartenübersicht', 'meinweimar-core'); ?></div>
<div class="mw-discovery__google-map" data-google-map role="region" aria-label="<?php esc_attr_e('Google-Karte', 'meinweimar-core'); ?>"></div>
<div class="mw-discovery__markers" data-markers></div>
<div class="mw-discovery__map-preview" data-map-preview hidden></div>
<p class="mw-discovery__map-note"><?php esc_html_e('Der Kartenadapter wird an dieser gemeinsamen Geo-Schnittstelle angebunden.', 'meinweimar-core'); ?></p>
</div>
<?php endif; ?>
</div>
</section>
<?php
return (string) ob_get_clean();
}
private static function discovery_category_options(string $type, array $available_types): array
{
if (!function_exists('get_terms')) {
return [];
}
$routes = 'all' === $type ? $available_types : [$type];
$options = [];
foreach ($routes as $route) {
$definition = ContentTypes::definitions()[$route] ?? null;
$taxonomy = is_array($definition) ? (string) ($definition['category'] ?? '') : '';
if (!$taxonomy || !taxonomy_exists($taxonomy)) {
continue;
}
$terms = get_terms(['taxonomy' => $taxonomy, 'hide_empty' => true, 'number' => 100, 'orderby' => 'name', 'order' => 'ASC']);
if (is_wp_error($terms)) {
continue;
}
foreach ((array) $terms as $term) {
$value = sanitize_title((string) ($term->slug ?? ''));
if (!$value) {
continue;
}
$option_key = 'all' === $type ? $route . ':' . $value : $value;
$label = (string) ($term->name ?? $value);
if ('all' === $type && is_array($definition)) {
$label = sprintf('%s: %s', (string) $definition['label'], $label);
}
$options[$option_key] = ['value' => $value, 'label' => $label, 'route' => $route];
}
}
return array_values($options);
}
/**
* Build discovery controls from the public FieldSchemas projection. Enum,
* boolean, date and numeric range filters all use the shared REST query
* contract; range bounds are represented as <api_key>_min/max controls.
*
* @param array<string, mixed> $schemas
* @return list<array<string, mixed>>
*/
private static function discovery_schema_filters(string $type, array $available_types, array $schemas): array
{
// Route-specific fields must never be merged into one cross-CPT
// control. The mixed discovery endpoint still supports shared search,
// category, region and geo parameters; typed filters belong to the
// dedicated route surface where their FieldSchemas constraints are
// unambiguous.
if ('all' === $type) {
return [];
}
$routes = 'all' === $type ? $available_types : [$type];
$filters = [];
foreach ($routes as $route) {
foreach ((array) ($schemas[$route] ?? []) as $schema_key => $field) {
if (!is_array($field) || empty($field['filter']['enabled'])) {
continue;
}
$kind = (string) ($field['filter']['kind'] ?? '');
if (!in_array($kind, ['enum', 'boolean', 'date', 'range'], true)) {
continue;
}
if ('date' === $kind && 'date' !== (string) ($field['input'] ?? '')) {
continue;
}
$api_key = (string) preg_replace('/^[^.]+\./', '', (string) $schema_key);
if ('' === $api_key) {
continue;
}
if (!isset($filters[$api_key])) {
$filters[$api_key] = [
'key' => $api_key,
'input' => 'range' === $kind
? 'range'
: ('date' === ($field['input'] ?? '') ? 'date' : 'select'),
'values' => [],
'unit' => $field['unit'] ?? null,
'filter_kind' => $kind,
'category_conditions' => [],
'conditions' => [],
'label' => self::schema_filter_label($api_key),
];
}
$filters[$api_key]['values'] = array_values(array_unique(array_merge(
(array) $filters[$api_key]['values'],
'boolean' === $kind ? ['1', '0'] : (array) ($field['values'] ?? [])
)));
$filters[$api_key]['category_conditions'] = array_values(array_unique(array_merge(
(array) $filters[$api_key]['category_conditions'],
(array) ($field['category_conditions'] ?? [])
)));
foreach ((array) ($field['conditions'] ?? []) as $condition) {
if (is_array($condition) && !in_array($condition, $filters[$api_key]['conditions'], true)) {
$filters[$api_key]['conditions'][] = $condition;
}
}
}
}
return array_values($filters);
}
private static function schema_filter_label(string $key): string
{
$labels = [
'offer_type' => __('Angebotsart', 'meinweimar-core'),
'provider_type' => __('Anbieter', 'meinweimar-core'),
'price_type' => __('Preisangabe', 'meinweimar-core'),
'price_interval' => __('Preisintervall', 'meinweimar-core'),
'transaction_type' => __('Vermarktungsart', 'meinweimar-core'),
'commission_applicable' => __('Provision', 'meinweimar-core'),
'heating_costs_included' => __('Heizkosten in Nebenkosten enthalten', 'meinweimar-core'),
'condition' => __('Zustand', 'meinweimar-core'),
'delivery_mode' => __('Übergabe / Versand', 'meinweimar-core'),
'employment_type' => __('Beschäftigungsart', 'meinweimar-core'),
'work_mode' => __('Arbeitsmodell', 'meinweimar-core'),
'featured' => __('Hervorgehoben', 'meinweimar-core'),
'start' => __('Beginn', 'meinweimar-core'),
'end' => __('Ende', 'meinweimar-core'),
'available_from' => __('Verfügbar ab', 'meinweimar-core'),
];
return $labels[$key] ?? __('Weitere Filter', 'meinweimar-core');
}
private static function schema_filter_value_label(string $value, string $key = ''): string
{
if ('ticket_mode' === $key) {
return match ($value) {
'free' => __('Kostenlos', 'meinweimar-core'),
'external' => __('Externe Buchung', 'meinweimar-core'),
'internal' => __('Interne Buchung', 'meinweimar-core'),
'registration' => __('Anmeldung erforderlich', 'meinweimar-core'),
default => __('Weitere', 'meinweimar-core'),
};
}
return match ($value) {
'1' => __('Ja', 'meinweimar-core'),
'0' => __('Nein', 'meinweimar-core'),
'offer' => __('Angebot', 'meinweimar-core'),
'search' => __('Gesuch', 'meinweimar-core'),
'employment' => __('Stelle', 'meinweimar-core'),
'rent' => __('Miete', 'meinweimar-core'),
'sale' => __('Kaufen', 'meinweimar-core'),
'lease' => __('Miete', 'meinweimar-core'),
'temporary' => __('Miete', 'meinweimar-core'),
'private' => __('Privat', 'meinweimar-core'),
'business' => __('Gewerblich', 'meinweimar-core'),
'organization' => __('Organisation', 'meinweimar-core'),
'broker' => __('Makler', 'meinweimar-core'),
'employer' => __('Arbeitgeber', 'meinweimar-core'),
'fixed' => __('Festpreis', 'meinweimar-core'),
'negotiable' => __('Verhandelbar', 'meinweimar-core'),
'free' => __('Zu verschenken', 'meinweimar-core'),
'on_request' => __('Auf Anfrage', 'meinweimar-core'),
'range' => __('Spanne', 'meinweimar-core'),
'once' => __('Einmalig', 'meinweimar-core'),
'hour' => __('Pro Stunde', 'meinweimar-core'),
'month' => __('Pro Monat', 'meinweimar-core'),
'year' => __('Pro Jahr', 'meinweimar-core'),
'yes' => __('Provisionspflichtig', 'meinweimar-core'),
'no' => __('Provisionsfrei', 'meinweimar-core'),
'percentage' => __('Prozentsatz', 'meinweimar-core'),
'monthly_rent' => __('Monatliche Kaltmieten', 'meinweimar-core'),
'included' => __('Enthalten', 'meinweimar-core'),
'excluded' => __('Nicht enthalten', 'meinweimar-core'),
'new' => __('Neu', 'meinweimar-core'),
'used' => __('Gebraucht', 'meinweimar-core'),
'defective' => __('Defekt', 'meinweimar-core'),
'pickup' => __('Abholung', 'meinweimar-core'),
'shipping' => __('Versand', 'meinweimar-core'),
'both' => __('Abholung oder Versand', 'meinweimar-core'),
'digital' => __('Digital', 'meinweimar-core'),
'none' => __('Keine Angabe', 'meinweimar-core'),
'full_time' => __('Vollzeit', 'meinweimar-core'),
'part_time' => __('Teilzeit', 'meinweimar-core'),
'freelance' => __('Freiberuflich', 'meinweimar-core'),
'apprenticeship' => __('Ausbildung', 'meinweimar-core'),
'internship' => __('Praktikum', 'meinweimar-core'),
'onsite' => __('Vor Ort', 'meinweimar-core'),
'hybrid' => __('Hybrid', 'meinweimar-core'),
'remote' => __('Fernarbeit', 'meinweimar-core'),
'scheduled' => __('Geplant', 'meinweimar-core'),
'cancelled' => __('Abgesagt', 'meinweimar-core'),
'postponed' => __('Verschoben', 'meinweimar-core'),
'expired' => __('Abgelaufen', 'meinweimar-core'),
'EUR' => __('Euro', 'meinweimar-core'),
'USD' => __('US-Dollar', 'meinweimar-core'),
'GBP' => __('Britische Pfund', 'meinweimar-core'),
default => __('Weitere', 'meinweimar-core'),
};
}
private static function discovery_region_options(): array
{
if (!function_exists('get_terms') || !taxonomy_exists('mw_region')) {
return [];
}
$terms = get_terms(['taxonomy' => 'mw_region', 'hide_empty' => true, 'number' => 100, 'orderby' => 'name', 'order' => 'ASC']);
if (is_wp_error($terms)) {
return [];
}
$options = [];
foreach ((array) $terms as $term) {
$value = sanitize_title((string) ($term->slug ?? ''));
if ($value) {
$options[$value] = ['value' => $value, 'label' => (string) ($term->name ?? $value)];
}
}
return array_values($options);
}
public static function shell_markup(array $attributes = []): string
{
self::enqueue_assets();
$brand = self::brand_markup();
ob_start();
?>
<header class="mw-shell">
<div class="mw-shell__inner">
<?php echo $brand; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php echo self::navigation_markup(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
</header>
<?php
return (string) ob_get_clean();
}
public static function navigation_markup(): string
{
self::enqueue_assets();
$navigation = Settings::public_config()['navigation'];
$navigation_id = wp_unique_id('mw-primary-navigation-');
ob_start();
?>
<nav class="mw-nav" aria-label="<?php esc_attr_e('Hauptnavigation', 'meinweimar-core'); ?>">
<button class="mw-nav__toggle" type="button" aria-expanded="false" aria-controls="<?php echo esc_attr($navigation_id); ?>"><?php esc_html_e('Menü', 'meinweimar-core'); ?></button>
<ul id="<?php echo esc_attr($navigation_id); ?>" class="mw-nav__list">
<?php foreach ($navigation as $item) : ?>
<li class="mw-nav__item"><a href="<?php echo esc_url(self::route_url((string) $item['route'])); ?>"><?php echo esc_html($item['label']); ?></a></li>
<?php endforeach; ?>
</ul>
</nav>
<?php
return (string) ob_get_clean();
}
private static function route_url(string $route): string
{
if (preg_match('#^https?://#i', $route)) {
return $route;
}
return home_url('/' . ltrim($route, '/'));
}
}

Architecture

High-level modules detected from the verified public snapshot. Protected implementation details are intentionally omitted.

flutter-appmodule
wordpress-pluginmodule

Technology stack

DartPHPJavaScriptARBLOCKPBXPROJFlutter / Dart
Dart56.1%
PHP29.4%
JavaScript4.0%
ARB2.9%
LOCK1.7%
PBXPROJ1.7%
CSS1.5%
Markdown0.4%