Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alex-georgiou/b33de74985f8220efc59b111c6bceb55 to your computer and use it in GitHub Desktop.
Save alex-georgiou/b33de74985f8220efc59b111c6bceb55 to your computer and use it in GitHub Desktop.
Bitcoin and Altcoin Wallets balances migration script
<?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