Initial commit
This commit is contained in:
493
app/Console/Commands/MigrateCyclosProfilesCommand.php
Normal file
493
app/Console/Commands/MigrateCyclosProfilesCommand.php
Normal file
@@ -0,0 +1,493 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Bank;
|
||||
use App\Models\Locations\Location;
|
||||
use App\Models\Organization;
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class MigrateCyclosProfilesCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'migrate:cyclos-profiles {source_db? : Name of the source Cyclos database (skips prompt if provided)}';
|
||||
protected $description = 'Migrates the Cyclos profile contents from the old Cyclos database to the new Laravel database';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
// Use argument if provided, otherwise fall back to cache (set by migrate:cyclos during db:seed)
|
||||
$sourceDb = $this->argument('source_db') ?: cache()->get('cyclos_migration_source_db');
|
||||
|
||||
if (empty($sourceDb)) {
|
||||
// If not in cache, ask for it
|
||||
$this->info('The source Cyclos database should be imported into MySQL and accessible from this application.');
|
||||
$this->info('Hint: Place the database dump in the app root and import with: mysql -u root -p < cyclos_dump.sql');
|
||||
$sourceDb = $this->ask('Enter the name of the source Cyclos database');
|
||||
|
||||
if (empty($sourceDb)) {
|
||||
$this->error('Source database name is required.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Remove .sql extension if present
|
||||
if (str_ends_with(strtolower($sourceDb), '.sql')) {
|
||||
$sourceDb = substr($sourceDb, 0, -4);
|
||||
$this->info("Using database name: {$sourceDb}");
|
||||
}
|
||||
|
||||
// Verify the database exists
|
||||
$databases = DB::select('SHOW DATABASES');
|
||||
$databaseNames = array_map(fn($db) => $db->Database, $databases);
|
||||
|
||||
if (!in_array($sourceDb, $databaseNames)) {
|
||||
$this->error("Database '{$sourceDb}' does not exist.");
|
||||
$this->info('Available databases:');
|
||||
foreach ($databaseNames as $name) {
|
||||
if (!in_array($name, ['information_schema', 'mysql', 'performance_schema', 'sys'])) {
|
||||
$this->line(" - {$name}");
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
$this->info("Using source database from previous step: {$sourceDb}");
|
||||
}
|
||||
|
||||
$destinationDb = env('DB_DATABASE');
|
||||
|
||||
// Migrate phone field
|
||||
$tables = ['users', 'organizations', 'banks'];
|
||||
|
||||
foreach ($tables as $tableName) {
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$affectedRows = DB::affectingStatement("
|
||||
UPDATE `{$destinationDb}`.`{$tableName}` dest
|
||||
JOIN (
|
||||
SELECT
|
||||
c.member_id,
|
||||
LEFT(c.string_value, 20) AS phone -- Truncate to 20 characters
|
||||
FROM `{$sourceDb}`.`custom_field_values` c
|
||||
WHERE c.field_id = 7
|
||||
) src ON dest.cyclos_id = src.member_id
|
||||
SET dest.phone = src.phone
|
||||
");
|
||||
DB::commit();
|
||||
$this->info(ucfirst($tableName) . " phone field updated for $affectedRows records");
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
$this->error(ucfirst($tableName) . " phone field migration failed: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Migrate locations
|
||||
|
||||
$countryCodeMap = [
|
||||
860 => 2, // BE
|
||||
861 => 7, // PT
|
||||
862 => 1, // NL
|
||||
863 => 10, // country not set / other country → "Location not specified"
|
||||
];
|
||||
$cityCodeMap = [
|
||||
864 => 188, // Amsterdam
|
||||
865 => 200, // Haarlem
|
||||
866 => 316, // Leiden
|
||||
867 => 305, // The Hague
|
||||
868 => 300, // Delft
|
||||
869 => 331, // Rotterdam
|
||||
870 => 272, // Utrecht
|
||||
881 => 345, // Brussels
|
||||
];
|
||||
|
||||
$updatedRecordsCount = 0;
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
// Wrap the migration calls in a function that returns the count of updated records
|
||||
$updatedRecordsCount += $this->migrateLocationData('User', $destinationDb, $sourceDb, $countryCodeMap, $cityCodeMap);
|
||||
$updatedRecordsCount += $this->migrateLocationData('Organization', $destinationDb, $sourceDb, $countryCodeMap, $cityCodeMap);
|
||||
$updatedRecordsCount += $this->migrateLocationData('Bank', $destinationDb, $sourceDb, $countryCodeMap, $cityCodeMap);
|
||||
|
||||
DB::commit();
|
||||
// Output the total number of records updated
|
||||
$this->info("Location fields migration updated for: " . $updatedRecordsCount);
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
$this->error("Location fields migration failed: " . $e->getMessage());
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Migrate user about field
|
||||
$tables = ['users'];
|
||||
foreach ($tables as $tableName) {
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$affectedRows = DB::affectingStatement("
|
||||
UPDATE `{$destinationDb}`.`{$tableName}` dest
|
||||
JOIN (
|
||||
SELECT
|
||||
c.member_id,
|
||||
c.string_value AS about
|
||||
FROM `{$sourceDb}`.`custom_field_values` c
|
||||
WHERE c.field_id = 17
|
||||
) src ON dest.cyclos_id = src.member_id
|
||||
SET dest.about = src.about
|
||||
");
|
||||
DB::commit();
|
||||
$this->info(ucfirst($tableName) . " about field updated for $affectedRows records");
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
$this->error(ucfirst($tableName) . " about field migration failed: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Migrate motivation field
|
||||
$tables = ['users', 'organizations', 'banks'];
|
||||
foreach ($tables as $tableName) {
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$affectedRows = DB::affectingStatement("
|
||||
UPDATE `{$destinationDb}`.`{$tableName}` dest
|
||||
JOIN (
|
||||
SELECT
|
||||
c.member_id,
|
||||
c.string_value AS motivation
|
||||
FROM `{$sourceDb}`.`custom_field_values` c
|
||||
WHERE c.field_id = 35
|
||||
) src ON dest.cyclos_id = src.member_id
|
||||
SET dest.motivation = src.motivation
|
||||
");
|
||||
DB::commit();
|
||||
$this->info(ucfirst($tableName) . " motivation field updated for $affectedRows records");
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
$this->error(ucfirst($tableName) . " motivation field migration failed: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate website field
|
||||
$tables = ['users', 'organizations', 'banks'];
|
||||
foreach ($tables as $tableName) {
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$affectedRows = DB::affectingStatement("
|
||||
UPDATE `{$destinationDb}`.`{$tableName}` dest
|
||||
JOIN (
|
||||
SELECT
|
||||
c.member_id,
|
||||
c.string_value AS website
|
||||
FROM `{$sourceDb}`.`custom_field_values` c
|
||||
WHERE c.field_id = 10
|
||||
) src ON dest.cyclos_id = src.member_id
|
||||
SET dest.website = src.website
|
||||
");
|
||||
DB::commit();
|
||||
$this->info(ucfirst($tableName) . " website field updated for $affectedRows records");
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
$this->error(ucfirst($tableName) . " website field migration failed: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Migrate birthday field
|
||||
$tables = ['users'];
|
||||
foreach ($tables as $tableName) {
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$affectedRows = DB::affectingStatement("
|
||||
UPDATE `{$destinationDb}`.`{$tableName}` dest
|
||||
JOIN (
|
||||
SELECT
|
||||
c.member_id,
|
||||
STR_TO_DATE(REPLACE(c.string_value, '/', '-'), '%d-%m-%Y') AS birthday
|
||||
FROM `{$sourceDb}`.`custom_field_values` c
|
||||
WHERE c.field_id = 1
|
||||
) src ON dest.cyclos_id = src.member_id
|
||||
SET dest.date_of_birth = src.birthday
|
||||
");
|
||||
DB::commit();
|
||||
$this->info(ucfirst($tableName) . " birthday field updated for $affectedRows records");
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
$this->error(ucfirst($tableName) . " birthday field migration failed: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate General Newsletter field to message_settings table
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
// Get all newsletter preferences from Cyclos
|
||||
$newsletterPrefs = DB::table("{$sourceDb}.custom_field_values")
|
||||
->where('field_id', 28)
|
||||
->get(['member_id', 'possible_value_id']);
|
||||
|
||||
$totalUpdated = 0;
|
||||
$tables = [
|
||||
'users' => User::class,
|
||||
'organizations' => Organization::class,
|
||||
'banks' => Bank::class
|
||||
];
|
||||
|
||||
foreach ($tables as $tableName => $modelClass) {
|
||||
foreach ($newsletterPrefs as $pref) {
|
||||
$entity = DB::table($tableName)
|
||||
->where('cyclos_id', $pref->member_id)
|
||||
->first();
|
||||
|
||||
if ($entity) {
|
||||
$model = $modelClass::find($entity->id);
|
||||
if ($model) {
|
||||
// Convert: 790 (No) → 0, 791 (Yes) → 1, null → 1
|
||||
$value = $pref->possible_value_id == 790 ? 0 : 1;
|
||||
|
||||
// Update or create message settings
|
||||
$model->message_settings()->updateOrCreate(
|
||||
['message_settingable_id' => $model->id, 'message_settingable_type' => $modelClass],
|
||||
['general_newsletter' => $value]
|
||||
);
|
||||
$totalUpdated++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
$this->info("General newsletter field migrated to message_settings for {$totalUpdated} records");
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
$this->error("General newsletter field migration failed: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// Migrate Local Newsletter field to message_settings table
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
// Get all newsletter preferences from Cyclos
|
||||
$newsletterPrefs = DB::table("{$sourceDb}.custom_field_values")
|
||||
->where('field_id', 29)
|
||||
->get(['member_id', 'possible_value_id']);
|
||||
|
||||
$totalUpdated = 0;
|
||||
$tables = [
|
||||
'users' => User::class,
|
||||
'organizations' => Organization::class,
|
||||
'banks' => Bank::class
|
||||
];
|
||||
|
||||
foreach ($tables as $tableName => $modelClass) {
|
||||
foreach ($newsletterPrefs as $pref) {
|
||||
$entity = DB::table($tableName)
|
||||
->where('cyclos_id', $pref->member_id)
|
||||
->first();
|
||||
|
||||
if ($entity) {
|
||||
$model = $modelClass::find($entity->id);
|
||||
if ($model) {
|
||||
// Convert: 792 (No) → 0, 793 (Yes) → 1, null → 1
|
||||
$value = $pref->possible_value_id == 792 ? 0 : 1;
|
||||
|
||||
// Update or create message settings
|
||||
$model->message_settings()->updateOrCreate(
|
||||
['message_settingable_id' => $model->id, 'message_settingable_type' => $modelClass],
|
||||
['local_newsletter' => $value]
|
||||
);
|
||||
$totalUpdated++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
$this->info("Local newsletter field migrated to message_settings for {$totalUpdated} records");
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
$this->error("Local newsletter field migration failed: " . $e->getMessage());
|
||||
}
|
||||
|
||||
|
||||
// Migrate Cyclos skills from refined database
|
||||
$tables = ['users', 'organizations'];
|
||||
foreach ($tables as $tableName) {
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$affectedRows = DB::affectingStatement("
|
||||
UPDATE `{$destinationDb}`.`{$tableName}` dest
|
||||
JOIN (
|
||||
SELECT
|
||||
c.member_id,
|
||||
CASE
|
||||
WHEN CHAR_LENGTH(c.string_value) > 495
|
||||
THEN CONCAT(LEFT(c.string_value, 495), ' ...')
|
||||
ELSE c.string_value
|
||||
END AS cyclos_skills
|
||||
FROM `{$sourceDb}`.`custom_field_values` c
|
||||
WHERE c.field_id = 13
|
||||
) src ON dest.cyclos_id = src.member_id
|
||||
SET dest.cyclos_skills = src.cyclos_skills
|
||||
");
|
||||
DB::commit();
|
||||
$this->info(ucfirst($tableName) . " skills field updated for $affectedRows records");
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
$this->error(ucfirst($tableName) . " skills field migration failed: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Strip all HTML tags from imported tables
|
||||
foreach ($tables as $tableName) {
|
||||
$records = DB::table($tableName)->select('id', 'cyclos_skills')->whereNotNull('cyclos_skills')->get();
|
||||
foreach ($records as $record) {
|
||||
$cleaned = strip_tags($record->cyclos_skills);
|
||||
if ($cleaned !== $record->cyclos_skills) {
|
||||
DB::table($tableName)->where('id', $record->id)->update(['cyclos_skills' => $cleaned]);
|
||||
}
|
||||
}
|
||||
$records = DB::table($tableName)->select('id', 'about')->whereNotNull('about')->get();
|
||||
foreach ($records as $record) {
|
||||
$cleaned = strip_tags($record->about);
|
||||
if ($cleaned !== $record->about) {
|
||||
DB::table($tableName)->where('id', $record->id)->update(['about' => $cleaned]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set suspicious robot members to inactive
|
||||
|
||||
// 1755
|
||||
// 1768
|
||||
// 1776
|
||||
// 1777
|
||||
|
||||
// Check if user.about is null, if true, copy skill tags where length > 50 to user.about
|
||||
// if user.about <> null, copy skill tags where length > 50 to about_short or update this field
|
||||
|
||||
protected function migrateLocationData($modelClass, $destinationDb, $sourceDb, $countryCodeMap, $cityCodeMap)
|
||||
{
|
||||
$fullyQualifiedModelClass = "App\\Models\\" . $modelClass;
|
||||
|
||||
$cyclos_countries = DB::table("{$sourceDb}.custom_field_values")
|
||||
->where('field_id', 36)
|
||||
->get(['possible_value_id', 'member_id']);
|
||||
$cyclos_cities = DB::table("{$sourceDb}.custom_field_values")
|
||||
->where('field_id', 38)
|
||||
->get(['possible_value_id', 'member_id']);
|
||||
|
||||
$remappedCountries = $cyclos_countries->mapWithKeys(function ($item) use ($countryCodeMap) {
|
||||
return [$item->member_id => $countryCodeMap[$item->possible_value_id] ?? null];
|
||||
});
|
||||
$remappedCities = $cyclos_cities->mapWithKeys(function ($item) use ($cityCodeMap) {
|
||||
return [$item->member_id => $cityCodeMap[$item->possible_value_id] ?? null];
|
||||
});
|
||||
|
||||
$recordUpdateCount = 0;
|
||||
$syncedDataCount = 0;
|
||||
|
||||
foreach ($remappedCountries as $memberId => $countryId) {
|
||||
$cityId = $remappedCities[$memberId] ?? null;
|
||||
if ($countryId !== null || $cityId !== null) {
|
||||
$entity = DB::table("{$destinationDb}." . strtolower($modelClass) . "s")
|
||||
->where('cyclos_id', $memberId)
|
||||
->first();
|
||||
|
||||
if ($entity) {
|
||||
$entityModel = $fullyQualifiedModelClass::find($entity->id);
|
||||
if ($entityModel) {
|
||||
$location = new Location();
|
||||
$location->name = 'Default location';
|
||||
$location->country_id = $countryId;
|
||||
$location->city_id = $cityId;
|
||||
$entityModel->locations()->save($location);
|
||||
$recordUpdateCount++;
|
||||
|
||||
// Sync all missing location data (divisions, etc.)
|
||||
try {
|
||||
$synced = $location->syncAllLocationData();
|
||||
if (!empty($synced)) {
|
||||
$syncedDataCount++;
|
||||
$this->info(" → Synced data for {$modelClass} ID {$entity->id}: " . implode(', ', $synced));
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->warn(" → Failed to sync location data for {$modelClass} ID {$entity->id}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->info("{$modelClass}: {$recordUpdateCount} locations created, {$syncedDataCount} had additional data synced");
|
||||
return $recordUpdateCount;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Tinker script to clean 'about' field containing only a single double quote
|
||||
*
|
||||
* Run this in Laravel Tinker:
|
||||
* php artisan tinker
|
||||
* Then paste this code
|
||||
*/
|
||||
protected function cleanAboutField()
|
||||
{
|
||||
echo "Starting cleanup of 'about' fields containing only double quotes...\n\n";
|
||||
$models = [
|
||||
'App\Models\User' => 'Users',
|
||||
'App\Models\Organization' => 'Organizations',
|
||||
'App\Models\Bank' => 'Banks',
|
||||
'App\Models\Admin' => 'Admins'
|
||||
];
|
||||
$totalUpdated = 0;
|
||||
foreach ($models as $modelClass => $tableName) {
|
||||
echo "Processing {$tableName}...\n";
|
||||
|
||||
// Check if the model class exists
|
||||
if (!class_exists($modelClass)) {
|
||||
echo " - Model {$modelClass} not found, skipping\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// Find records where about field contains only a double quote
|
||||
$records = $modelClass::where('about', '"')->get();
|
||||
|
||||
echo " - Found {$records->count()} records with about = '\"'\n";
|
||||
|
||||
if ($records->count() > 0) {
|
||||
// Update records to set about to null
|
||||
$updated = $modelClass::where('about', '"')->update(['about' => null]);
|
||||
|
||||
echo " - Updated {$updated} records\n";
|
||||
$totalUpdated += $updated;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
echo " - Error processing {$tableName}: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
}
|
||||
echo "Cleanup completed!\n";
|
||||
echo "Total records updated: {$totalUpdated}\n";
|
||||
echo "\nTo verify the cleanup, you can run:\n";
|
||||
foreach ($models as $modelClass => $tableName) {
|
||||
if (class_exists($modelClass)) {
|
||||
echo "{$modelClass}::where('about', '\"')->count(); // Should return 0\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user