Migration complete
This commit is contained in:
40
app/Console/Commands/FixEncodedSlugs.php
Normal file
40
app/Console/Commands/FixEncodedSlugs.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('fix:encoded-slugs')]
|
||||
class FixEncodedSlugs extends Command
|
||||
{
|
||||
private const array TABLES = [
|
||||
'entries', 'games', 'platforms', 'genres', 'languages', 'modifications',
|
||||
'levels', 'statuses', 'authors', 'categories', 'systems',
|
||||
];
|
||||
|
||||
public function handle()
|
||||
{
|
||||
foreach (self::TABLES as $table) {
|
||||
$rows = DB::table($table)->where('slug', 'like', '%\\%%')->get(['id', 'slug']);
|
||||
$fixed = 0;
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$decoded = rawurldecode($row->slug);
|
||||
if ($decoded === $row->slug) continue;
|
||||
|
||||
try {
|
||||
DB::table($table)->where('id', $row->id)->update(['slug' => $decoded]);
|
||||
$fixed++;
|
||||
} catch (\Throwable $e) {
|
||||
$this->warn("{$table}#{$row->id} : collision '{$decoded}', ignored ({$e->getMessage()}).");
|
||||
}
|
||||
}
|
||||
|
||||
if ($fixed > 0) {
|
||||
$this->info("{$table}: {$fixed} fixed slugs.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
app/Console/Commands/FixEntriesDescription.php
Normal file
47
app/Console/Commands/FixEntriesDescription.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Helpers\MigrationHelpers;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use League\HTMLToMarkdown\HtmlConverter;
|
||||
|
||||
#[Signature('fix:entries-description')]
|
||||
#[Description('Reconvert HTML descriptions to Markdown')]
|
||||
class FixEntriesDescription extends Command
|
||||
{
|
||||
public function handle()
|
||||
{
|
||||
$converter = new HtmlConverter(['hard_break' => true]);
|
||||
|
||||
$rows = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('source_table', 'wp_posts')
|
||||
->where('target_table', 'entries')
|
||||
->get(['source_id', 'target_id']);
|
||||
|
||||
$this->info("{$rows->count()} entries touched.");
|
||||
|
||||
$this->withProgressBar($rows, function ($row) use ($converter) {
|
||||
$rawHtml = DB::connection('old_wp')->table('posts')
|
||||
->where('ID', $row->source_id)
|
||||
->value('post_content');
|
||||
|
||||
if ($rawHtml === null) return;
|
||||
|
||||
$markdown = trim($rawHtml) === '' ? $rawHtml : $converter->convert(MigrationHelpers::wpAutoP($rawHtml));
|
||||
|
||||
DB::table('entries')->where('id', $row->target_id)->update([
|
||||
'description' => $markdown,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
$this->info('Process finished.');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
44
app/Console/Commands/MigrateCategoriesConfigure.php
Normal file
44
app/Console/Commands/MigrateCategoriesConfigure.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:categories:configure')]
|
||||
#[Description('Configure categories table.')]
|
||||
class MigrateCategoriesConfigure extends Command
|
||||
{
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$taxonomyMap = [];
|
||||
$taxonomy = "a";
|
||||
$section = "";
|
||||
|
||||
while( $taxonomy !== "" ){
|
||||
$taxonomy = "";
|
||||
$section = "";
|
||||
|
||||
$taxonomy = $this->ask("Write WP taxonomy name. Write nothing if you want to save changes.", "" );
|
||||
if( $taxonomy == "" )
|
||||
break;
|
||||
|
||||
$section = $this->ask("Write entry section equivalent.");
|
||||
|
||||
$taxonomyMap[$taxonomy] = $section;
|
||||
}
|
||||
|
||||
DB::table('migration_settings')
|
||||
->updateOrInsert([
|
||||
'key' => 'wp_categories_to_entry_sections'
|
||||
],[
|
||||
'value' => json_encode($taxonomyMap), 'updated_at' => now()
|
||||
]);
|
||||
|
||||
$this->info('WP Categories to entry sections has been configured.');
|
||||
}
|
||||
}
|
||||
92
app/Console/Commands/MigrateCategoriesExecute.php
Normal file
92
app/Console/Commands/MigrateCategoriesExecute.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:categories:execute')]
|
||||
#[Description('Migrate categories, make sure you have configured the migration before that.')]
|
||||
class MigrateCategoriesExecute extends Command
|
||||
{
|
||||
|
||||
private const string TABLE = 'categories';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$taxMap = json_decode(DB::table('migration_settings')->where('key', 'wp_categories_to_entry_sections')->value('value'), true);
|
||||
|
||||
if( !$taxMap ){
|
||||
$this->error("No WP taxonomies need to be transferred.");
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if( $this->ask("Are you sure you want launch that migration? Write ok if you want to launch it.", "") !== 'ok' ){
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$table = self::TABLE;
|
||||
|
||||
foreach ( $taxMap as $wpTax => $restrictedTo )
|
||||
{
|
||||
$this->info("Migrate: {$wpTax} restricted to {$table}");
|
||||
|
||||
$terms = DB::connection('old_wp')
|
||||
->table('term_taxonomy')
|
||||
->join('terms', 'term_taxonomy.term_id', '=', 'terms.term_id')
|
||||
->where('term_taxonomy.taxonomy', $wpTax)
|
||||
->select('term_taxonomy.term_taxonomy_id', 'terms.name', 'terms.slug')
|
||||
->get();
|
||||
|
||||
$this->withProgressBar($terms, function ($term) use ($table, $restrictedTo) {
|
||||
|
||||
$exists = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('source_table', 'wp_term_taxonomy')
|
||||
->where('source_id', $term->term_taxonomy_id )
|
||||
->exists();
|
||||
|
||||
if( $exists )
|
||||
return;
|
||||
|
||||
$existing = DB::table( $table )->where('slug', $term->slug)->first();
|
||||
|
||||
if( $existing === null) {
|
||||
$Id = DB::table($table)
|
||||
->insertGetId([
|
||||
'name' => $term->name, 'slug' => $term->slug,
|
||||
'restricted_to' => json_encode([$restrictedTo]),
|
||||
'created_at' => now(), 'updated_at' => now()
|
||||
]);
|
||||
} else {
|
||||
$Id = $existing->id;
|
||||
$restrictedToField = json_decode($existing->restricted_to, true) ?? [];
|
||||
if( !in_array( $restrictedTo, $restrictedToField, true ) ){
|
||||
$restrictedToField[] = $restrictedTo;
|
||||
DB::table($table)
|
||||
->where('id', $Id)
|
||||
->update(['restricted_to' => json_encode($restrictedToField), 'updated_at' => now()]);
|
||||
}
|
||||
}
|
||||
|
||||
DB::table('migrations_logs')->insert([
|
||||
'source_system' => 'wp',
|
||||
'source_table' => 'wp_term_taxonomy',
|
||||
'source_id' => $term->term_taxonomy_id,
|
||||
'target_table' => $table,
|
||||
'target_id' => $Id,
|
||||
'status' => 'done',
|
||||
'migrated_at' => now(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now()
|
||||
]);
|
||||
});
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
$this->info("Migration done");
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
80
app/Console/Commands/MigrateEntriesComments.php
Normal file
80
app/Console/Commands/MigrateEntriesComments.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Connection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:entries:comments {--import-log-table=} {--limit=}')]
|
||||
class MigrateEntriesComments extends Command
|
||||
{
|
||||
public function handle()
|
||||
{
|
||||
$logTable = $this->option('import-log-table');
|
||||
if( !$logTable ){
|
||||
$this->error( 'XenForo import log table required' );
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$this->commentsThread( 'entries', 'wp_posts', $logTable );
|
||||
$this->commentsThread( 'news', 'wp_posts__news', $logTable );
|
||||
|
||||
$this->info( "Done" );
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function commentsThread( string $table, string $sourceTable, string $logTable ): void
|
||||
{
|
||||
$query = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp' )
|
||||
->where('source_table', $sourceTable )
|
||||
->where('target_table', $table );
|
||||
|
||||
if( $limit = $this->option('limit') ){
|
||||
$query->limit((int)$limit);
|
||||
}
|
||||
|
||||
$rows = $query->get(['source_id', 'target_id']);
|
||||
$this->info( "{$rows->count()} need migration logs" );
|
||||
|
||||
$stats = [ 'updated' => 0, 'no_meta' => 0, 'no_new_thread' => 0 ];
|
||||
|
||||
$this->withProgressBar( $rows, function ( $row ) use( $table, $logTable, &$stats ) {
|
||||
|
||||
$oldThreadId = DB::connection('old_wp')
|
||||
->table('postmeta')
|
||||
->where('post_id', $row->source_id )
|
||||
->where('meta_key', 'xf_thread_id')
|
||||
->value('meta_value');
|
||||
|
||||
if( !$oldThreadId ){
|
||||
$stats['no_meta']++;
|
||||
return;
|
||||
}
|
||||
|
||||
$newThreadId = DB::connection('xenforo')
|
||||
->withoutTablePrefix( function( Connection $db ) use( $logTable, $oldThreadId ){
|
||||
return $db->table( $logTable )
|
||||
->where('content_type', 'thread')
|
||||
->where('old_id', (string) $oldThreadId )
|
||||
->value('new_id');
|
||||
});
|
||||
|
||||
if( !$newThreadId ){
|
||||
$stats['no_new_thread']++;
|
||||
return;
|
||||
}
|
||||
|
||||
DB::table( $table )->where('id', $row->target_id )->update([
|
||||
'comments_thread_id' => (int) $newThreadId
|
||||
]);
|
||||
|
||||
$stats['updated']++;
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
$this->info( "Updated: {$stats['updated']}, No new thread: {$stats['no_new_thread']}, No meta: {$stats['no_meta']}" );
|
||||
}
|
||||
}
|
||||
295
app/Console/Commands/MigrateEntriesExecute.php
Normal file
295
app/Console/Commands/MigrateEntriesExecute.php
Normal file
@@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Helpers\MigrationHelpers;
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:entries:execute {--limit=}')]
|
||||
#[Description('Migrate WP entries')]
|
||||
class MigrateEntriesExecute extends Command
|
||||
{
|
||||
private const array WP_CPTS = ['translations', 'romhacks', 'homebrew', 'utilities', 'documents' ];
|
||||
|
||||
private const array STATE_MAP = [
|
||||
'draft' => '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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
146
app/Console/Commands/MigrateEntriesImages.php
Normal file
146
app/Console/Commands/MigrateEntriesImages.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
#[Signature('migrate:entries:images {--wp-uploads-path=} {--limit=}')]
|
||||
#[Description("Migrate WP Main images and galeries for entries.")]
|
||||
class MigrateEntriesImages extends Command
|
||||
{
|
||||
private const string GALLERYABLE_TYPE = 'App\\Models\\Entry';
|
||||
|
||||
private function copyAttachment( int $attachmentId, string $wpUploadsPath, string $destinationPath ): ?string
|
||||
{
|
||||
$relativePath = DB::connection('old_wp')
|
||||
->table('postmeta')
|
||||
->where('post_id', $attachmentId)
|
||||
->where('meta_key', '_wp_attached_file')
|
||||
->value('meta_value');
|
||||
|
||||
if( !$relativePath )
|
||||
return null;
|
||||
|
||||
$sourcePath = rtrim($wpUploadsPath, '/') . '/' . $relativePath;
|
||||
if( !is_file($sourcePath) )
|
||||
return null;
|
||||
|
||||
$extension = pathinfo($sourcePath, PATHINFO_EXTENSION);
|
||||
$filename = Str::random(40) . ($extension ? '.' . $extension : '');
|
||||
$destinationRelative = $destinationPath . '/' . $filename;
|
||||
|
||||
$stream = fopen($sourcePath, 'r');
|
||||
Storage::disk('public')->put($destinationRelative, $stream);
|
||||
if( is_resource($stream) )
|
||||
fclose($stream);
|
||||
|
||||
return $destinationRelative;
|
||||
}
|
||||
|
||||
private function processEntry( int $wpPostId, int $entryId, string $wpUploadsPath, array &$stats ): void
|
||||
{
|
||||
$exists = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('source_table', 'wp_posts__attachments')
|
||||
->where('source_id', $wpPostId)
|
||||
->exists();
|
||||
|
||||
if( $exists )
|
||||
return;
|
||||
|
||||
$thumbnailId = DB::connection('old_wp')
|
||||
->table('postmeta')
|
||||
->where('post_id', $wpPostId)
|
||||
->where('meta_key', '_thumbnail_id')
|
||||
->value('meta_value');
|
||||
|
||||
if( $thumbnailId ){
|
||||
$newPath = $this->copyAttachment( (int) $thumbnailId, $wpUploadsPath, 'entries/main-images' );
|
||||
if( $newPath ){
|
||||
DB::table('entries')
|
||||
->where('id', $entryId)
|
||||
->update(['main_image' => $newPath]);
|
||||
$stats['main_image']++;
|
||||
} else {
|
||||
$stats['missing_files']++;
|
||||
}
|
||||
}
|
||||
|
||||
$galleryRaw = DB::connection('old_wp')
|
||||
->table('postmeta')
|
||||
->where('post_id', $wpPostId)
|
||||
->where('meta_key', 'my_gallery')
|
||||
->value('meta_value');
|
||||
|
||||
$attachmentIds = $galleryRaw ? (@unserialize($galleryRaw) ?: []) : [];
|
||||
|
||||
foreach ( array_values( $attachmentIds ) as $order => $attachmentId ) {
|
||||
$newPath = $this->copyAttachment( (int) $attachmentId, $wpUploadsPath, "entries/gallery-images/{$entryId}" );
|
||||
if( !$newPath ){
|
||||
$stats['missing_files']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
DB::table('galleries')
|
||||
->insert([
|
||||
'galleryable_type' => self::GALLERYABLE_TYPE,
|
||||
'galleryable_id' => $entryId,
|
||||
'image' => $newPath,
|
||||
'order' => $order,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
$stats['gallery_images']++;
|
||||
}
|
||||
|
||||
DB::table('migrations_logs')
|
||||
->insert([
|
||||
'source_system' => 'wp',
|
||||
'source_table' => 'wp_posts__attachments',
|
||||
'source_id' => $wpPostId,
|
||||
'target_table' => 'entries',
|
||||
'target_id' => $entryId,
|
||||
'status' => 'done',
|
||||
'migrated_at' => now(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$wpUploadsPath = $this->option('wp-uploads-path');
|
||||
if( !$wpUploadsPath || !is_dir($wpUploadsPath) ){
|
||||
$this->error('Missing WP Uploads Path');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$query = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('source_table', 'wp_posts')
|
||||
->where('target_table', 'entries')
|
||||
;
|
||||
|
||||
if( $limit = $this->option('limit') ){
|
||||
$query->limit((int)$limit);
|
||||
}
|
||||
|
||||
$rows = $query->get(['source_id', 'target_id']);
|
||||
$this->info("{$rows->count()} entries need to be migrated");
|
||||
|
||||
$stats = ['main_image' => 0, 'gallery_images' => 0, 'missing_files' => 0 ];
|
||||
|
||||
$this->withProgressBar($rows, function($row) use($wpUploadsPath, &$stats) {
|
||||
$this->processEntry( $row->source_id, $row->target_id, $wpUploadsPath, $stats );
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
$this->info("Migrated attachments. Main images: {$stats['main_image']}, Galleries: {$stats['gallery_images']}, Missing files: {$stats['missing_files']}");
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
169
app/Console/Commands/MigrateGamesExecute.php
Normal file
169
app/Console/Commands/MigrateGamesExecute.php
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Platform;
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:games:execute')]
|
||||
#[Description("Migrate all games from WP posts.")]
|
||||
class MigrateGamesExecute extends Command
|
||||
{
|
||||
private const array WP_CPTS = [ 'translations', 'romhacks', 'homebrew', 'utilities', 'documents', 'lua-scripts' ];
|
||||
private const array RELATED_TAXS = ['game', 'platform', 'genre'];
|
||||
private const int DEFAULT_GENRE_ID = 1;
|
||||
private const int NO_GENRE_SENTINEL = 0;
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$rows = DB::connection('old_wp')
|
||||
->table('term_relationships as tr')
|
||||
->join('term_taxonomy as tt', 'tr.term_taxonomy_id', '=', 'tt.term_taxonomy_id')
|
||||
->join('posts as p', 'tr.object_id', '=', 'p.ID')
|
||||
->whereIn('tt.taxonomy', self::RELATED_TAXS )
|
||||
->whereIn('p.post_type', self::WP_CPTS )
|
||||
->where('p.post_status', '!=', 'trash')
|
||||
->select('p.ID as post_id', 'tt.taxonomy', 'tt.term_taxonomy_id' )
|
||||
->get();
|
||||
|
||||
$byPost = [];
|
||||
$ignored = 0;
|
||||
foreach( $rows as $row ){
|
||||
if( isset( $byPost[ $row->post_id ][$row->taxonomy] ) ){
|
||||
$ignored++;
|
||||
continue;
|
||||
}
|
||||
$byPost[ $row->post_id ][$row->taxonomy] = $row->term_taxonomy_id;
|
||||
}
|
||||
if( $ignored ){
|
||||
$this->warn("$ignored posts with multiple taxs will be ignored.");
|
||||
}
|
||||
|
||||
$structure = [];
|
||||
$ignoredNoPlatform = 0;
|
||||
|
||||
foreach ( $byPost as $data )
|
||||
{
|
||||
if( empty( $data['game'] ) )
|
||||
continue;
|
||||
|
||||
if( empty( $data['platform'] ) ){
|
||||
$ignoredNoPlatform++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$gameId = $data['game'];
|
||||
$platformId = $data['platform'];
|
||||
$genreId = $data['genre'] ?? self::NO_GENRE_SENTINEL;
|
||||
|
||||
$structure[$gameId][$platformId]['count'] = ( $structure[$gameId][$platformId]['count'] ?? 0 ) + 1;
|
||||
$structure[$gameId][$platformId]['genres'][$genreId] = ( $structure[$gameId][$platformId]['genres'][$genreId] ?? 0 ) + 1;
|
||||
}
|
||||
if( $ignoredNoPlatform ){
|
||||
$this->warn("$ignoredNoPlatform posts with no platforms will be ignored.");
|
||||
}
|
||||
|
||||
$games = DB::connection('old_wp')
|
||||
->table('term_taxonomy')
|
||||
->join('terms', 'term_taxonomy.term_id', '=', 'terms.term_id')
|
||||
->where('term_taxonomy.taxonomy', 'game' )
|
||||
->select('term_taxonomy.term_taxonomy_id', 'terms.name', 'terms.slug' )
|
||||
->get()
|
||||
->keyBy('term_taxonomy_id');
|
||||
|
||||
$platformMap = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp' )
|
||||
->where('target_table', 'platforms')
|
||||
->pluck('target_id', 'source_id');
|
||||
|
||||
$genreMap = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp' )
|
||||
->where('target_table', 'genres')
|
||||
->pluck('target_id', 'source_id');
|
||||
|
||||
if( $this->ask("Are you sure you want launch that migration? Write ok if you want to launch it.", "") !== 'ok' ){
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$created = 0;
|
||||
$genreConflicts = 0;
|
||||
|
||||
foreach( $structure as $gameId => $platforms )
|
||||
{
|
||||
$game = $games->get( $gameId );
|
||||
if( !$game )
|
||||
continue;
|
||||
|
||||
foreach( $platforms as $platformId => $info )
|
||||
{
|
||||
$alreadyExists = DB::table('migration_game_plan')
|
||||
->where('wp_game_id', $gameId )
|
||||
->where('wp_platform_id', $platformId )
|
||||
->exists();
|
||||
if( $alreadyExists )
|
||||
continue;
|
||||
|
||||
$newPlatformId = $platformMap[$platformId] ?? null;
|
||||
if( !$newPlatformId ){
|
||||
$this->warn("$gameId ignored because platform $platformId does not exist in Laravel.");
|
||||
continue;
|
||||
}
|
||||
|
||||
$genres = $info['genres'];
|
||||
$newGenreId = null;
|
||||
$topId = null;
|
||||
$genreConflict = false;
|
||||
|
||||
arsort( $genres );
|
||||
$topId = array_key_first( $genres );
|
||||
$topCount = $genres[$topId];
|
||||
$tied = count(array_filter(
|
||||
$genres, fn( $c ) => $c === $topCount
|
||||
));
|
||||
if( $tied > 1 ){
|
||||
$genreConflict = true;
|
||||
$genreConflicts++;
|
||||
}
|
||||
$newGenreId = $topId === self::NO_GENRE_SENTINEL
|
||||
? self::DEFAULT_GENRE_ID
|
||||
: ($genreMap[$topId] ?? self::DEFAULT_GENRE_ID);
|
||||
|
||||
$gameSlug = $game->slug;
|
||||
if (count($platforms) > 1) {
|
||||
$platformSlug = DB::table('platforms')->where('id', $newPlatformId)->value('slug');
|
||||
$gameSlug = $game->slug . '-' . $platformSlug;
|
||||
}
|
||||
|
||||
$newGameId = DB::table('games')
|
||||
->insertGetId([
|
||||
'name' => $game->name,
|
||||
'slug' => $gameSlug,
|
||||
'platform_id' => $newPlatformId,
|
||||
'genre_id' => $newGenreId,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
DB::table('migration_game_plan')->insert([
|
||||
'wp_game_id' => $gameId,
|
||||
'wp_platform_id' => $platformId,
|
||||
'game_id' => $newGameId,
|
||||
'wp_genre_id' => $topId === self::NO_GENRE_SENTINEL ? null : $topId,
|
||||
'post_count' => $info['count'],
|
||||
'genre_conflict' => $genreConflict,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$created++;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
$this->info("$created games created. $genreConflicts genre conflicts.");
|
||||
}
|
||||
}
|
||||
149
app/Console/Commands/MigrateNewsExecute.php
Normal file
149
app/Console/Commands/MigrateNewsExecute.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Helpers\MigrationHelpers;
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
#[Signature('migrate:news:execute {--limit=}')]
|
||||
#[Description("Migrate WP news to news table")]
|
||||
class MigrateNewsExecute extends Command
|
||||
{
|
||||
private const array STATE_MAP = [
|
||||
'draft' => 'draft',
|
||||
'pending' => 'pending',
|
||||
'publish' => 'published',
|
||||
'private' => 'hidden',
|
||||
'locked' => 'locked',
|
||||
];
|
||||
|
||||
private array $stats = [];
|
||||
|
||||
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 migrateNews($post): void
|
||||
{
|
||||
$exists = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('source_table', 'wp_posts__news')
|
||||
->where('source_id', $post->ID)
|
||||
->exists();
|
||||
if ($exists)
|
||||
return;
|
||||
|
||||
$meta = DB::connection('old_wp')
|
||||
->table('postmeta')
|
||||
->where('post_id', $post->ID)
|
||||
->whereIn('meta_key', ['release_site', 'romhacks_page', 'youtube_video' ])
|
||||
->pluck('meta_value', 'meta_key');
|
||||
|
||||
$categoryTtId = 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)
|
||||
->where('tt.taxonomy', 'news-category')
|
||||
->value('tt.term_taxonomy_id');
|
||||
|
||||
$categoryId = null;
|
||||
if( $categoryTtId ){
|
||||
$categoryId = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('target_table', 'categories' )
|
||||
->where('source_id', $categoryTtId)
|
||||
->value('target_id');
|
||||
if( !$categoryId ){
|
||||
$this->stats['missing_category']++;
|
||||
}
|
||||
}
|
||||
|
||||
$userId = DB::table('migration_user_plan')
|
||||
->where('wp_user_id', $post->post_author )
|
||||
->value('user_id');
|
||||
|
||||
if( !$userId ){
|
||||
$this->stats['missing_author']++;
|
||||
return;
|
||||
}
|
||||
|
||||
$slug = $this->uniqueSlug(rawurldecode($post->post_name), 'news');
|
||||
$description = trim($post->post_content) === '' ? '' : MigrationHelpers::htmlToMarkdown($post->post_content);
|
||||
|
||||
if( isset( $meta['romhacks_page'] ) && $meta['romhacks_page'] !== null && $meta['romhacks_page'] !== '' ){
|
||||
$description .= "\n\nLink to: " . $meta['romhacks_page'];
|
||||
}
|
||||
|
||||
$newsId = DB::table('news')
|
||||
->insertGetId([
|
||||
'title' => $post->post_title,
|
||||
'slug' => $slug,
|
||||
'category_id' => $categoryId,
|
||||
'description' => $description,
|
||||
'state' => self::STATE_MAP[$post->post_status],
|
||||
'entry_id' => 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,
|
||||
]);
|
||||
|
||||
DB::table('migrations_logs')->insert([
|
||||
'source_system' => 'wp',
|
||||
'source_table' => 'wp_posts__news',
|
||||
'source_id' => $post->ID,
|
||||
'target_table' => 'news',
|
||||
'target_id' => $newsId,
|
||||
'status' => 'done',
|
||||
'migrated_at' => now(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->stats['created']++;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->stats = [ 'created' => 0, 'missing_author' => 0, 'missing_category' => 0 ];
|
||||
|
||||
$query = DB::connection('old_wp')
|
||||
->table('posts')
|
||||
->where('post_type', 'news')
|
||||
->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_author', 'post_status', 'post_date', 'post_modified')->get();
|
||||
|
||||
$this->info( "{$posts->count()} posts found" );
|
||||
|
||||
$this->withProgressBar($posts, fn($post) => $this->migrateNews($post) );
|
||||
|
||||
$this->newLine();
|
||||
$this->info( "Created {$this->stats['created']} posts. Missing authors: {$this->stats['missing_author']}. Missing category: {$this->stats['missing_category']}" );
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
128
app/Console/Commands/MigrateNewsImages.php
Normal file
128
app/Console/Commands/MigrateNewsImages.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
#[Signature('migrate:news:images {--wp-uploads-path=} {--limit=}')]
|
||||
#[Description("Migrate WP galeries for news.")]
|
||||
class MigrateNewsImages extends Command
|
||||
{
|
||||
private const string GALLERYABLE_TYPE = 'App\\Models\\News';
|
||||
|
||||
private function copyAttachment( int $attachmentId, string $wpUploadsPath, string $destinationPath ): ?string
|
||||
{
|
||||
$relativePath = DB::connection('old_wp')
|
||||
->table('postmeta')
|
||||
->where('post_id', $attachmentId)
|
||||
->where('meta_key', '_wp_attached_file')
|
||||
->value('meta_value');
|
||||
|
||||
if( !$relativePath )
|
||||
return null;
|
||||
|
||||
$sourcePath = rtrim($wpUploadsPath, '/') . '/' . $relativePath;
|
||||
if( !is_file($sourcePath) )
|
||||
return null;
|
||||
|
||||
$extension = pathinfo($sourcePath, PATHINFO_EXTENSION);
|
||||
$filename = Str::random(40) . ($extension ? '.' . $extension : '');
|
||||
$destinationRelative = $destinationPath . '/' . $filename;
|
||||
|
||||
$stream = fopen($sourcePath, 'r');
|
||||
Storage::disk('public')->put($destinationRelative, $stream);
|
||||
if( is_resource($stream) )
|
||||
fclose($stream);
|
||||
|
||||
return $destinationRelative;
|
||||
}
|
||||
|
||||
private function processEntry( int $wpPostId, int $entryId, string $wpUploadsPath, array &$stats ): void
|
||||
{
|
||||
$exists = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('source_table', 'wp_posts__attachments')
|
||||
->where('source_id', $wpPostId)
|
||||
->exists();
|
||||
|
||||
if( $exists )
|
||||
return;
|
||||
|
||||
$galleryRaw = DB::connection('old_wp')
|
||||
->table('postmeta')
|
||||
->where('post_id', $wpPostId)
|
||||
->where('meta_key', 'my_gallery')
|
||||
->value('meta_value');
|
||||
|
||||
$attachmentIds = $galleryRaw ? (@unserialize($galleryRaw) ?: []) : [];
|
||||
|
||||
foreach ( array_values( $attachmentIds ) as $order => $attachmentId ) {
|
||||
$newPath = $this->copyAttachment( (int) $attachmentId, $wpUploadsPath, "news/gallery-images/{$entryId}" );
|
||||
if( !$newPath ){
|
||||
$stats['missing_files']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
DB::table('galleries')
|
||||
->insert([
|
||||
'galleryable_type' => self::GALLERYABLE_TYPE,
|
||||
'galleryable_id' => $entryId,
|
||||
'image' => $newPath,
|
||||
'order' => $order,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
$stats['gallery_images']++;
|
||||
}
|
||||
|
||||
DB::table('migrations_logs')
|
||||
->insert([
|
||||
'source_system' => 'wp',
|
||||
'source_table' => 'wp_posts__attachments',
|
||||
'source_id' => $wpPostId,
|
||||
'target_table' => 'news',
|
||||
'target_id' => $entryId,
|
||||
'status' => 'done',
|
||||
'migrated_at' => now(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$wpUploadsPath = $this->option('wp-uploads-path');
|
||||
if( !$wpUploadsPath || !is_dir($wpUploadsPath) ){
|
||||
$this->error('Missing WP Uploads Path');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$query = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('source_table', 'wp_posts__news')
|
||||
->where('target_table', 'news')
|
||||
;
|
||||
|
||||
if( $limit = $this->option('limit') ){
|
||||
$query->limit((int)$limit);
|
||||
}
|
||||
|
||||
$rows = $query->get(['source_id', 'target_id']);
|
||||
$this->info("{$rows->count()} news need to be migrated");
|
||||
|
||||
$stats = ['gallery_images' => 0, 'missing_files' => 0 ];
|
||||
|
||||
$this->withProgressBar($rows, function($row) use($wpUploadsPath, &$stats) {
|
||||
$this->processEntry( $row->source_id, $row->target_id, $wpUploadsPath, $stats );
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
$this->info("Migrated attachments Galleries: {$stats['gallery_images']}, Missing files: {$stats['missing_files']}");
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
117
app/Console/Commands/MigrateReviewsExecute.php
Normal file
117
app/Console/Commands/MigrateReviewsExecute.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Helpers\MigrationHelpers;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:reviews:execute {--limit=}')]
|
||||
class MigrateReviewsExecute extends Command
|
||||
{
|
||||
|
||||
private function migrateReview( $post, array &$stats ): void
|
||||
{
|
||||
$exists = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('source_table', 'wp_posts__reviews')
|
||||
->where('source_id', $post->ID )
|
||||
->exists();
|
||||
|
||||
if( $exists )
|
||||
return;
|
||||
|
||||
$meta = DB::connection('old_wp')
|
||||
->table('postmeta')
|
||||
->where('post_id', $post->ID)
|
||||
->whereIn('meta_key', ['reviews_post_link', 'review_rating'])
|
||||
->pluck('meta_value', 'meta_key')
|
||||
;
|
||||
|
||||
$linkedWpPostId = $meta['reviews_post_link'] ?? null;
|
||||
if( !$linkedWpPostId ){
|
||||
$stats['missing_entry']++;
|
||||
return;
|
||||
}
|
||||
|
||||
$entryId = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('source_table', 'wp_posts')
|
||||
->where('source_id', (int) $linkedWpPostId )
|
||||
->where('target_table', 'entries')
|
||||
->value('target_id');
|
||||
|
||||
if( !$entryId ){
|
||||
$stats['missing_entry']++;
|
||||
return;
|
||||
}
|
||||
|
||||
$userId = DB::table('migration_user_plan')
|
||||
->where('wp_user_id', $post->post_author )
|
||||
->value('user_id');
|
||||
|
||||
if( !$userId ){
|
||||
$stats['missing_author']++;
|
||||
return;
|
||||
}
|
||||
|
||||
$rating = $meta['review_rating'] ?? null;
|
||||
if( $rating === null || $rating === '' ){
|
||||
$stats['missing_rating']++;
|
||||
return;
|
||||
}
|
||||
|
||||
$description = trim($post->post_content) === '' ? '' : MigrationHelpers::htmlToMarkdown($post->post_content);
|
||||
|
||||
$reviewId = DB::table('entry_reviews')->insertGetId([
|
||||
'entry_id' => $entryId,
|
||||
'title' => $post->post_title,
|
||||
'rating' => (int) $rating,
|
||||
'description' => $description,
|
||||
'user_id' => $userId,
|
||||
'created_at' => $post->post_date,
|
||||
'updated_at' => $post->post_modified,
|
||||
]);
|
||||
|
||||
DB::table('migrations_logs')->insert([
|
||||
'source_system' => 'wp',
|
||||
'source_table' => 'wp_posts__reviews',
|
||||
'source_id' => $post->ID,
|
||||
'target_table' => 'entry_reviews',
|
||||
'target_id' => $reviewId,
|
||||
'status' => 'done',
|
||||
'migrated_at' => now(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$stats['created']++;
|
||||
}
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$query = DB::connection('old_wp')
|
||||
->table('posts')
|
||||
->where('post_type', 'reviews')
|
||||
->where('post_status', 'publish')
|
||||
;
|
||||
|
||||
if( $limit = $this->option('limit') ) {
|
||||
$query->limit((int) $limit);
|
||||
}
|
||||
|
||||
$posts = $query->select('ID', 'post_title', 'post_content', 'post_author', 'post_date', 'post_modified' )->get();
|
||||
$this->info("{$posts->count()} reviews found.");
|
||||
|
||||
$stats = ['created' => 0, 'missing_entry' => 0, 'missing_author' => 0, 'missing_rating' => 0 ];
|
||||
|
||||
$this->withProgressBar($posts, function($post) use (&$stats){
|
||||
$this->migrateReview($post, $stats);
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
$this->info("{$stats['created']} reviews created. {$stats['missing_entry']} missing entry. {$stats['missing_author']} missing author. {$stats['missing_rating']} missing rating.");
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
44
app/Console/Commands/MigrateTaxonomiesConfigure.php
Normal file
44
app/Console/Commands/MigrateTaxonomiesConfigure.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:taxonomies:configure')]
|
||||
#[Description('Configure taxonomies table.')]
|
||||
class MigrateTaxonomiesConfigure extends Command
|
||||
{
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$taxonomyMap = [];
|
||||
$taxonomy = "a";
|
||||
$tableName = "";
|
||||
|
||||
while( $taxonomy !== "" ){
|
||||
$taxonomy = "";
|
||||
$tableName = "";
|
||||
|
||||
$taxonomy = $this->ask("Write WP taxonomy name. Write nothing if you want to save changes.", "" );
|
||||
if( $taxonomy == "" )
|
||||
break;
|
||||
|
||||
$tableName = $this->ask("Write equivalent Laravel table name.");
|
||||
|
||||
$taxonomyMap[$taxonomy] = $tableName;
|
||||
}
|
||||
|
||||
DB::table('migration_settings')
|
||||
->updateOrInsert([
|
||||
'key' => 'wp_taxonomies_to_laravel_tables'
|
||||
],[
|
||||
'value' => json_encode($taxonomyMap), 'updated_at' => now()
|
||||
]);
|
||||
|
||||
$this->info('WP Taxonomies to Laravel tables have been configured.');
|
||||
}
|
||||
}
|
||||
77
app/Console/Commands/MigrateTaxonomiesExecute.php
Normal file
77
app/Console/Commands/MigrateTaxonomiesExecute.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:taxonomies:execute')]
|
||||
#[Description('Migrate taxonomies, make sure you have configured the migration before that.')]
|
||||
class MigrateTaxonomiesExecute extends Command
|
||||
{
|
||||
public function handle()
|
||||
{
|
||||
$taxMap = json_decode(DB::table('migration_settings')->where('key', 'wp_taxonomies_to_laravel_tables')->value('value'), true);
|
||||
|
||||
if( !$taxMap ){
|
||||
$this->error("No WP taxonomies need to be transferred.");
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if( $this->ask("Are you sure you want launch that migration? Write ok if you want to launch it.", "") !== 'ok' ){
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
foreach ( $taxMap as $wpTax => $table )
|
||||
{
|
||||
$this->info("Migrate: {$wpTax} => {$table}");
|
||||
|
||||
$terms = DB::connection('old_wp')
|
||||
->table('term_taxonomy')
|
||||
->join('terms', 'term_taxonomy.term_id', '=', 'terms.term_id')
|
||||
->where('term_taxonomy.taxonomy', $wpTax)
|
||||
->select('term_taxonomy.term_taxonomy_id', 'terms.name', 'terms.slug')
|
||||
->get();
|
||||
|
||||
$this->withProgressBar($terms, function ($term) use ($table) {
|
||||
|
||||
$exists = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('source_table', 'wp_term_taxonomy')
|
||||
->where('source_id', $term->term_taxonomy_id )
|
||||
->exists();
|
||||
|
||||
if( $exists )
|
||||
return;
|
||||
|
||||
$Id = DB::table( $table )->where('slug', $term->slug)->value('id');
|
||||
|
||||
if( $Id === null) {
|
||||
$Id = DB::table($table)
|
||||
->insertGetId([
|
||||
'name' => $term->name, 'slug' => $term->slug,
|
||||
'created_at' => now(), 'updated_at' => now()
|
||||
]);
|
||||
}
|
||||
|
||||
DB::table('migrations_logs')->insert([
|
||||
'source_system' => 'wp',
|
||||
'source_table' => 'wp_term_taxonomy',
|
||||
'source_id' => $term->term_taxonomy_id,
|
||||
'target_table' => $table,
|
||||
'target_id' => $Id,
|
||||
'status' => 'done',
|
||||
'migrated_at' => now(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now()
|
||||
]);
|
||||
});
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
$this->info("Migration done");
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
57
app/Console/Commands/MigrateUsersConfigure.php
Normal file
57
app/Console/Commands/MigrateUsersConfigure.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:users:configure')]
|
||||
#[Description('Configure users migrations settings like roles.')]
|
||||
class MigrateUsersConfigure extends Command
|
||||
{
|
||||
|
||||
private function getWpRoles(): array
|
||||
{
|
||||
$wpRoles = ['administrator', 'editor', 'author', 'contributor', 'subscriber'];
|
||||
$customWpRoles = ['bot'];
|
||||
|
||||
return array_merge($wpRoles, $customWpRoles);
|
||||
}
|
||||
|
||||
private function getOldXfGroups(): array|Collection
|
||||
{
|
||||
return DB::connection('old_xf')->table('user_group')->pluck('title', 'user_group_id');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$wpRoles = $this->getWpRoles();
|
||||
$roleMap = [];
|
||||
foreach ($wpRoles as $role) {
|
||||
$roleMap[$role] = (int) $this->ask("XenForo group ID linked to role {$role}");
|
||||
}
|
||||
DB::table('migration_settings')
|
||||
->updateOrInsert([
|
||||
'key' => 'wp_role_to_xf_group'
|
||||
], [
|
||||
'value' => json_encode($roleMap), 'updated_at' => now()
|
||||
]);
|
||||
|
||||
$oldXfGroups = $this->getOldXfGroups();
|
||||
$groupMap = [];
|
||||
foreach ($oldXfGroups as $id => $title) {
|
||||
$groupMap[$id] = (int) $this->ask("Xenforo group ID linked to old XF group {$title}|{$id}");
|
||||
}
|
||||
DB::table('migration_settings')
|
||||
->updateOrInsert([
|
||||
'key' => 'old_xf_group_to_xf_group'
|
||||
],[
|
||||
'value' => json_encode($groupMap), 'updated_at' => now()
|
||||
]);
|
||||
|
||||
$this->info('XF groups updated.');
|
||||
}
|
||||
}
|
||||
229
app/Console/Commands/MigrateUsersExecute.php
Normal file
229
app/Console/Commands/MigrateUsersExecute.php
Normal file
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\MigrationUserPlan;
|
||||
use App\Services\XenforoApiService;
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
#[Signature('migrate:users:execute {--limit=} {--wp-uploads-path=} {--xf-data-path=}')]
|
||||
#[Description("Migrate all users in migration table to XenForo.")]
|
||||
class MigrateUsersExecute extends Command
|
||||
{
|
||||
|
||||
private function extractWpRole(?string $serialized): string
|
||||
{
|
||||
$caps = $serialized ? @unserialize($serialized) : null;
|
||||
return is_array($caps) ? (array_key_first($caps) ?: 'contributor') : 'contributor';
|
||||
}
|
||||
|
||||
private function extractWpAvatarPath(?string $serialized, string $wpUploadsPath ): ?string
|
||||
{
|
||||
$data = $serialized ? @unserialize($serialized) : null;
|
||||
$mediaId = $data['media_id'] ?? null;
|
||||
if( !$mediaId )
|
||||
return null;
|
||||
|
||||
$relative = DB::connection('old_wp')
|
||||
->table('postmeta')
|
||||
->where('post_id', $mediaId)
|
||||
->where('meta_key', '_wp_attached_file')
|
||||
->value('meta_value');
|
||||
|
||||
if( !$relative )
|
||||
return null;
|
||||
|
||||
$path = rtrim($wpUploadsPath, '/') . '/' . $relative;
|
||||
return is_file($path) ? $path : null;
|
||||
}
|
||||
|
||||
private function extractXfAvatarPath(int $userId, string $xfDataPath, string $size = 'o' ): ?string
|
||||
{
|
||||
$path = sprintf('%s/avatars/%s/%d/%d.jpg',
|
||||
rtrim($xfDataPath, '/'), $size, (int) floor($userId / 1000), $userId
|
||||
);
|
||||
return is_file($path) ? $path : null;
|
||||
}
|
||||
|
||||
private function extractXfBannerPath(int $userId, string $xfDataPath, string $size = 'l' ): ?string
|
||||
{
|
||||
$path = sprintf('%s/profile_banners/%s/%d/%d.jpg',
|
||||
rtrim($xfDataPath, '/'), $size, (int) floor($userId / 1000), $userId
|
||||
);
|
||||
return is_file($path) ? $path : null;
|
||||
}
|
||||
|
||||
private function buildUserInfos( MigrationUserPlan $plan, array $roleMap, array $groupMap, string $wpUploadsPath, string $xfDataPath ): array
|
||||
{
|
||||
$infos = [
|
||||
'username' => $plan->xf_username ?: $plan->wp_username,
|
||||
'email' => $plan->email,
|
||||
'user_group_id' => null,
|
||||
'password' => Str::uuid(), // Used for API verifications.
|
||||
'xf_user_id' => 0,
|
||||
'avatar_path' => null,
|
||||
'banner_path' => null,
|
||||
'register_date' => null,
|
||||
'profile' => [
|
||||
'about' => null,
|
||||
'website' => null,
|
||||
],
|
||||
'source_real_password' => null,
|
||||
'wp_password' => null,
|
||||
'xf_scheme_class' => null,
|
||||
'xf_password_data' => null,
|
||||
];
|
||||
|
||||
if( $plan->wp_user_id )
|
||||
{
|
||||
$wp = DB::connection('old_wp')
|
||||
->table('users')
|
||||
->leftJoin('usermeta as m1', fn( $j ) => $j->on('users.ID', '=', 'm1.user_id')->where('m1.meta_key', '=', 'description') )
|
||||
->leftJoin('usermeta as m2', fn( $j ) => $j->on('users.ID', '=', 'm2.user_id')->where('m2.meta_key', '=', 'wp_capabilities') )
|
||||
->leftJoin('usermeta as m3', fn( $j ) => $j->on('users.ID', '=', 'm3.user_id')->where('m3.meta_key', '=', 'simple_local_avatar') )
|
||||
->where('users.ID', '=', $plan->wp_user_id )
|
||||
->select('m1.meta_value as description', 'm2.meta_value as capabilities', 'm3.meta_value as avatar_meta', 'users.user_url as website', 'users.user_pass as password', 'users.user_registred' )
|
||||
->first();
|
||||
|
||||
$infos['register_date'] = $wp->user_registred ? strtotime($wp->user_registred) : null;
|
||||
$infos['profile']['about'] = $wp->description;
|
||||
$infos['profile']['website'] = $wp->website;
|
||||
$role = $this->extractWpRole($wp->capabilities);
|
||||
$infos['user_group_id'] = $roleMap[$role] ?? $roleMap['contributor'];
|
||||
|
||||
if( $url = $this->extractWpAvatarPath($wp->avatar_meta, $wpUploadsPath)){
|
||||
$infos['avatar_path'] = $url;
|
||||
}
|
||||
|
||||
$infos['source_real_password'] = 'wp';
|
||||
$infos['wp_password'] = $wp->password;
|
||||
}
|
||||
|
||||
if( $plan->xf_user_id )
|
||||
{
|
||||
$xf = DB::connection('old_xf')
|
||||
->table('user')
|
||||
->leftJoin('user_profile', 'user.user_id', '=', 'user_profile.user_id')
|
||||
->leftJoin('user_authenticate', 'user.user_id', '=', 'user_authenticate.user_id')
|
||||
->where('user.user_id', '=', $plan->xf_user_id)
|
||||
->select('user.avatar_date', 'user.user_group_id', 'user.register_date', 'user_profile.about', 'user_profile.website', 'user_profile.banner_date', 'user_authenticate.scheme_class', 'user_authenticate.data')
|
||||
->first();
|
||||
|
||||
if( !$infos['register_date'] && $xf )
|
||||
$infos['register_date'] = $xf->register_date ?: null;
|
||||
if( !$infos['profile']['about'] && $xf )
|
||||
$infos['profile']['about'] = $xf->about ?: null;
|
||||
if( !$infos['profile']['website'] && $xf )
|
||||
$infos['profile']['website'] = $xf->website ?: null;
|
||||
|
||||
if( !$plan->wp_user_id ){
|
||||
$infos['user_group_id'] = $groupMap[$xf->user_group_id] ?? reset($groupMap);
|
||||
}
|
||||
$infos['xf_user_id'] = $plan->xf_user_id;
|
||||
|
||||
if( $infos['avatar_path'] === null && (int) $xf->avatar_date > 0){
|
||||
if( $path = $this->extractXfAvatarPath($plan->xf_user_id, $xfDataPath)){
|
||||
$infos['avatar_path'] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
if( $infos['banner_path'] === null && (int) $xf->banner_date > 0 ){
|
||||
if( $path = $this->extractXfBannerPath($plan->xf_user_id, $xfDataPath)){
|
||||
$infos['banner_path'] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
if( $infos['source_real_password'] === null && $xf->scheme_class && $xf->data ){
|
||||
$infos['source_real_password'] = 'xf';
|
||||
$infos['xf_scheme_class'] = $xf->scheme_class;
|
||||
$infos['xf_password_data'] = $xf->data;
|
||||
}
|
||||
}
|
||||
|
||||
return $infos;
|
||||
}
|
||||
|
||||
private function logMap( string $sourceSystem, string $sourceTable, int $sourceId, int $targetId )
|
||||
{
|
||||
DB::table('migrations_logs')->insert([
|
||||
'source_system' => $sourceSystem,
|
||||
'source_table' => $sourceTable,
|
||||
'source_id' => $sourceId,
|
||||
'target_table' => 'xf_user',
|
||||
'target_id' => $targetId,
|
||||
'status' => 'done',
|
||||
'migrated_at' => now(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now()
|
||||
]);
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$wpUploadsPath = $this->option('wp-uploads-path');
|
||||
$xfDataPath = $this->option('xf-data-path');
|
||||
|
||||
if( !$wpUploadsPath || !is_dir($wpUploadsPath) ){
|
||||
$this->error('Missing WP Uploads Path');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if( !$xfDataPath || !is_dir($xfDataPath) ){
|
||||
$this->error('Missing XF Data Path');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$roleMap = json_decode(DB::table('migration_settings')->where('key', 'wp_role_to_xf_group')->value('value'), true);
|
||||
$groupMap = json_decode(DB::table('migration_settings')->where('key', 'old_xf_group_to_xf_group')->value('value'), true);
|
||||
|
||||
if( !$roleMap || !$groupMap ) {
|
||||
$this->error('Role map and group map are required.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$query = MigrationUserPlan::where('status', 'approved')->whereNull('user_id');
|
||||
if( $limit = $this->option('limit') ) {
|
||||
$query->limit((int) $limit);
|
||||
}
|
||||
|
||||
$rows = $query->get();
|
||||
$this->info("{$rows->count()} accounts will be created on XenForo database !!!.");
|
||||
$ok = $this->ask("Write 'ok' if you want to start the migration. Everything else to quit the migration.");
|
||||
if( $ok !== 'ok' ) {
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$service = app(XenforoApiService::class);
|
||||
|
||||
$this->withProgressBar($rows, function($plan) use( $roleMap, $groupMap, $wpUploadsPath, $xfDataPath, $service ) {
|
||||
try {
|
||||
$infos = $this->buildUserInfos( $plan, $roleMap, $groupMap, $wpUploadsPath, $xfDataPath );
|
||||
|
||||
[ $userId, $passwordSet ] = $service->_migrateUser( $infos );
|
||||
if( !$userId ){
|
||||
throw new \RuntimeException("Error when user creation.");
|
||||
}
|
||||
|
||||
MigrationUserPlan::where('id', $plan->id )->update([ 'user_id' => $userId ]);
|
||||
|
||||
$this->logMap( $plan->wp_user_id ? 'wp' : 'xf', $plan->wp_user_id ? 'wp_users' : 'xf_user', $plan->wp_user_id ?? $plan->xf_user_id, $userId );
|
||||
if( $plan->wp_user_id && $plan->xf_user_id ){
|
||||
$this->logMap('xf', 'xf_user', $plan->xf_user_id, $userId );
|
||||
}
|
||||
|
||||
|
||||
} catch ( \Throwable $e ) {
|
||||
Log::error("Unable to create Plan#{$plan->id} user : {$e->getMessage()}");
|
||||
}
|
||||
});
|
||||
|
||||
$this->newLine(2);
|
||||
$this->info("Process finished.");
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
143
app/Console/Commands/MigrateUsersPlan.php
Normal file
143
app/Console/Commands/MigrateUsersPlan.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\MigrationUserPlan;
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:users:plan {--fresh}')]
|
||||
#[Description('Construct migration plan for WP/XF accounts.')]
|
||||
class MigrateUsersPlan extends Command
|
||||
{
|
||||
public function handle()
|
||||
{
|
||||
if( $this->option('fresh') ) {
|
||||
MigrationUserPlan::truncate();
|
||||
}
|
||||
|
||||
$this->info("Loading old XF accounts...");
|
||||
|
||||
$xfUsers = DB::connection('old_xf')->table('user')
|
||||
->select('user_id','username','email')
|
||||
->get()
|
||||
->keyBy('user_id')
|
||||
;
|
||||
|
||||
$xfUsersByEmail = $xfUsers->groupBy(fn($u) => strtolower($u->email));
|
||||
|
||||
$this->info("Loading old WP accounts...");
|
||||
|
||||
$wpUsers = DB::connection('old_wp')->table('users')
|
||||
->leftJoin('usermeta', function ($join) {
|
||||
$join->on('users.ID', '=', 'usermeta.user_id')->where('usermeta.meta_key', '=', 'xf_user_id');
|
||||
})
|
||||
->select('users.ID as wp_id', 'users.user_email as email', 'users.user_login as username', 'usermeta.meta_value as linked_xf_id')
|
||||
->get();
|
||||
|
||||
$linkedXfIds = [];
|
||||
|
||||
$this->withProgressBar( $wpUsers, function ($wp) use ($xfUsers, $xfUsersByEmail, &$linkedXfIds) {
|
||||
$email = strtolower($wp->email);
|
||||
$linkedId = $wp->linked_xf_id ? (int) $wp->linked_xf_id : null;
|
||||
|
||||
if( $linkedId && $xfUsers->has($linkedId) ) {
|
||||
|
||||
$xf = $xfUsers->get($linkedId);
|
||||
$matchType = strtolower($xf->email) === $email ? 'explicit' : 'conflict';
|
||||
|
||||
MigrationUserPlan::updateOrCreate(
|
||||
['wp_user_id' => $wp->wp_id],
|
||||
[
|
||||
'xf_user_id' => $linkedId,
|
||||
'match_type' => $matchType,
|
||||
'email' => $email,
|
||||
'wp_username' => $wp->username,
|
||||
'xf_username' => $xf->username,
|
||||
'note' => $matchType === 'conflict' ? "E-Mail différent: {$xf->email}" : null,
|
||||
'status' => $matchType === 'explicit' ? 'approved' : 'pending',
|
||||
]
|
||||
);
|
||||
$linkedXfIds[$linkedId] = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if( $linkedId && !$xfUsers->has($linkedId) ) {
|
||||
MigrationUserPlan::updateOrCreate(
|
||||
['wp_user_id' => $wp->wp_id ],
|
||||
[
|
||||
'match_type' => 'conflict',
|
||||
'email' => $email,
|
||||
'wp_username' => $wp->username,
|
||||
'note' => "xf_user_id={$linkedId} introuvable",
|
||||
'status' => 'approved'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$candidates = $xfUsersByEmail->get($email,collect());
|
||||
|
||||
if( $candidates->count() === 1 ){
|
||||
$xf = $candidates->first();
|
||||
MigrationUserPlan::updateOrCreate(
|
||||
['wp_user_id' => $wp->wp_id],
|
||||
[
|
||||
'xf_user_id' => $xf->user_id,
|
||||
'match_type' => 'email',
|
||||
'email' => $email,
|
||||
'wp_username' => $wp->username,
|
||||
'xf_username' => $xf->username,
|
||||
'status' => 'pending'
|
||||
]
|
||||
);
|
||||
$linkedXfIds[$xf->user_id] = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if( $candidates->count() > 1 ){
|
||||
MigrationUserPlan::updateOrCreate(
|
||||
['wp_user_id' => $wp->wp_id ],
|
||||
[
|
||||
'match_type' => 'conflict',
|
||||
'email' => $email,
|
||||
'wp_username' => $wp->username,
|
||||
'note' => "E-mail identique sur plusieurs comptes XF.",
|
||||
]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
MigrationUserPlan::updateOrCreate(
|
||||
['wp_user_id' => $wp->wp_id ],
|
||||
[
|
||||
'match_type' => 'wp_only',
|
||||
'email' => $email,
|
||||
'wp_username' => $wp->username,
|
||||
'status' => 'approved'
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
$this->info("Listing old XF accounts...");
|
||||
foreach( $xfUsers as $id => $xf ) {
|
||||
if( isset($linkedXfIds[$id]) ) {
|
||||
continue;
|
||||
}
|
||||
MigrationUserPlan::updateOrCreate(
|
||||
['xf_user_id' => $xf->user_id, 'wp_user_id' => null],
|
||||
[
|
||||
'match_type' => 'xf_only',
|
||||
'email' => strtolower($xf->email),
|
||||
'xf_username' => $xf->username,
|
||||
'status' => 'approved'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$pending = MigrationUserPlan::where('status', 'pending')->count();
|
||||
$this->newLine(2);
|
||||
$this->info("Plan generated. {$pending} pending cases.");
|
||||
}
|
||||
}
|
||||
41
app/Console/Commands/MigrateXFConfigure.php
Normal file
41
app/Console/Commands/MigrateXFConfigure.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:xf:configure')]
|
||||
#[Description("Configure forums map for migrating threads.")]
|
||||
class MigrateXFConfigure extends Command
|
||||
{
|
||||
public function handle()
|
||||
{
|
||||
$forums = DB::connection('old_xf')
|
||||
->table('node')
|
||||
->where('node_type_id', 'Forum')
|
||||
->select('node_id', 'title')
|
||||
->get()
|
||||
;
|
||||
|
||||
$this->info("{$forums->count()} forums found.");
|
||||
|
||||
$forumMap = [];
|
||||
foreach ($forums as $forum) {
|
||||
$newId = $this->ask("New ID for {$forum->title}");
|
||||
if( $newId !== null && $newId !== '' ){
|
||||
$map[$forum->node_id] = (int) $newId;
|
||||
}
|
||||
}
|
||||
|
||||
DB::table('migration_settings')
|
||||
->updateOrInsert(
|
||||
[ 'key' => 'old_xf_node_to_new_xf_node'],
|
||||
[ 'value' => json_encode($map), 'updated_at' => now() ],
|
||||
);
|
||||
|
||||
$this->info("Config saved.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user