Files
RomhackPlaza/app/Console/Commands/MigrateUsersExecute.php
2026-06-23 19:24:38 +02:00

230 lines
9.1 KiB
PHP

<?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;
}
}