Created
March 14, 2023 10:08
-
-
Save alex-georgiou/b33de74985f8220efc59b111c6bceb55 to your computer and use it in GitHub Desktop.
Bitcoin and Altcoin Wallets balances migration script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
* Bitcoin and Altcoin Wallets balances migration script | |
* | |
* This script will transfer balances of users from version 5.x of the plugin to version 6.x. Transaction histories will NOT be migrated. | |
* Version 5.x of the plugin uses the custom SQL table wp_wallets_txs to store transactions. | |
* Version 6.x of the plugin uses Custom Post Types of type wallets_tx to store transactions. | |
* | |
* This script will read user deposit addresses from wp_wallets_adds. | |
* Then it will delete any previously migrated addresses (address posts with tag `migrated`). | |
* Then, it will create a new deposit address CPT for each user deposit address. The created addresses will have the tag `migrated`. | |
* | |
* This script will then read a snapshot of user balances from wp_wallets_txs. | |
* Then it will delete any previously migrated balances (transaction posts with tag `migrated`). | |
* Then, it will loop over each combination of user and balance and create a new debit transaction. The created transactions will have the tags `migrated` and `migrated-balance`. | |
* | |
* | |
* @author Alex Georgiou <info@dashed-slug.net> | |
* @copyright 2023 | |
* @license GPL-2.0-or-later | |
* @version 1.0.0 | |
* @link https://github.com/dashed-slug/wallets/blob/master/docs/migration.md | |
*/ | |
/* INSTRUCTIONS - please read carefully!!! | |
* | |
* WARNING: DO NOT RUN THIS SCRIPT UNLESS YOU KNOW WHAT YOU ARE DOING. | |
* IF YOU HAVE QUESTIONS, CONTACT ME at info@dashed-slug.net | |
* | |
* 1. Enter the path to your WordPress installation into the `$wppath` variable below. Also set `$is_net_active` and `$current_blog_id` if appropriate. | |
* 2. Take a backup of your WordPress site. | |
* 3. Install and activate the wallets 6.0.0 plugin. | |
* 4. Create all the Currencies on your system. The currencies must have the correct ticker symbol. All ticker symbols must match those on the old system. | |
* 5. If you have already started a migration via the admin screens, go to Tools -> Migration Tool and choose "Pause/stop current migration". | |
* 6. Run this script with: `php ./wallets-migration-script.php`. The script will Revert any previously migrated data. | |
* 7. Please note the script's output. If you encounter any problems with migration, please email me the script's output. | |
*/ | |
// SET THIS TO THE PATH OF YOUR WORDPRESS INSTALLATION | |
$wppath = '/var/www/wordpress'; | |
// SET THIS TO TRUE, ONLY IF YOU HAVE A MULTISITE INSTALLATION AND THE PLUGIN WAS NETWORK-ACTIVATED | |
$is_net_active = false; | |
// SET THIS TO THE ID OF YOUR BLOG, ONLY IF YOU HAVE A MULTISITE INSTALLATION AND THE PLUGIN WAS ACTIVATED ON A SINGLE BLOG (NOT NETWORK-ACTIVATED) | |
$current_blog_id = 1; | |
// DO NOT EDIT BELOW THIS LINE | |
$wppath = rtrim( $wppath, '/ \n\r\t\v\x00' ); | |
if ( ! file_exists( "$wppath/wp-load.php" ) ) { | |
echo "Invalid WordPress dir!\n"; | |
die; | |
} | |
require_once "$wppath/wp-load.php"; | |
echo "Bitcoin and Altcoin Wallets script migration\n"; | |
echo "DB NAME: " . DB_NAME ."\n"; | |
echo "DB USER: " . DB_USER ."\n"; | |
echo "DB_HOST: " . DB_HOST ."\n"; | |
echo "ABSPATH: " . ABSPATH ."\n"; | |
if ( ! function_exists( 'DSWallets\get_first_currency_by_symbol' ) ) { | |
echo "Wallets plugin is not installed!\n"; | |
die; | |
} | |
require_once ABSPATH . WPINC . '/class-wp-site-query.php'; | |
require_once ABSPATH . WPINC . '/class-wp-network-query.php'; | |
require_once ABSPATH . WPINC . '/ms-blogs.php'; | |
if ( ! $is_net_active && $current_blog_id != 1 ) { | |
$switched_ok = switch_to_blog( absint( $current_blog_id ) ); | |
if ( $switched_ok ) { | |
echo "Switched to blog: $current_blog_id\n"; | |
} else { | |
echo "Could not switch to blog: $current_blog_id\n"; | |
die; | |
} | |
} else { | |
switch_to_blog( 1 ); | |
} | |
if( !function_exists('get_plugin_data') ){ | |
require_once ABSPATH . 'wp-admin/includes/plugin.php'; | |
} | |
$version = get_plugin_data( ABSPATH . 'wp-content/plugins/wallets/wallets.php' )['Version']; | |
echo "Version $version found\n"; | |
if ( version_compare( $version, '6.0.0' ) < 0 ) { | |
echo "You must install and activate wallets version 6.0.0 or later\n"; | |
die; | |
} | |
if ( ! is_plugin_active( 'wallets/wallets.php' ) ) { | |
echo "You must activate the wallets plugin\n"; | |
die; | |
} | |
if ( ! function_exists( 'get_option' ) ) { | |
echo ABSPATH . 'wp-includes/option.php'; | |
require_once ABSPATH . 'wp-includes/option.php'; | |
} | |
$state = get_option( 'wallets_migration_state' ); | |
echo "Migration state: " . json_encode( $state ) . "\n"; | |
if ( $state && 'none' != $state['type'] ) { | |
echo "Stopping current migration task and starting over. Will wait for 30 seconds now before proceeding...\n"; | |
delete_option( 'wallets_migration_state' ); | |
sleep( 30 ); // let any running crons finish to avoid race conditions | |
delete_option( 'wallets_migration_state' ); | |
echo "Current migration task stopped.\n"; | |
} | |
global $wpdb; | |
$prefix = is_multisite() ? $wpdb->base_prefix : $wpdb->prefix; | |
$table_name_txs = "{$prefix}wallets_txs"; | |
$table_name_adds = "{$prefix}wallets_adds"; | |
echo "Transactions table: $table_name_txs\n"; | |
echo "Addresses table: $table_name_adds\n"; | |
$wpdb->flush(); | |
$tickers_a = $wpdb->get_col( "SELECT DISTINCT symbol from $table_name_adds;" ); | |
$tickers_t = $wpdb->get_col( "SELECT DISTINCT symbol from $table_name_txs;" ); | |
$tickers = array_unique( array_merge( $tickers_a, $tickers_t ) ); | |
echo "Tickers found: " .implode( ', ', $tickers ) . "\n"; | |
$tickers_ok = true; | |
$currencies = []; | |
foreach ( $tickers as $ticker ) { | |
$currency = DSWallets\get_first_currency_by_symbol( $ticker ); | |
if ( ! $currency ) { | |
echo "You must first create the currency with ticker symbol: $ticker\n"; | |
$tickers_ok = false; | |
} | |
$currencies[ $ticker ] = $currency; | |
} | |
if ( ! $tickers_ok ) { | |
printf( | |
"To create the missing currencies, please visit: %s\n", | |
admin_url( 'post-new.php?post_type=wallets_currency' ) | |
); | |
die; | |
} | |
echo "All Currencies exist!\n"; | |
$state = [ | |
'type' => 'balances', // can be: transactions, balances, revert, none | |
'add_count' => false, // false means that addresses have not been counted yet | |
'add_count_ok' => 0, // count of successfully migrated addresses | |
'add_count_fail' => 0, // count of addresses that could not be migrated | |
'add_last_id' => false, // becomes false after address migration finishes | |
'tx_count' => false, // false means that txs have not been counted yet | |
'tx_count_ok' => 0, // count of successfully migrated transactions | |
'tx_count_fail' => 0, // count of transactions that could not be migrated | |
'tx_last_id' => false, // becomes false after transaction migration finishes | |
'bal_last_uid' => false, // becomes false after balances migration finishes | |
'bal_pending' => [], // tuples of [ [currency_id, amount], ... ] to be written asyncronously for curreny uid | |
'bal_count_ok' => 0, // count of successfully migrated balances | |
'bal_count_fail' => 0, // count of balances that could not be migrated | |
]; | |
$wpdb->flush(); | |
$aquery = $wpdb->prepare( | |
"SELECT | |
id, | |
a.account AS user_id, | |
a.symbol, | |
a.address, | |
a.extra | |
FROM | |
{$table_name_adds} a | |
WHERE | |
( %d OR a.blog_id = %d )", | |
$is_net_active ? 1 : 0, | |
$current_blog_id | |
); | |
$addresses = $wpdb->get_results( $aquery, OBJECT_K ); | |
$state['add_count'] = count( $addresses ); | |
$migrated_add_term = get_term_by( 'slug', 'migrated', 'wallets_address_tags', ARRAY_A ); | |
if ( ! is_array( $migrated_add_term ) ) { | |
$migrated_add_term = wp_insert_term( 'migrated', 'wallets_address_tags', [ 'slug' => 'migrated' ] ); | |
} | |
echo "Migrated address tag has term_id: ". $migrated_add_term['term_id'] . "\n"; | |
$existing_adds_query = new \WP_Query( [ | |
'post_type' => 'wallets_address', | |
'posts_per_page' => -1, | |
'tax_query' => [ | |
[ | |
'taxonomy' => 'wallets_address_tags', | |
'field' => 'term_id', | |
'terms' => $migrated_add_term['term_id'], | |
] | |
] | |
] ); | |
if ( $existing_adds_query->have_posts() ) { | |
echo "Deleting previously migrated addresses\n"; | |
while ( $existing_adds_query->have_posts() ) { | |
$existing_adds_query->the_post(); | |
printf( "Deleting previously migrated address with post_id:%d\n", get_the_ID() ); | |
wp_delete_post( get_the_ID(), true ); | |
} | |
} else { | |
echo "No existing previously migrated addresses were found\n"; | |
} | |
foreach ( $addresses as $a ) { | |
printf( | |
"Creating deposit address with u: %d, ticker: %s, address: %s, extra: %s\n", | |
$a->user_id, | |
$a->symbol, | |
$a->address, | |
$a->extra | |
); | |
$currency = $currencies[ $a->symbol ]; | |
if ( ! $currency ) { | |
$state['add_count_fail']++; | |
printf( "Could not find currency for %s\n", $a->symbol ); | |
continue; | |
} | |
$postarr = [ | |
'post_title' => sprintf( | |
'%s deposit address', | |
$a->name, | |
), | |
'post_type' => 'wallets_address', | |
'post_status' => 'publish', | |
'meta_input' => [ | |
'wallets_user' => $a->user_id, | |
'wallets_address' => $a->address, | |
'wallets_extra' => $a->extra, | |
'wallets_type' => 'deposit', | |
'wallets_currency_id' => $currency->post_id, | |
], | |
]; | |
$post_id = wp_insert_post( $postarr ); | |
if ( $post_id instanceof \WP_Error ) { | |
printf( "Could not create deposit address %s for currency %s and user %d\n", $a->address, $currency->name, $a->user_id ); | |
$state['add_count_fail']++; | |
} elseif ( $post_id ) { | |
$state['add_count_ok']++; | |
printf( "Created deposit address %s for currency %s and user %d\n", $a->address, $currency->name, $a->user_id ); | |
$result = wp_set_object_terms( $post_id, [ $migrated_add_term['term_id'] ], 'wallets_address_tags' ); | |
if ( $result instanceof \WP_Error ) { | |
printf( | |
"Could not mark address with ID:%d and post_id:%d as migrated because: %s\n", | |
$a->id, | |
$post_id, | |
$result->get_error_message() | |
); | |
} | |
} | |
} | |
$wpdb->flush(); | |
$bquery = $wpdb->prepare( | |
"SELECT | |
t.id, | |
t.account AS user_id, | |
t.symbol AS symbol, | |
SUM( IF( t.amount > 0, t.amount - t.fee, t.amount ) ) AS balance | |
FROM | |
{$table_name_txs} t | |
WHERE | |
t.status = 'done' | |
AND ( %d OR t.blog_id = %d ) | |
GROUP BY | |
t.symbol, | |
t.account | |
", | |
$is_net_active ? 1 : 0, | |
$current_blog_id | |
); | |
$balances = $wpdb->get_results( $bquery, OBJECT_K ); | |
$migrated_tx_term = get_term_by( 'slug', 'migrated', 'wallets_tx_tags', ARRAY_A ); | |
if ( ! is_array( $migrated_tx_term ) ) { | |
$migrated_tx_term = wp_insert_term( 'migrated', 'wallets_tx_tags', [ 'slug' => 'migrated' ] ); | |
} | |
echo "Migrated transaction tag has term_id: ". $migrated_tx_term['term_id'] . "\n"; | |
$migrated_balance_tx_term = get_term_by( 'slug', 'migrated-balance', 'wallets_tx_tags', ARRAY_A ); | |
if ( ! is_array( $migrated_balance_tx_term ) ) { | |
$migrated_balance_tx_term = wp_insert_term( 'migrated-balance', 'wallets_tx_tags', [ 'slug' => 'migrated-balance' ] ); | |
} | |
echo "Migrated balance transaction tag has term_id: ". $migrated_balance_tx_term['term_id'] . "\n"; | |
$existing_bals_query = new \WP_Query( [ | |
'post_type' => 'wallets_tx', | |
'posts_per_page' => -1, | |
'tax_query' => [ | |
[ | |
'taxonomy' => 'wallets_tx_tags', | |
'field' => 'term_id', | |
'terms' => $migrated_tx_term['term_id'], | |
] | |
] | |
] ); | |
if ( $existing_bals_query->have_posts() ) { | |
echo "Deleting previously migrated balances or transactions\n"; | |
while ( $existing_bals_query->have_posts() ) { | |
$existing_bals_query->the_post(); | |
printf( "Deleting previously migrated transaction with post_id:%d\n", get_the_ID() ); | |
wp_delete_post( get_the_ID(), true ); | |
} | |
} else { | |
echo "No existing previously migrated transactions and balances were found\n"; | |
} | |
foreach ( $balances as $b ) { | |
printf( | |
"Creating balance with u: %d, ticker: %s, amount: %f\n", | |
$b->user_id, | |
$b->symbol, | |
$b->balance | |
); | |
$currency = $currencies[ $b->symbol ]; | |
if ( ! $currency ) { | |
$state['bal_count_fail']++; | |
printf( "Could not find currency for %s\n", $b->symbol ); | |
continue; | |
} | |
$postarr = [ | |
'post_title' => sprintf( 'Migrated %s user balance', $b->symbol ), | |
'post_status' => 'publish', | |
'post_type' => 'wallets_tx', | |
'meta_input' => [ | |
'wallets_user' => $b->user_id, | |
'wallets_category' => 'move', | |
'wallets_currency_id' => $currency->post_id, | |
'wallets_amount' => absint( round( $b->balance * 10 ** $currency->decimals ) ), | |
'wallets_timestamp' => time(), | |
], | |
]; | |
remove_action( 'transition_post_status', [ 'DSWallets\Transaction', 'status_transition'], 10 ); //don't send emails | |
$post_id = wp_insert_post( $postarr ); | |
if ( $post_id instanceof \WP_Error ) { | |
printf( "Could not create balance transaction for currency %s and user %d with value %f\n", $currency->name, $b->user_id, $b->balance ); | |
$state['bal_count_fail']++; | |
} elseif ( $post_id ) { | |
$state['bal_count_ok']++; | |
printf( "Created balance transaction for currency %s and user %d with value %f\n", $currency->name, $b->user_id, $b ->balance ); | |
$result = wp_set_object_terms( $post_id, [ $migrated_tx_term['term_id'], $migrated_balance_tx_term['term_id'] ], 'wallets_tx_tags' ); | |
if ( $result instanceof \WP_Error ) { | |
printf( | |
"Could not mark balance transaction with ID:%d and post_id:%d as migrated because: %s\n", | |
$a->id, | |
$post_id, | |
$result->get_error_message() | |
); | |
} | |
} | |
} | |
printf( | |
"%d addresses out of %d were migrated\n", | |
$state['add_count_ok'], | |
$state['add_count'] | |
); | |
printf( | |
"%d addresses out of %d were NOT migrated\n", | |
$state['add_count_fail'], | |
$state['add_count'] | |
); | |
printf( | |
"%d balances out of %d were migrated\n", | |
$state['bal_count_ok'], | |
count( $balances ) | |
); | |
printf( | |
"%d balances out of %d were NOT migrated\n", | |
$state['bal_count_fail'], | |
count( $balances ) | |
); | |
$state['type'] = 'none'; | |
// Finally we mark the migration as done so that the plugin will not attempt it again. | |
update_option( 'wallets_migration_state', $state ); | |
echo "Marked migration as finished (option: wallets_migration_state)\n"; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment