'draft', 'pending' => 'pending', 'publish' => 'published', 'private' => 'hidden', 'locked' => 'locked', ]; private const array ACF_FIELDS = [ 'entry_title', 'version_number', 'release_date', 'release_site', 'youtube_video', 'staff', 'hashes', ]; private const array MULTI_TAXONOMIES = [ 'language' => 'languages', 'modifications' => 'modifications', 'author-name' => 'authors', 'document-category' => 'categories', 'utility-category' => 'categories', 'utility-os' => 'systems', ]; private array $stats = []; private function getSingle( ?int $term_id, string $targetTable ): ?int { if (!$term_id) { return null; } return DB::table('migrations_logs') ->where('source_system', 'wp') ->where('target_table', $targetTable ) ->where('source_id', $term_id) ->value('target_id'); } private function uniqueSlug(string $baseSlug, string $table, ?int $ignoreId = null): string { $slug = $baseSlug; $i = 1; while ( DB::table($table)->where('slug', $slug) ->when($ignoreId, fn ($q) => $q->where('id', '!=', $ignoreId)) ->exists() && $i < 100 ) { $slug = $baseSlug . '-' . $i++; } if ($i >= 100) { $slug = (string) \Str::uuid(); } return $slug; } private function parseStaffCredits(?string $raw): array { if (!$raw || trim($raw) === '') { return []; } $credits = []; foreach (preg_split('/\r\n|\r|\n/', $raw) as $line) { $line = trim($line); if ($line === '') continue; if (preg_match('/^(.+?):\s*(.*)$/', $line, $m)) { $credits[] = ['name' => trim($m[1]), 'description' => trim($m[2])]; } elseif (!empty($credits)) { $last = array_key_last($credits); $credits[$last]['description'] = trim($credits[$last]['description'] . ' ' . $line); } else { $credits[] = ['name' => $line, 'description' => '']; } } return $credits; } private function parseHashes(?string $raw): array { if (!$raw || trim($raw) === '') return []; $results = []; foreach (preg_split('/\n\s*\n/', trim($raw)) as $block) { $fields = []; foreach (preg_split('/\r\n|\r|\n/', trim($block)) as $line) { $line = trim($line); if ($line === '' || !str_contains($line, ':')) continue; [$key, $value] = array_map('trim', explode(':', $line, 2)); $fields[strtolower(preg_replace('/[^a-z0-9]/i', '', $key))] = $value; } if (empty($fields)) continue; $results[] = [ 'filename' => $fields['filename'] ?? '', 'hash_crc32' => $fields['crc32'] ?? '', 'hash_sha1' => $fields['sha1'] ?? '', ]; } return $results; } private function attachMany(int $entryId, string $pivotTable, string $foreignKey, array $ttids, string $targetTable): void { if (empty($ttids)) return; $ids = DB::table('migrations_logs') ->where('source_system', 'wp')->where('target_table', $targetTable) ->whereIn('source_id', $ttids)->pluck('target_id'); foreach ($ids as $id) { DB::table($pivotTable)->insertOrIgnore(['entry_id' => $entryId, $foreignKey => $id]); } } /** * @param \StdClass $post * @param string $cpt * * @return void */ private function migratePost( $post, string $cpt ): void { $exists = DB::table('migrations_logs') ->where('source_system', 'wp') ->where('source_table', 'wp_posts') ->where('source_id', $post->ID) ->exists(); if ($exists) return; $meta = DB::connection('old_wp') ->table('postmeta') ->where('post_id', $post->ID) ->whereIn('meta_key', self::ACF_FIELDS) ->pluck('meta_value', 'meta_key'); $terms = DB::connection('old_wp') ->table('term_relationships as tr') ->join('term_taxonomy as tt', 'tr.term_taxonomy_id', '=', 'tt.term_taxonomy_id') ->where('tr.object_id', $post->ID) ->whereIn('tt.taxonomy', array_merge(['game', 'platform', 'hack-status', 'experience-level'], array_keys(self::MULTI_TAXONOMIES)) ) ->select('tt.taxonomy', 'tt.term_taxonomy_id') ->get(); $byTax = []; foreach ($terms as $term) { $byTax[$term->taxonomy][] = $term->term_taxonomy_id; } $gameId = null; if( !empty( $byTax['game'][0] ) && !empty( $byTax['platform'][0] ) ) { $gameId = DB::table('migration_game_plan') ->where('wp_game_id', $byTax['game'][0]) ->where('wp_platform_id', $byTax['platform'][0]) ->value('game_id'); if( !$gameId ) $this->stats['missing_game_plan']++; } $statusId = $this->getSingle( $byTax['hack-status'][0] ?? null, 'statuses' ); $levelId = $this->getSingle( $byTax['experience-level'][0] ?? null, 'levels' ); $userId = DB::table('migration_user_plan') ->where('wp_user_id', $post->post_author ) ->value('user_id'); if( !$userId ) { $this->stats['missing_author']++; return; } $title = $meta['entry_title'] ?? null; if( $cpt === 'translations' && !$title && $gameId ) { $title = DB::table('games')->where('id', $gameId)->value('name'); } $slug = $this->uniqueSlug( rawurldecode($post->post_name), 'entries' ); $entryId = DB::table('entries')->insertGetId([ 'type' => $cpt, 'title' => $title, 'complete_title' => $post->post_title ?? null, 'slug' => $slug, 'description' => MigrationHelpers::htmlToMarkdown($post->post_content), 'state' => self::STATE_MAP[$post->post_status], 'game_id' => $gameId, 'platform_id' => null, 'status_id' => $statusId, 'level_id' => $levelId, 'version' => $meta['version_number'] ?? null, 'release_date' => $meta['release_date'] ?? null, 'staff_credits' => json_encode( $this->parseStaffCredits($meta['staff'] ?? null)), 'relevant_link' => $meta['release_site'] ?? null, 'youtube_link' => $meta['youtube_video'] ?? null, 'user_id' => $userId, 'created_at' => $post->post_date, 'updated_at' => $post->post_modified, ]); $this->attachMany($entryId, 'entry_authors', 'author_id', $byTax['author-name'] ?? [], 'authors'); $this->attachMany($entryId, 'entry_languages', 'language_id', $byTax['language'] ?? [], 'languages'); if( $cpt === 'romhacks') $this->attachMany($entryId, 'entry_modifications', 'modification_id', $byTax['modifications'] ?? [], 'modifications'); if ($cpt === 'utilities') { $this->attachMany($entryId, 'entry_categories', 'category_id', $byTax['utility-category'] ?? [], 'categories'); $this->attachMany($entryId, 'entry_systems', 'system_id', $byTax['utility-os'] ?? [], 'systems'); } if ($cpt === 'documents') { $this->attachMany($entryId, 'entry_categories', 'category_id', $byTax['document-category'] ?? [], 'categories'); } if( $cpt === 'translations' || $cpt === 'romhacks' ){ foreach ( $this->parseHashes( $meta['hashes'] ?? null ) as $hash ) { DB::table('entry_hashes')->insert([ 'entry_id' => $entryId, 'filename' => $hash['filename'], 'hash_crc32' => $hash['hash_crc32'], 'hash_sha1' => $hash['hash_sha1'], 'verified' => 'TBD', 'created_at' => now(), 'updated_at' => now(), ]); } } DB::table('migrations_logs')->insert([ 'source_system' => 'wp', 'source_table' => 'wp_posts', 'source_id' => $post->ID, 'target_table' => 'entries', 'target_id' => $entryId, 'status' => 'done', 'migrated_at' => now(), 'updated_at' => now(), 'created_at' => now(), ]); $this->stats['created']++; } private function migrateCpt( string $cpt ): void { $this->info( "Current migration of : $cpt "); $this->stats = [ 'missing_author' => 0, 'missing_game_plan' => 0, 'created' => 0 ]; $query = DB::connection('old_wp') ->table('posts') ->where('post_type', $cpt) ->whereIn('post_status', array_keys(self::STATE_MAP)) ; if( $limit = $this->option('limit') ) { $query->limit((int) $limit); } $posts = $query->select('ID', 'post_title', 'post_name', 'post_content', 'post_status', 'post_author', 'post_date', 'post_modified') ->get(); $this->withProgressBar($posts, fn( $post ) => $this->migratePost( $post, $cpt ) ); $this->newLine(); $this->info( "Created {$this->stats['created']} entries'. No authors {$this->stats['missing_author']}. Missing game {$this->stats['missing_game_plan']} entries" ); } public function handle() { foreach ( self::WP_CPTS as $cpt ) { $this->migrateCpt( $cpt ); } } }