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-legacy-importer.phpPHP
<?php
declare(strict_types=1);
namespace MeinWeimar;
use mysqli;
use WP_Error;
/**
* Controlled, repeatable importer for the local legacy WordPress snapshot.
*
* The source database is read through mysqli only. The target is written
* exclusively through WordPress APIs, so legacy GeoDirectory/EventON/YITH
* tables never become target dependencies.
*/
final class LegacyImporter
{
private static string $sourcePrefix = '';
private const MAPPING_VERSION = '2';
private const SOURCE_META = '_mw_legacy_source_key';
private const SOURCE_SYSTEM_META = '_mw_legacy_source_system';
private const SOURCE_ID_META = '_mw_legacy_source_id';
private const SOURCE_HASH_META = '_mw_legacy_source_hash';
private const SOURCE_STATUS_META = '_mw_legacy_import_status';
public static function run(array $options = []): array
{
self::loadMediaDependencies();
$options = wp_parse_args($options, [
'source_config' => dirname(ABSPATH) . '/wp-config.php',
'source_root' => dirname(ABSPATH),
'source_uploads' => dirname(ABSPATH) . '/wp-content/uploads',
'publish' => false,
]);
$connection = self::connectSource((string) $options['source_config']);
$source = $connection['db'];
self::$sourcePrefix = (string) $connection['prefix'];
ContentTypes::seed_place_categories();
$stats = [
'mode' => !empty($options['publish']) ? 'publish-test-data' : 'draft-import',
'mapping_version' => self::MAPPING_VERSION,
'posts_created' => 0,
'posts_updated' => 0,
'posts_skipped' => 0,
'posts_failed' => 0,
'terms_created' => 0,
'term_links' => 0,
'media_created' => 0,
'media_reused' => 0,
'media_missing' => 0,
'quarantined_events' => 0,
'sources' => [],
'errors' => [],
];
$resources = [
[
'site' => 1,
'source_type' => 'post',
'target_type' => 'mw_news',
'source_system' => 'wordpress-posts',
'taxonomies' => ['category'],
],
[
'site' => 1,
'source_type' => 'gd_place',
'target_type' => 'mw_place',
'source_system' => 'wordpress-geodirectory',
'detail_table' => 'geodir_gd_place_detail',
'taxonomies' => ['gd_placecategory'],
'category_root' => 'gd_place',
],
[
'site' => 1,
'source_type' => 'gd_kunstkultur',
'target_type' => 'mw_place',
'source_system' => 'wordpress-geodirectory',
'detail_table' => 'geodir_gd_kunstkultur_detail',
'taxonomies' => ['gd_kunstkulturcategory'],
'category_root' => 'gd_kunstkultur',
],
[
'site' => 1,
'source_type' => 'gd_freizeiterlebe',
'target_type' => 'mw_place',
'source_system' => 'wordpress-geodirectory',
'detail_table' => 'geodir_gd_freizeiterlebe_detail',
'taxonomies' => ['gd_freizeiterlebecategory'],
'category_root' => 'gd_freizeiterlebe',
],
[
'site' => 2,
'source_type' => 'ajde_events',
'target_type' => 'mw_event',
'source_system' => 'wordpress-eventon',
'taxonomies' => ['event_type', 'event_category'],
'event' => true,
],
[
'site' => 1,
'source_type' => 'tribe_events',
'target_type' => 'mw_event',
'source_system' => 'wordpress-tribe-events',
'taxonomies' => ['tribe_events_cat', 'event_category'],
'event' => true,
],
];
foreach ($resources as $resource) {
$sourceKey = $resource['source_system'] . ':blog:' . $resource['site'] . ':' . $resource['source_type'];
$stats['sources'][$sourceKey] = 0;
$rows = self::sourcePosts($source, (int) $resource['site'], (string) $resource['source_type']);
foreach ($rows as $row) {
$result = self::importPost($source, $row, $resource, $options, $stats);
if ($result) {
$stats['sources'][$sourceKey]++;
}
}
}
$source->close();
return $stats;
}
/**
* Import one local, non-post media asset and keep its source provenance.
* Used for shared platform assets such as the legacy map-pin fallback.
*/
public static function importLocalImage(string $sourcePath, string $sourceKey, string $title): int
{
self::loadMediaDependencies();
if (!is_file($sourcePath)) {
return 0;
}
$existing = get_posts([
'post_type' => 'attachment',
'post_status' => 'inherit',
'posts_per_page' => 1,
'fields' => 'ids',
'meta_key' => self::SOURCE_META,
'meta_value' => $sourceKey,
]);
if ($existing) {
return (int) $existing[0];
}
$upload = wp_upload_dir();
if (!empty($upload['error']) || !wp_mkdir_p($upload['path'])) {
return 0;
}
$filename = wp_unique_filename($upload['path'], basename($sourcePath));
$targetPath = trailingslashit($upload['path']) . $filename;
if (!copy($sourcePath, $targetPath)) {
return 0;
}
$filetype = wp_check_filetype($filename, null);
$targetId = wp_insert_attachment([
'post_mime_type' => $filetype['type'] ?: 'application/octet-stream',
'post_title' => sanitize_text_field($title ?: pathinfo($filename, PATHINFO_FILENAME)),
'post_status' => 'inherit',
], $targetPath, 0, true);
if (is_wp_error($targetId)) {
@unlink($targetPath);
return 0;
}
$targetId = (int) $targetId;
$metadata = wp_generate_attachment_metadata($targetId, $targetPath);
if (is_array($metadata)) {
wp_update_attachment_metadata($targetId, $metadata);
}
update_post_meta($targetId, self::SOURCE_META, $sourceKey);
update_post_meta($targetId, self::SOURCE_SYSTEM_META, 'wordpress-media');
update_post_meta($targetId, self::SOURCE_HASH_META, hash_file('sha256', $sourcePath) ?: '');
return $targetId;
}
private static function importPost(mysqli $source, array $row, array $resource, array $options, array &$stats): bool
{
$sourceId = absint($row['ID'] ?? 0);
if (!$sourceId) {
$stats['posts_skipped']++;
return false;
}
$site = (int) $resource['site'];
$sourceType = (string) $resource['source_type'];
$targetType = (string) $resource['target_type'];
$sourceKey = sprintf('blog:%d:%s:%d', $site, $sourceType, $sourceId);
$detail = !empty($resource['detail_table'])
? self::sourceDetail($source, (string) $resource['detail_table'], $sourceId)
: [];
$sourceMeta = self::sourceMeta($source, $site, $sourceId);
$title = self::firstValue($detail, ['post_title'], (string) ($row['post_title'] ?? ''));
$title = trim(wp_strip_all_tags($title));
if ('' === $title) {
$stats['posts_skipped']++;
return false;
}
$isEvent = !empty($resource['event']);
$demoEvent = $isEvent && (bool) preg_match('/test[_ -]?event|demo|lorem/i', $title);
$placeholderEvent = $isEvent;
$targetStatus = !empty($options['publish']) && !$placeholderEvent ? 'publish' : 'draft';
$content = (string) ($row['post_content'] ?? '');
$excerpt = (string) ($row['post_excerpt'] ?? '');
$hash = hash('sha256', wp_json_encode([$sourceKey, $title, $content, $detail, $sourceMeta]));
$targetId = self::findTargetPost($sourceKey, $targetType);
$postarr = [
'post_type' => $targetType,
'post_status' => $targetStatus,
'post_title' => $title,
'post_name' => sanitize_title((string) ($row['post_name'] ?? $title)),
'post_content' => wp_kses_post($content),
'post_excerpt' => wp_kses_post($excerpt),
'post_date' => self::validDate((string) ($row['post_date'] ?? '')),
'post_date_gmt' => self::validDate((string) ($row['post_date_gmt'] ?? '')),
];
if ($targetId) {
$postarr['ID'] = $targetId;
$savedId = wp_update_post(wp_slash($postarr), true);
$stats['posts_updated']++;
} else {
$savedId = wp_insert_post(wp_slash($postarr), true);
$stats['posts_created']++;
}
if (is_wp_error($savedId)) {
$stats['posts_failed']++;
$stats['errors'][] = [
'source_key' => $sourceKey,
'message' => $savedId->get_error_message(),
];
return false;
}
$targetId = (int) $savedId;
update_post_meta($targetId, self::SOURCE_META, $sourceKey);
update_post_meta($targetId, self::SOURCE_SYSTEM_META, (string) $resource['source_system']);
update_post_meta($targetId, self::SOURCE_ID_META, $sourceId);
update_post_meta($targetId, self::SOURCE_HASH_META, $hash);
update_post_meta(
$targetId,
self::SOURCE_STATUS_META,
$demoEvent ? 'quarantine-demo' : ($placeholderEvent ? 'quarantine-placeholder' : 'imported')
);
$targetRoute = ContentTypes::route_for_post_type($targetType);
$locale = FieldSchemas::sanitize_value('mw_content_locale', 'de_DE', $targetRoute);
if ('' !== (string) $locale) {
update_post_meta($targetId, 'mw_content_locale', $locale);
}
if (in_array($targetRoute, ['places', 'services'], true)) {
self::writePlaceMeta($targetId, $detail, $targetRoute);
}
if ($isEvent) {
self::writeEventMeta($targetId, $sourceMeta, $detail, $demoEvent, $stats);
}
$mediaIds = self::sourceMediaIds($source, $site, $sourceId, $sourceMeta, $detail);
$targetMediaIds = [];
foreach ($mediaIds as $mediaId) {
$targetMediaId = self::importAttachment($source, $site, $mediaId, $targetId, $options, $stats);
if ($targetMediaId) {
$targetMediaIds[] = $targetMediaId;
}
}
if ($targetMediaIds) {
$gallery = ContentTypes::sanitize_gallery_ids($targetMediaIds);
if ($gallery) {
set_post_thumbnail($targetId, $gallery[0]);
update_post_meta($targetId, 'mw_gallery_ids', $gallery);
}
}
$categoryTaxonomy = ContentTypes::category_taxonomy($targetType);
if ($categoryTaxonomy) {
$termIds = self::importTerms(
$source,
$site,
$sourceId,
(array) ($resource['taxonomies'] ?? []),
$categoryTaxonomy,
$stats,
(string) ($resource['category_root'] ?? '')
);
if ($termIds) {
wp_set_post_terms($targetId, $termIds, $categoryTaxonomy, false);
$stats['term_links'] += count($termIds);
}
}
$tagIds = self::importTerms($source, $site, $sourceId, ['post_tag'], 'mw_tag', $stats);
$legacyTags = self::firstValue($detail, ['post_tags']);
if ('' !== $legacyTags) {
foreach (preg_split('/[,|]+/', $legacyTags) ?: [] as $tagName) {
$tagName = trim(wp_strip_all_tags((string) $tagName));
if ('' === $tagName) {
continue;
}
$existing = term_exists($tagName, 'mw_tag');
if ($existing) {
$tagIds[] = (int) (is_array($existing) ? ($existing['term_id'] ?? 0) : $existing);
continue;
}
$created = wp_insert_term($tagName, 'mw_tag', ['slug' => sanitize_title($tagName)]);
if (!is_wp_error($created)) {
$tagIds[] = (int) ($created['term_id'] ?? 0);
$stats['terms_created']++;
}
}
}
$tagIds = array_values(array_unique(array_filter(array_map('absint', $tagIds))));
if ($tagIds) {
wp_set_post_terms($targetId, $tagIds, 'mw_tag', false);
$stats['term_links'] += count($tagIds);
}
self::importSeoMeta($targetId, $sourceMeta);
if ($isEvent) {
$stats['quarantined_events']++;
}
return true;
}
private static function writePlaceMeta(int $targetId, array $detail, string $route): void
{
$website = self::firstValue($detail, ['website']);
$phone = self::firstValue($detail, ['phone', 'telefon']);
$hours = self::firstValue($detail, ['business_hours']);
$expireDate = self::firstValue($detail, ['expire_date']);
$featured = self::firstValue($detail, ['featured']);
$rights = self::firstValue($detail, ['bildrechte']);
$claimed = self::firstValue($detail, ['claimed']);
$mapZoom = self::firstValue($detail, ['mapzoom']);
$fields = [
'mw_address_line1' => self::firstValue($detail, ['street']),
'mw_address_line2' => self::firstValue($detail, ['street2']),
'mw_postal_code' => self::firstValue($detail, ['zip']),
'mw_locality' => self::firstValue($detail, ['city']),
'mw_region' => self::firstValue($detail, ['region']),
'mw_country' => self::firstValue($detail, ['country'], 'DE'),
'mw_phone' => $phone,
'mw_email' => sanitize_email(self::firstValue($detail, ['email'])),
'mw_website_url' => esc_url_raw($website),
'mw_facebook_url' => esc_url_raw(self::firstValue($detail, ['facebook'])),
'mw_twitter_url' => esc_url_raw(self::firstValue($detail, ['twitter'])),
'mw_video_url' => esc_url_raw(self::firstValue($detail, ['video'])),
'mw_opening_hours' => $hours,
'mw_rating_average' => is_numeric(self::firstValue($detail, ['overall_rating'])) ? max(0, min(5, (float) self::firstValue($detail, ['overall_rating']))) : '',
'mw_rating_count' => is_numeric(self::firstValue($detail, ['rating_count'])) ? max(0, (int) self::firstValue($detail, ['rating_count'])) : '',
'mw_legacy_package_id' => is_numeric(self::firstValue($detail, ['package_id'])) ? absint(self::firstValue($detail, ['package_id'])) : '',
'mw_special_offers' => sanitize_textarea_field(self::firstValue($detail, ['special_offers'])),
'mw_neighbourhood' => self::firstValue($detail, ['neighbourhood']),
'mw_featured' => in_array(strtolower($featured), ['1', 'true', 'yes'], true) ? 1 : 0,
'mw_media_rights_status' => in_array(strtolower($rights), ['1', 'true', 'yes'], true) ? 'legacy-confirmed' : 'unknown',
'mw_claim_status' => in_array(strtolower($claimed), ['1', 'true', 'yes'], true) ? 'claimed' : 'unclaimed',
'mw_map_style' => self::firstValue($detail, ['mapview']),
'mw_map_zoom' => is_numeric($mapZoom) ? max(1, min(20, (int) $mapZoom)) : '',
'mw_expire_date' => preg_match('/^\d{4}-\d{2}-\d{2}$/', $expireDate) && '0000-00-00' !== $expireDate ? $expireDate : '',
'mw_geo_latitude' => self::coordinate(self::firstValue($detail, ['latitude'])),
'mw_geo_longitude' => self::coordinate(self::firstValue($detail, ['longitude'])),
'mw_geo_precision' => 'exact',
'mw_geo_source' => 'legacy',
'mw_geo_privacy' => 'public',
];
self::writeSanitizedMeta($targetId, $route, $fields);
}
/**
* Persist only fields belonging to the import target route. Legacy source
* names are mapped by the caller, but validation and storage remain owned
* by the canonical FieldSchemas registry.
*
* @param array<string, mixed> $fields
*/
private static function writeSanitizedMeta(int $targetId, string $route, array $fields): void
{
foreach ($fields as $key => $value) {
if (!FieldSchemas::has($route, $key)) {
continue;
}
$value = FieldSchemas::sanitize_value($key, $value, $route);
if ('' === (string) $value) {
delete_post_meta($targetId, $key);
continue;
}
update_post_meta($targetId, $key, $value);
}
}
private static function writeEventMeta(int $targetId, array $sourceMeta, array $detail, bool $demoEvent, array &$stats): void
{
$start = self::eventTimestamp($sourceMeta, ['_unix_start_ev', '_EventStartDateUTC', '_EventStartDate']);
$end = self::eventTimestamp($sourceMeta, ['_unix_end_ev', '_EventEndDateUTC', '_EventEndDate']);
if ($start) {
update_post_meta($targetId, 'mw_event_start', FieldSchemas::sanitize_value('mw_event_start', $start, 'events'));
}
if ($end) {
update_post_meta($targetId, 'mw_event_end', FieldSchemas::sanitize_value('mw_event_end', $end, 'events'));
}
$sourceStatus = strtolower((string) ($sourceMeta['_status'] ?? 'scheduled'));
$status = in_array($sourceStatus, ['cancelled', 'postponed', 'expired'], true) ? $sourceStatus : 'scheduled';
update_post_meta($targetId, 'mw_event_status', FieldSchemas::sanitize_value('mw_event_status', $status, 'events'));
if ($demoEvent) {
update_post_meta($targetId, self::SOURCE_STATUS_META, 'quarantine-demo');
}
$lat = self::coordinate(self::firstValue($detail, ['latitude']));
$lng = self::coordinate(self::firstValue($detail, ['longitude']));
if ('' !== $lat && '' !== $lng) {
foreach ([
'mw_geo_latitude' => $lat,
'mw_geo_longitude' => $lng,
'mw_geo_precision' => 'exact',
'mw_geo_source' => 'legacy',
'mw_geo_privacy' => 'public',
] as $key => $value) {
if (FieldSchemas::has('events', $key)) {
update_post_meta($targetId, $key, FieldSchemas::sanitize_value($key, $value, 'events'));
}
}
}
if (!$start) {
$stats['errors'][] = ['target_id' => $targetId, 'message' => 'Event ohne verwertbaren Startzeitpunkt importiert'];
}
}
private static function importTerms(mysqli $source, int $site, int $sourcePostId, array $taxonomies, string $targetTaxonomy, array &$stats, string $categoryRoot = ''): array
{
if (!$taxonomies) {
return [];
}
$termsTable = self::table($source, 'terms', $site);
$termTaxonomyTable = self::table($source, 'term_taxonomy', $site);
$relationshipsTable = self::table($source, 'term_relationships', $site);
$taxonomySql = implode(',', array_map(static fn (string $taxonomy): string => "'" . esc_sql($taxonomy) . "'", $taxonomies));
$sql = "SELECT DISTINCT t.term_id,t.name,t.slug,tt.taxonomy
FROM `{$relationshipsTable}` tr
INNER JOIN `{$termTaxonomyTable}` tt ON tt.term_taxonomy_id=tr.term_taxonomy_id
INNER JOIN `{$termsTable}` t ON t.term_id=tt.term_id
WHERE tr.object_id=" . absint($sourcePostId) . " AND tt.taxonomy IN ({$taxonomySql})
ORDER BY t.name";
$rows = self::queryRows($source, $sql);
$targetIds = [];
$rootId = 0;
if ('mw_place_category' === $targetTaxonomy && '' !== $categoryRoot) {
$roots = ContentTypes::place_category_roots();
$rootSlug = '';
foreach ($roots as $slug => $definition) {
if ($definition['source_post_type'] === $categoryRoot) {
$rootSlug = $slug;
break;
}
}
if ('' !== $rootSlug) {
$root = term_exists($rootSlug, $targetTaxonomy);
$rootId = is_array($root)
? (int) ($root['term_id'] ?? 0)
: (is_int($root) ? $root : 0);
}
}
foreach ($rows as $row) {
$name = trim(wp_strip_all_tags((string) ($row['name'] ?? '')));
if ('' === $name) {
continue;
}
$slug = sanitize_title((string) ($row['slug'] ?? $name));
$sourceTermKey = sprintf('blog:%d:%s:%d', $site, $row['taxonomy'], (int) $row['term_id']);
$existingBySource = get_terms([
'taxonomy' => $targetTaxonomy,
'hide_empty' => false,
'fields' => 'ids',
'number' => 1,
'meta_query' => [[
'key' => self::SOURCE_META,
'value' => $sourceTermKey,
'compare' => '=',
]],
]);
$existing = $existingBySource && !is_wp_error($existingBySource)
? (int) $existingBySource[0]
: term_exists($slug, $targetTaxonomy);
if ($existing) {
$candidateId = (int) (is_array($existing) ? ($existing['term_id'] ?? 0) : $existing);
$candidateSource = (string) get_term_meta($candidateId, self::SOURCE_META, true);
if ('' !== $candidateSource && $candidateSource !== $sourceTermKey) {
$existing = 0;
}
}
if ($existing) {
$termId = (int) (is_array($existing) ? ($existing['term_id'] ?? 0) : $existing);
$term_update = ['name' => $name];
if ('mw_place_category' === $targetTaxonomy) {
$term_update['parent'] = $rootId;
}
$updated = wp_update_term($termId, $targetTaxonomy, $term_update);
if (is_wp_error($updated)) {
$stats['errors'][] = ['message' => $updated->get_error_message(), 'term' => $name];
continue;
}
} else {
$term_insert = ['slug' => $slug];
if ('mw_place_category' === $targetTaxonomy) {
$term_insert['parent'] = $rootId;
}
$created = wp_insert_term($name, $targetTaxonomy, $term_insert);
if (is_wp_error($created) && 'mw_place_category' === $targetTaxonomy && $rootId) {
$created = wp_insert_term($name, $targetTaxonomy, [
'slug' => sanitize_title($categoryRoot . '-' . $slug),
'parent' => $rootId,
]);
}
if (is_wp_error($created)) {
$stats['errors'][] = ['message' => $created->get_error_message(), 'term' => $name];
continue;
}
$termId = (int) ($created['term_id'] ?? 0);
$stats['terms_created']++;
}
if (!$termId) {
continue;
}
update_term_meta($termId, self::SOURCE_META, $sourceTermKey);
$targetIds[] = $termId;
}
return array_values(array_unique(array_filter(array_map('absint', $targetIds))));
}
private static function importSeoMeta(int $targetId, array $sourceMeta): void
{
foreach ($sourceMeta as $key => $value) {
if (!str_starts_with((string) $key, 'rank_math_')) {
continue;
}
update_post_meta($targetId, sanitize_key((string) $key), maybe_unserialize($value));
}
}
private static function importAttachment(mysqli $source, int $site, int $sourceId, int $targetParent, array $options, array &$stats): int
{
$sourceKey = sprintf('blog:%d:attachment:%d', $site, $sourceId);
$existing = get_posts([
'post_type' => 'attachment',
'post_status' => 'inherit',
'posts_per_page' => 1,
'fields' => 'ids',
'meta_key' => self::SOURCE_META,
'meta_value' => $sourceKey,
]);
if ($existing) {
$stats['media_reused']++;
return (int) $existing[0];
}
$attachment = self::sourcePost($source, $site, $sourceId);
if (!$attachment || 'attachment' !== ($attachment['post_type'] ?? '')) {
$stats['media_missing']++;
return 0;
}
$meta = self::sourceMeta($source, $site, $sourceId);
$relative = ltrim((string) ($meta['_wp_attached_file'] ?? ''), '/');
if ('' === $relative) {
$stats['media_missing']++;
return 0;
}
$sourceRoot = rtrim((string) $options['source_uploads'], '/');
$candidates = [$sourceRoot . '/' . $relative];
if ($site > 1) {
array_unshift($candidates, $sourceRoot . '/sites/' . $site . '/' . $relative);
}
$sourcePath = '';
foreach ($candidates as $candidate) {
if (is_file($candidate)) {
$sourcePath = $candidate;
break;
}
}
if ('' === $sourcePath) {
$stats['media_missing']++;
return 0;
}
$upload = wp_upload_dir(self::validDate((string) ($attachment['post_date'] ?? '')));
if (!empty($upload['error']) || !wp_mkdir_p($upload['path'])) {
$stats['media_missing']++;
return 0;
}
$filename = wp_unique_filename($upload['path'], basename($relative));
$targetPath = trailingslashit($upload['path']) . $filename;
if (!copy($sourcePath, $targetPath)) {
$stats['media_missing']++;
return 0;
}
$filetype = wp_check_filetype($filename, null);
$targetId = wp_insert_attachment([
'post_mime_type' => $filetype['type'] ?: 'application/octet-stream',
'post_title' => sanitize_text_field((string) ($attachment['post_title'] ?? pathinfo($filename, PATHINFO_FILENAME))),
'post_excerpt' => sanitize_text_field((string) ($attachment['post_excerpt'] ?? '')),
'post_status' => 'inherit',
'post_parent' => $targetParent,
'post_date' => self::validDate((string) ($attachment['post_date'] ?? '')),
], $targetPath, $targetParent, true);
if (is_wp_error($targetId)) {
@unlink($targetPath);
$stats['media_missing']++;
$stats['errors'][] = ['source_key' => $sourceKey, 'message' => $targetId->get_error_message()];
return 0;
}
$targetId = (int) $targetId;
$metadata = wp_generate_attachment_metadata($targetId, $targetPath);
if (is_array($metadata)) {
wp_update_attachment_metadata($targetId, $metadata);
}
$alt = (string) ($meta['_wp_attachment_image_alt'] ?? '');
if ('' !== $alt) {
update_post_meta($targetId, '_wp_attachment_image_alt', sanitize_text_field($alt));
}
update_post_meta($targetId, self::SOURCE_META, $sourceKey);
update_post_meta($targetId, self::SOURCE_SYSTEM_META, 'wordpress-media');
update_post_meta($targetId, self::SOURCE_ID_META, $sourceId);
update_post_meta($targetId, self::SOURCE_HASH_META, hash_file('sha256', $sourcePath) ?: '');
$stats['media_created']++;
return $targetId;
}
private static function sourceMediaIds(mysqli $source, int $site, int $sourcePostId, array $sourceMeta, array $detail): array
{
$ids = [];
foreach (['_thumbnail_id', '_featured_image_id'] as $key) {
if (!empty($sourceMeta[$key])) {
$ids[] = absint($sourceMeta[$key]);
}
}
$featured = self::firstValue($detail, ['featured_image']);
if (ctype_digit($featured)) {
$ids[] = absint($featured);
} elseif ('' !== $featured) {
$postsTable = self::table($source, 'posts', $site);
$metaTable = self::table($source, 'postmeta', $site);
$filename = basename((string) (parse_url($featured, PHP_URL_PATH) ?: $featured));
$filename = str_replace(['%', '_'], ['\\%', '\\_'], $filename);
$filename = $source->real_escape_string($filename);
if ('' !== $filename) {
$rows = self::queryRows(
$source,
"SELECT DISTINCT p.ID FROM `{$postsTable}` p
LEFT JOIN `{$metaTable}` pm ON pm.post_id=p.ID AND pm.meta_key='_wp_attached_file'
WHERE p.post_type='attachment'
AND (p.guid LIKE '%{$filename}' OR pm.meta_value LIKE '%{$filename}')
ORDER BY p.ID"
);
foreach ($rows as $row) {
$ids[] = absint($row['ID'] ?? 0);
}
}
}
$postsTable = self::table($source, 'posts', $site);
$children = self::queryRows($source, "SELECT ID FROM `{$postsTable}` WHERE post_type='attachment' AND post_parent=" . absint($sourcePostId) . " ORDER BY ID");
foreach ($children as $child) {
$ids[] = absint($child['ID'] ?? 0);
}
return array_values(array_unique(array_filter($ids)));
}
private static function sourcePosts(mysqli $source, int $site, string $postType): array
{
$table = self::table($source, 'posts', $site);
$type = $source->real_escape_string($postType);
return self::queryRows($source, "SELECT * FROM `{$table}` WHERE post_type='{$type}' AND post_status='publish' ORDER BY ID");
}
private static function sourcePost(mysqli $source, int $site, int $postId): array
{
$table = self::table($source, 'posts', $site);
$rows = self::queryRows($source, "SELECT * FROM `{$table}` WHERE ID=" . absint($postId) . " LIMIT 1");
return $rows[0] ?? [];
}
private static function sourceDetail(mysqli $source, string $tableSuffix, int $postId): array
{
$table = self::table($source, $tableSuffix, 1);
$rows = self::queryRows($source, "SELECT * FROM `{$table}` WHERE post_id=" . absint($postId) . " LIMIT 1");
return $rows[0] ?? [];
}
private static function sourceMeta(mysqli $source, int $site, int $postId): array
{
$table = self::table($source, 'postmeta', $site);
$rows = self::queryRows($source, "SELECT meta_key,meta_value FROM `{$table}` WHERE post_id=" . absint($postId));
$meta = [];
foreach ($rows as $row) {
$key = (string) ($row['meta_key'] ?? '');
if ('' !== $key && !array_key_exists($key, $meta)) {
$meta[$key] = (string) ($row['meta_value'] ?? '');
}
}
return $meta;
}
private static function connectSource(string $configPath): array
{
if (!is_file($configPath)) {
throw new \RuntimeException('Source wp-config.php not found: ' . $configPath);
}
$config = (string) file_get_contents($configPath);
$value = static function (string $name) use ($config): string {
$pattern = '/define\s*\(\s*["\x27]' . preg_quote($name, '/') . '["\x27]\s*,\s*(["\x27])(.*?)\1\s*\)/s';
if (preg_match($pattern, $config, $matches) !== 1) {
throw new \RuntimeException('Missing source config value: ' . $name);
}
return stripcslashes($matches[2]);
};
if (preg_match('/\$table_prefix\s*=\s*(["\x27])(.*?)\1\s*;/', $config, $prefixMatches) !== 1) {
throw new \RuntimeException('Missing source table prefix');
}
$hostValue = $value('DB_HOST');
$host = $hostValue;
$socket = null;
$port = 0;
if (str_contains($hostValue, ':')) {
[$host, $tail] = explode(':', $hostValue, 2);
if (str_starts_with($tail, '/')) {
$socket = $tail;
} else {
$port = absint($tail);
}
}
mysqli_report(MYSQLI_REPORT_OFF);
$db = @new mysqli($host, $value('DB_USER'), $value('DB_PASSWORD'), $value('DB_NAME'), $port, $socket);
if ($db->connect_errno) {
throw new \RuntimeException('Source database connection failed: ' . $db->connect_error);
}
$db->set_charset('utf8mb4');
return ['db' => $db, 'prefix' => $prefixMatches[2], 'database' => $value('DB_NAME')];
}
private static function table(mysqli $source, string $name, int $site): string
{
return self::$sourcePrefix . ($site > 1 ? $site . '_' : '') . $name;
}
private static function queryRows(mysqli $source, string $sql): array
{
$result = $source->query($sql);
if (!$result) {
throw new \RuntimeException('Legacy read failed: ' . $source->error);
}
$rows = [];
while ($row = $result->fetch_assoc()) {
$rows[] = $row;
}
$result->free();
return $rows;
}
private static function findTargetPost(string $sourceKey, string $postType): int
{
$ids = get_posts([
'post_type' => $postType,
'post_status' => 'any',
'posts_per_page' => 1,
'fields' => 'ids',
'meta_key' => self::SOURCE_META,
'meta_value' => $sourceKey,
]);
return $ids ? (int) $ids[0] : 0;
}
private static function firstValue(array $row, array $keys, string $default = ''): string
{
foreach ($keys as $key) {
$value = trim((string) ($row[$key] ?? ''));
if ('' !== $value) {
return $value;
}
}
return $default;
}
private static function coordinate(string $value): string
{
if (!is_numeric($value)) {
return '';
}
$number = (float) $value;
return is_finite($number) ? (string) $number : '';
}
private static function eventTimestamp(array $meta, array $keys): string
{
foreach ($keys as $key) {
$value = trim((string) ($meta[$key] ?? ''));
if ('' === $value) {
continue;
}
$timestamp = ctype_digit($value) ? (int) $value : strtotime($value);
if ($timestamp) {
return wp_date(DATE_ATOM, $timestamp, wp_timezone());
}
}
return '';
}
private static function validDate(string $value): string
{
return preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $value) ? $value : current_time('mysql');
}
private static function loadMediaDependencies(): void
{
require_once ABSPATH . 'wp-admin/includes/image.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/media.php';
}
}

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%