Plugin Directory

Changeset 3089855


Ignore:
Timestamp:
05/21/2024 05:22:41 AM (22 months ago)
Author:
dashedslug
Message:

Adding version 6.2.6 of wallets

Location:
wallets/trunk
Files:
20 added
20 deleted
19 edited

Legend:

Unmodified
Added
Removed
  • wallets/trunk/adapters/class-bank-fiat-adapter.php

    r3070693 r3089855  
    158158
    159159    public function get_wallet_version(): string {
    160         return '6.2.5';
     160        return '6.2.6';
    161161    }
    162162
     
    683683                    get_asset_path( 'wallets-admin-deposit-tool' ),
    684684                    [ 'jquery' ],
    685                     '6.2.5',
     685                    '6.2.6',
    686686                    true
    687687                );
     
    12861286                                    $query = new \WP_Query( $query_args );
    12871287
    1288                                     $this->items = load_transactions( $query->posts );
     1288                                    $this->items = Transaction::load_many( $query->posts );
    12891289
    12901290                                    $this->set_pagination_args(
     
    18411841                                    $query = new \WP_Query( $query_args );
    18421842
    1843                                     $this->items = load_transactions( $query->posts );
     1843                                    $this->items = Transaction::load_many( $query->posts );
    18441844
    18451845                                    $this->set_pagination_args(
  • wallets/trunk/adapters/class-bitcoin-core-like-wallet-adapter.php

    r3070693 r3089855  
    738738                [
    739739                    'timeout'     => absint( get_ds_option( 'wallets_http_timeout', 5 ) ),
    740                     'user-agent'  => 'Bitcoin and Altcoin Wallets version 6.2.5',
     740                    'user-agent'  => 'Bitcoin and Altcoin Wallets version 6.2.6',
    741741                    'headers'     => [
    742742                        'Accept-Encoding: gzip',
  • wallets/trunk/admin/assets.php

    r3070693 r3089855  
    2222                get_asset_path( 'wallets-admin', 'style' ),
    2323                [],
    24                 '6.2.5'
     24                '6.2.6'
    2525            );
    2626
     
    5252                get_asset_path( 'wallets-admin-menu-item' ),
    5353                [ 'jquery' ],
    54                 '6.2.5',
     54                '6.2.6',
    5555                true
    5656            );
     
    6060                get_asset_path( 'wallets-admin-cs-tool' ),
    6161                [ 'jquery-qrcode' ],
    62                 '6.2.5',
     62                '6.2.6',
    6363                true
    6464            );
     
    7575                get_asset_path( 'wallets-admin-capabilities' ),
    7676                [ 'jquery-ui-tabs' ],
    77                 '6.2.5',
     77                '6.2.6',
    7878                true
    7979            );
     
    8383                get_asset_path( 'wallets-admin-dashboard' ),
    8484                [ 'jquery-ui-tabs', 'jqcloud' ],
    85                 '6.2.5',
     85                '6.2.6',
    8686                true
    8787            );
     
    9191                get_asset_path( 'wallets-admin-docs' ),
    9292                [ 'jquery' ],
    93                 '6.2.5',
     93                '6.2.6',
    9494                true
    9595            );
     
    9999                get_asset_path( 'wallets-admin-editor' ),
    100100                [ 'suggest' ],
    101                 '6.2.5',
     101                '6.2.6',
    102102                true
    103103            );
  • wallets/trunk/admin/dashboard.php

    r3070693 r3089855  
    349349        global $wpdb;
    350350
    351         $debug_data[ (string) __( 'Plugin version', 'wallets' ) ]         = '6.2.5';
    352         $debug_data[ (string) __( 'Git SHA', 'wallets' ) ]                = 'b66f30e0';
     351        $debug_data[ (string) __( 'Plugin version', 'wallets' ) ]         = '6.2.6';
     352        $debug_data[ (string) __( 'Git SHA', 'wallets' ) ]                = 'c1bedcf0';
    353353        $debug_data[ (string) __( 'Web Server', 'wallets' ) ]             = $_SERVER['SERVER_SOFTWARE'];
    354354        $debug_data[ (string) __( 'PHP version', 'wallets' ) ]            = PHP_VERSION;
  • wallets/trunk/admin/settings.php

    r2941101 r3089855  
    3030const DEFAULT_RATES_VS = [ 'btc', 'usd' ];
    3131const DEFAULT_CRON_EMAILS_MAX_BATCH_SIZE = 8;
     32const DEFAULT_DISABLE_CACHE = false;
    3233const DEFAULT_TRANSIENTS_BROKEN = false;
    3334const DEFAULT_FIAT_FIXERIO_CURRENCIES = [ 'USD' ];
     
    5556    add_ds_option( 'wallets_http_tor_port',                 get_option( 'wallets_rates_tor_port',    DEFAULT_HTTP_TOR_PORT ) );
    5657    add_ds_option( 'wallets_rates_vs',                      DEFAULT_RATES_VS );
     58    add_ds_option( 'wallets_disable_cache',                 DEFAULT_DISABLE_CACHE );
    5759    add_ds_option( 'wallets_transients_broken',             DEFAULT_TRANSIENTS_BROKEN );
    5860    add_ds_option( 'wallets_fiat_fixerio_key',              get_ds_option( 'wallets_rates_fixer_key', '' ) );
     
    252254
    253255            add_settings_field(
     256                'wallets_disable_cache',
     257                sprintf( (string) __( '%s Disable built-in object cache (debug)', 'wallets' ), '🗇' ),
     258                __NAMESPACE__ . '\checkbox_cb',
     259                "wallets_settings_{$tab}_page",
     260                "wallets_{$tab}_section",
     261                [
     262                    'label_for' => 'wallets_disable_cache',
     263                    'description' => __(
     264                        'The plugin speeds up DB reads of its Wallets, Currencies, Transactions and Addresses into its built-in object cache. ' .
     265                        'If this uses up too much memory and causes the plugin to crash, you can disable the cache here and see if it helps. ' .
     266                        'Otherwise, it\'s best to leave it on.',
     267                        'wallets'
     268                    ),
     269                ]
     270            );
     271
     272            register_setting(
     273                "wallets_{$tab}_section",
     274                'wallets_disable_cache'
     275            );
     276
     277            add_settings_field(
    254278                'wallets_transients_broken',
    255279                sprintf( (string) __( '%s Transients broken (debug)', 'wallets' ), '🐛' ),
  • wallets/trunk/docs/settings.md

    r2941101 r3089855  
    2323Effectively this controls up to how many deposit addresses a user can have. A user would typically create a deposit address via the `[wallets_deposit]` shortcode. The user clicks the "Get new address" button, which triggers a WP-REST API POST request to `dswallets/v1/users/(user_id)/currencies/(currency_id)/addresses`. If the deposit addresses of the user have reached this max count, the request returns with the HTTP 420 Enhance Your Calm status code and no address is created.
    2424
     25### Disable built-in object cache (debug)
     26
     27|     |     |
     28| --- | --- |
     29| *Option* | `wallets_disable_cache` |
     30| *Default* | `''` (off) |
     31| *Description* | *The plugin speeds up DB reads of its Wallets, Currencies, Transactions and Addresses into its built-in object cache. If this uses up too much memory and causes the plugin to crash, you can disable the cache here and see if it helps. Otherwise, it's best to leave it on.* |
     32
     33This is a built-in cache for the plugin's CPTs: Wallets, Currencies, Transactions and Addresses. Since `6.0.0`, these are loaded as objects. Since `6.2.6`, these are loaded in batches where possible, to minimize SQL queries to the DB. They are also cached. This won't be a problem unless if the plugin loads too many objects in memory per request. This shouldn't be the case, since all cron jobs process data in small batches. But if you notice the plugin crashing due to insufficient memory, AND you are unable to increase the memory available to WordPress, then disabling this cache MAY help.
    2534
    2635### Transients broken (debug)
  • wallets/trunk/frontend/assets.php

    r3070693 r3089855  
    2323            get_asset_path( 'wallets', 'style' ),
    2424            [],
    25             '6.2.5'
     25            '6.2.6'
    2626        );
    2727
     
    8686            get_asset_path( 'jsqrcode' ),
    8787            [ 'jquery' ],
    88             '6.2.5',
     88            '6.2.6',
    8989            true
    9090        );
     
    128128            get_asset_path( 'wallets-front' ),
    129129            [ 'knockout', 'jquery', 'style-scoped', 'sprintf.js' ],
    130             '6.2.5',
     130            '6.2.6',
    131131            true
    132132        );
  • wallets/trunk/helpers/addresses.php

    r2933770 r3089855  
    9696    $address = null;
    9797    try {
    98         $address = Address::load( $post_ids[ 0 ] );
     98        $address = Address::load( array_shift( $post_ids ) );
    9999
    100100    } catch ( \Exception $e ) {
     
    171171    $post_ids = array_values( $query->posts );
    172172
    173     $addresses = array_map(
    174         function( $post_id ) {
    175             try {
    176 
    177                 return Address::load( $post_id );
    178 
    179             } catch ( \Exception $e ) {
    180                 error_log(
    181                     sprintf(
    182                         'get_all_addresses_for_user_id: Could not instantiate address %d due to: %s',
    183                         $post_id,
    184                         $e->getMessage()
    185                     )
    186                 );
    187             }
    188             return null;
    189         },
    190         $post_ids
    191     );
     173    $addresses = Address::load_many( $post_ids );
     174
    192175    maybe_restore_blog();
    193176
     
    305288    if ( $post_ids ) {
    306289
    307         $found = Address::load( $post_ids[ 0 ] );
     290        $found = Address::load( array_shift( $post_ids ) );
    308291
    309292        if (
     
    386369    if ( $post_ids ) {
    387370        try {
    388             $address = Address::load( $post_ids[ 0 ] );
     371            $address = Address::load( array_shift( $post_ids ) );
    389372        } catch ( \Exception $e ) {
    390373            error_log(
     
    453436    $post_ids = array_values( $query->posts );
    454437
    455     $addresses = [];
    456 
    457     foreach ( $post_ids as $post_id ) {
    458         try {
    459             $address = Address::load( $post_id );
    460         } catch ( \Exception $e ) {
    461             error_log(
    462                 sprintf(
    463                     '%s: Could not instantiate address %d due to: %s',
    464                     __FUNCTION__,
    465                     $post_id,
    466                     $e->getMessage()
    467                 )
    468             );
    469         }
    470 
    471         if ( ! isset( $addresses[ $address->currency->post_id ] ) ) {
    472             $addresses[ $address->currency->post_id ] = $address;
     438    $addresses = Address::load_many( $post_ids );
     439
     440    $last_addresses = [];
     441
     442    foreach ( $addresses as $address ) {
     443
     444        if ( ! isset( $last_addresses[ $address->currency->post_id ] ) ) {
     445            $last_addresses[ $address->currency->post_id ] = $address;
    473446        } else {
    474             if ( get_post_time( 'U', true, $address->post_id ) > get_post_time( 'U', true, $addresses[ $address->currency->post_id ]->post_id ) ) {
    475                 $addresses[ $address->currency->post_id ] = $address;
     447            if ( get_post_time( 'U', true, $address->post_id ) > get_post_time( 'U', true, $last_addresses[ $address->currency->post_id ]->post_id ) ) {
     448                $last_addresses[ $address->currency->post_id ] = $address;
    476449            }
    477450        }
     
    480453    maybe_restore_blog();
    481454
    482     return array_values( $addresses );
     455    return array_values( $last_addresses );
    483456}
    484457
     
    539512    $post_ids = array_values( $query->posts );
    540513
    541     $addresses = array_map(
    542         function( int $post_id ): ?Address {
    543             try {
    544 
    545                 return Address::load( $post_id );
    546 
    547             } catch ( \Exception $e ) {
    548                 error_log(
    549                     sprintf(
    550                         'get_all_addresses_for_user_id_and_currency_id: Could not instantiate address %d due to: %s',
    551                         $post_id,
    552                         $e->getMessage()
    553                     )
    554                 );
    555             }
    556             return null;
    557         },
    558         $post_ids
    559     );
    560 
    561     maybe_restore_blog();
    562 
    563     return array_filter( $addresses );
     514    $addresses = Address::load_many( $post_ids );
     515
     516    maybe_restore_blog();
     517
     518    return $addresses;
    564519}
    565520
  • wallets/trunk/helpers/currencies.php

    r2887566 r3089855  
    1717 *
    1818 * If a currency cannot be loaded due to Currency::load() throwing (i.e. bad DB data),
    19  * then the error will be logged and the rest of the currencies will be loaded.
     19 * then it is skipped and the rest of the currencies will be loaded.
    2020 *
    2121 * @param array $post_ids The array of integer post_ids
    2222 * @return array The array of Currency objects.
     23 * @deprecated since 6.2.6
     24 * @since 6.2.6 Deprecated in favor of the Currency::load_many() factory.
    2325 */
    2426function load_currencies( array $post_ids ): array {
    25     return
    26         array_values(
    27             array_filter(
    28                 array_map(
    29                     function( int $post_id ) {
    30                         try {
    31                             return Currency::load( $post_id );
    32 
    33                         } catch ( \Exception $e ) {
    34                             error_log(
    35                                 sprintf(
    36                                     'load_currencies: Could not instantiate currency %d due to: %s',
    37                                     $post_id,
    38                                     $e->getMessage()
    39                                 )
    40                             );
    41                         }
    42                         return null;
    43                     },
    44                     $post_ids
    45                 )
    46             )
    47         );
     27    _doing_it_wrong(
     28        __FUNCTION__,
     29        'Calling load_currencies( $post_ids ) is deprecated and may be removed in a future version. Instead, use the new currency factory: Currency::load_many( $post_ids )',
     30        '6.2.6'
     31    );
     32    return Currency::load_many( $post_ids );
    4833}
    4934
     
    152137 */
    153138function get_currencies_for_wallet( Wallet $wallet ): array {
    154     return load_currencies( get_currency_ids_for_wallet( $wallet ) );
     139    return Currency::load_many( get_currency_ids_for_wallet( $wallet ) );
    155140}
    156141
     
    164149 */
    165150function get_currency_symbols_names_for_wallet( Wallet $wallet ): array {
     151    maybe_switch_blog();
     152
     153    global $wpdb;
     154
     155    $query = $wpdb->prepare(
     156        "
     157        SELECT
     158            p.post_title AS name,
     159            pm_symbol.meta_value AS symbol
     160        FROM
     161            {$wpdb->posts} p
     162        JOIN
     163            {$wpdb->postmeta} AS pm_wallet_id ON ( p.ID = pm_wallet_id.post_id AND pm_wallet_id.meta_key = 'wallets_wallet_id' AND pm_wallet_id.meta_value = %d )
     164        JOIN
     165            {$wpdb->postmeta} as pm_symbol ON ( p.ID = pm_symbol.post_id AND pm_symbol.meta_key = 'wallets_symbol' )
     166        WHERE
     167            p.post_status = 'publish'
     168            AND p.post_type = 'wallets_currency'
     169        ",
     170        $wallet->post_id
     171    );
     172
     173    $data = $wpdb->get_results( $query, OBJECT );
     174
     175    $assoc = [];
     176    foreach ( $data as $row ) {
     177        $assoc[ $row->symbol ] = $row->name;
     178    }
     179
     180    maybe_restore_blog();
     181
     182    return $assoc;
     183}
     184
     185/**
     186 * Retrieve all published currencies.
     187 *
     188 * Returns all currencies that are not drafts, pending review, trashed, etc.
     189 * Some of these currencies may not be currently attached to wallets.
     190 *
     191 * @return Currency[] The currencies found on the system.
     192 */
     193function get_all_currencies(): array {
    166194    maybe_switch_blog();
    167195
     
    171199        'post_status' => 'publish',
    172200        'nopaging'    => true,
    173         'meta_query'  => [
    174             [
    175                 'key'   => 'wallets_wallet_id',
    176                 'value' => $wallet->post_id,
    177             ],
    178         ],
    179     ];
    180 
    181     $query = new \WP_Query( $query_args );
    182 
    183     $post_ids = array_values( $query->posts );
    184 
    185     $assoc = [];
    186 
    187     foreach ( $post_ids as $post_id ) {
    188         $symbol = get_post_meta( $post_id, 'wallets_symbol', true );
    189         if ( $symbol ) {
    190             $post = get_post( $post_id );
    191             if ( $post ) {
    192                 $assoc[ $symbol ] = $post->post_title;
    193             }
    194         }
    195     }
    196 
    197     maybe_restore_blog();
    198 
    199     return $assoc;
    200 }
    201 
    202 /**
    203  * Retrieve all published currencies.
    204  *
    205  * Returns all currencies that are not drafts, pending review, trashed, etc.
    206  * Some of these currencies may not be currently attached to wallets.
    207  *
    208  * @return Currency[] The currencies found on the system.
    209  */
    210 function get_all_currencies(): array {
    211     maybe_switch_blog();
    212 
    213     $query_args = [
    214         'fields'      => 'ids',
    215         'post_type'   => 'wallets_currency',
    216         'post_status' => 'publish',
    217         'nopaging'    => true,
    218     ];
    219 
    220     $query = new \WP_Query( $query_args );
    221 
    222 
    223     $currencies = array_map(
    224         function( $post_id ) {
    225             try {
    226 
    227                 return Currency::load( $post_id );
    228 
    229             } catch ( \Exception $e ) {
    230                 error_log(
    231                     sprintf(
    232                         'get_all_currencies: Could not instantiate currency %d due to: %s',
    233                         $post_id,
    234                         $e->getMessage()
    235                     )
    236                 );
    237             }
    238             return null;
    239         },
    240         array_values( $query->posts )
    241     );
    242 
    243     maybe_restore_blog();
    244 
    245     return array_values( array_filter( $currencies ) );
     201    ];
     202
     203    $query = new \WP_Query( $query_args );
     204
     205    $post_ids = $query->posts;
     206
     207    $currencies = Currency::load_many( $post_ids );
     208
     209    maybe_restore_blog();
     210
     211    return $currencies;
    246212}
    247213
     
    282248    $query = new \WP_Query( $query_args );
    283249
    284     $currencies = array_map(
    285         function( $post_id ) {
    286             try {
    287                 return Currency::load( $post_id );
    288             } catch ( \Exception $e ) {
    289                 error_log(
    290                     sprintf(
    291                         'get_all_enabled_currencies: Could not instantiate currency %d due to: %s',
    292                         $post_id,
    293                         $e->getMessage()
    294                     )
    295                 );
    296             }
    297             return null;
    298         },
    299         array_values( $query->posts )
    300     );
     250    $currencies = Currency::load_many( $query->posts );
    301251
    302252    if ( $with_wallet ) {
     
    312262    maybe_restore_blog();
    313263
    314     return array_values( array_filter( $currencies ) );
     264    return $currencies;
    315265}
    316266
     
    426376    $post_ids = array_values( $query->posts );
    427377
    428     $currencies = [];
    429 
    430     foreach ( $post_ids as $post_id ) {
    431         try {
    432             $currencies[] = Currency::load( $post_id );
    433         } catch ( \Exception $e ) {
    434             continue;
    435         }
    436     }
     378    $currencies = Currency::load_many( $post_ids );
    437379
    438380    maybe_restore_blog();
     
    546488function get_all_fiat_currencies(): array {
    547489    return
    548         load_currencies(
     490        Currency::load_many(
    549491            get_currency_ids( 'fiat' )
    550492        );
     
    562504function get_all_cryptocurrencies(): array {
    563505    return
    564         load_currencies(
     506        Currency::load_many(
    565507            array_diff(
    566508                get_currency_ids(),
     
    738680    $post_ids = array_values( $query->posts );
    739681
    740     $currencies = load_currencies( $post_ids );
     682    $currencies = Currency::load_many( $post_ids );
    741683
    742684    maybe_restore_blog();
  • wallets/trunk/helpers/transactions.php

    r2887566 r3089855  
    1616 *
    1717 * If a transaction cannot be loaded due to Transaction::load() throwing (i.e. bad DB data),
    18  * then the error will be logged and the rest of the transactions will be loaded.
     18 * then it is skipped and the rest of the transactions will be loaded.
    1919 *
    2020 * @param array $post_ids The array of integer post_ids
    2121 * @return array The array of Transaction objects.
     22 *
     23 * @deprecated since 6.2.6
     24 * @since 6.2.6 Deprecated in favor of the Currency::load_many() factory.
    2225 */
    2326function load_transactions( array $post_ids ): array {
    24     return
    25         array_values(
    26             array_filter(
    27                 array_map(
    28                     function( int $post_id ) {
    29                         try {
    30                             return Transaction::load( $post_id );
    31 
    32                         } catch ( \Exception $e ) {
    33                             error_log(
    34                                 sprintf(
    35                                     'load_transactions: Could not instantiate transaction %d due to: %s',
    36                                     $post_id,
    37                                     $e->getMessage()
    38                                 )
    39                             );
    40                         }
    41                         return null;
    42                     },
    43                     $post_ids
    44                 )
    45             )
    46         );
    47 }
     27    _doing_it_wrong(
     28        __FUNCTION__,
     29        'Calling load_transactions( $post_ids ) is deprecated and may be removed in a future version. Instead, use the new currency factory: Transaction::load_many( $post_ids )',
     30        '6.2.6'
     31    );
     32    return Transaction::load_many( $post_ids );
     33}
     34
    4835
    4936/**
     
    223210    $query = new \WP_Query( $query_args );
    224211
    225     $transactions = array_map(
    226         function( $post_id ) {
    227             try {
    228                 return Transaction::load( $post_id );
    229 
    230             } catch ( \Exception $e ) {
    231                 error_log(
    232                     sprintf(
    233                         'get_pending_transactions_by_currency_and_category: Could not instantiate transaction with ID %d due to: %s',
    234                         $post_id,
    235                         $e->getMessage()
    236                     )
    237                 );
    238             }
    239             return null;
    240         },
    241         array_values( $query->posts )
    242     );
    243 
    244     maybe_restore_blog();
    245 
    246     return array_values( array_filter( $transactions ) );
     212    $transactions = Transaction::load_many( $query->posts );
     213
     214    maybe_restore_blog();
     215
     216    return $transactions;
    247217}
    248218
     
    337307    $query = new \WP_Query( $query_args );
    338308
    339     $transactions = array_map(
    340         function( $post_id ) {
    341             try {
    342 
    343                 return Transaction::load( $post_id );
    344 
    345             } catch ( \Exception $e ) {
    346                 error_log(
    347                     sprintf(
    348                         'get_transactions: Could not instantiate transaction %d due to: %s',
    349                         $post_id,
    350                         $e->getMessage()
    351                     )
    352                 );
    353             }
    354             return null;
    355         },
    356         array_values( $query->posts )
    357     );
     309    $transactions = Transaction::load_many( $query->posts );
    358310
    359311    maybe_restore_blog();
     
    389341    $query = new \WP_Query( $query_args );
    390342
    391     $transactions = array_map(
    392         function( $post_id ) {
    393             try {
    394 
    395                 return Transaction::load( $post_id );
    396 
    397             } catch ( \Exception $e ) {
    398                 error_log(
    399                     sprintf(
    400                         'get_transactions_for_address: Could not instantiate transaction %d due to: %s',
    401                         $post_id,
    402                         $e->getMessage()
    403                     )
    404                 );
    405             }
    406             return null;
    407         },
    408         array_values( $query->posts )
    409     );
    410 
    411     maybe_restore_blog();
    412 
    413     return array_values( array_filter( $transactions ) );
     343    $transactions = Transaction::load_many( $query->posts );
     344
     345    maybe_restore_blog();
     346
     347    return $transactions;
    414348}
    415349
     
    469403    $post_ids = array_values( $query->posts );
    470404
    471     $txs = load_transactions( $post_ids );
    472 
    473     maybe_restore_blog();
    474 
    475     return array_values( $txs );
     405    $transactions = Transaction::load_many( $post_ids );
     406
     407    maybe_restore_blog();
     408
     409    return $transactions;
    476410}
    477411
     
    527461    $post_ids = array_values( $query->posts );
    528462
    529     $txs = load_transactions( $post_ids );
    530 
    531     maybe_restore_blog();
    532 
    533     return array_values( $txs );
     463    $txs = Transaction::load_many( $post_ids );
     464
     465    maybe_restore_blog();
     466
     467    return $txs;
    534468}
    535469
     
    614548    $post_ids = array_values( $query->posts );
    615549
    616     $txs = load_transactions( $post_ids );
    617 
    618     maybe_restore_blog();
    619 
    620     return array_values( $txs );
     550    $txs = Transaction::load_many( $post_ids );
     551
     552    maybe_restore_blog();
     553
     554    return $txs;
    621555}
    622556
     
    655589    $post_ids = array_values( $query->posts );
    656590
    657     $txs = load_transactions( $post_ids );
     591    $txs = Transaction::load_many( $post_ids );
    658592
    659593    if ( count( $txs ) > 2 ) {
     
    746680    $post_ids = array_values( $query->posts );
    747681
    748     $txs = load_transactions( $post_ids );
     682    $txs = Transaction::load_many( $post_ids );
    749683
    750684    $tx = null;
     
    795729    $post_ids = array_values( $query->posts );
    796730
    797     $txs = load_transactions( $post_ids );
     731    $txs = Transaction::load_many( $post_ids );
    798732
    799733    maybe_restore_blog();
     
    884818
    885819    if ( $post_ids ) {
    886         $found = Transaction::load( $post_ids[ 0 ] );
     820        $found = Transaction::load( array_shift( $post_ids ) );
    887821        return $found;
    888822    }
  • wallets/trunk/helpers/users.php

    r2887566 r3089855  
    7979    $post_ids = array_values( $query->posts );
    8080
    81     $send_moves = load_transactions( $post_ids );
     81    $send_moves = Transaction::load_many( $post_ids );
    8282    foreach ( $send_moves as $send_move ) {
    8383        $other_tx = $send_move->get_other_tx();
  • wallets/trunk/helpers/wallets.php

    r2887566 r3089855  
    1818 *
    1919 * If a wallet cannot be loaded due to Wallet::load() throwing,
    20  * then the error will be logged and the rest of the wallets will be loaded.
     20 * then it is skipped and the rest of the wallets will be loaded.
    2121 *
    2222 * @param array $post_ids The array of integer post_ids
    2323 * @return array The array of Wallet objects.
     24 *
     25 * @deprecated since 6.2.6
     26 * @since 6.2.6 Deprecated in favor of the Currency::load_many() factory.
    2427 */
    2528function load_wallets( array $post_ids ): array {
    26     return array_filter(
    27         array_map(
    28             function( int $post_id ) {
    29                 try {
    30                     return Wallet::load( $post_id );
    31 
    32                 } catch ( \Exception $e ) {
    33                     error_log(
    34                         sprintf(
    35                             'load_wallets: Could not instantiate wallet %d due to: %s',
    36                             $post_id,
    37                             $e->getMessage()
    38                         )
    39                     );
    40                 }
    41                 return null;
    42             },
    43             $post_ids
    44         )
     29    _doing_it_wrong(
     30        __FUNCTION__,
     31        'Calling load_wallets( $post_ids ) is deprecated and may be removed in a future version. Instead, use the new currency factory: Wallet::load_many( $post_ids )',
     32        '6.2.6'
    4533    );
     34    return Wallet::load_many( $post_ids );
    4635}
    4736
     
    6756    $post_ids = array_values( $query->posts );
    6857
    69     $wallets = load_wallets( $post_ids );
     58    $wallets = Wallet::load_many( $post_ids );
    7059
    7160    maybe_restore_blog();
     
    143132    return $adapters;
    144133}
    145 
  • wallets/trunk/post-types/abstract-post-type.php

    r2940696 r3089855  
    2828    protected $post_id;
    2929
     30    protected static $object_cache = [];
     31
     32    /**
     33     * Loads Wallets, Currencies, Transactions and Addresses by their post_ids. Quickly.
     34     *
     35     * Any IDs not found are skipped silently.
     36     *
     37     * @param array $post_ids The post IDs corresponding to the objects to load.
     38     * @param ?string $post_type Optionally retrieve only posts of this type.
     39     *
     40     * @return Post_Type[] The instantiated objects.
     41     * @throws \Exception If DB access or instantiation fails.
     42     *
     43     * @since 6.2.6 Introduced.
     44     */
     45    public static function load_many( array $post_ids, ?string $post_type = null ): array {
     46
     47        global $wpdb;
     48
     49        $cache_hit_post_ids    = [];
     50        $cache_missed_post_ids = [];
     51
     52        foreach ( array_unique( $post_ids ) as $post_id ) {
     53
     54            if ( array_key_exists( $post_id, self::$object_cache ) ) {
     55                $cache_hit_post_ids[] = absint( $post_id );
     56            } else {
     57                $cache_missed_post_ids[] = absint( $post_id );
     58            }
     59
     60        }
     61
     62        if ( $cache_missed_post_ids ) {
     63
     64            maybe_switch_blog();
     65
     66            $cache_missed_post_ids_imploded = implode( ',', $cache_missed_post_ids );
     67
     68
     69            $wpdb->flush();
     70
     71            $query = "
     72                SELECT
     73                    ID,
     74                    post_title,
     75                    post_type,
     76                    post_status,
     77                    post_parent
     78                FROM
     79                    {$wpdb->posts} p
     80                WHERE
     81                    ID IN ( $cache_missed_post_ids_imploded )
     82                ";
     83
     84            if ( in_array( $post_type, [ 'wallets_wallet', 'wallets_address', 'wallets_tx', 'wallets_currency' ], true ) ) {
     85                $query .= 'AND post_type = "' . $post_type . '"';
     86            }
     87
     88            $posts = $wpdb->get_results( $query, OBJECT_K );
     89
     90            if ( false === $posts ) {
     91                throw new \Exception(
     92                    sprintf(
     93                        '%s: Failed getting posts with: %s',
     94                        __FUNCTION__,
     95                        $wpdb->last_error
     96                    )
     97                );
     98            }
     99
     100            $wpdb->flush();
     101
     102            $query = "
     103                SELECT
     104                    post_id,
     105                    meta_key,
     106                    meta_value
     107                FROM
     108                    {$wpdb->postmeta} p
     109                WHERE
     110                    post_id IN ( $cache_missed_post_ids_imploded )
     111                ";
     112
     113            $postmeta = $wpdb->get_results( $query, OBJECT );
     114
     115            if ( false === $postmeta ) {
     116                throw new \Exception(
     117                    sprintf(
     118                        '%s: Failed getting post meta with: %s',
     119                        __FUNCTION__,
     120                        $wpdb->last_error
     121                    )
     122                );
     123            }
     124
     125
     126            $wpdb->flush();
     127
     128            $query = "
     129                SELECT
     130                    tr.object_id object_id,
     131                    tt.taxonomy taxonomy,
     132                    t.slug slug
     133                FROM
     134                    {$wpdb->terms} t
     135
     136                JOIN
     137                    {$wpdb->term_relationships} tr ON t.term_id = tr.term_taxonomy_id
     138
     139                JOIN
     140                    {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
     141
     142                WHERE
     143                    tr.object_id IN ($cache_missed_post_ids_imploded) AND
     144                    tt.taxonomy IN ('wallets_tx_tags','wallets_currency_tags','wallets_address_tags')
     145                ";
     146
     147            $terms = $wpdb->get_results( $query, OBJECT );
     148
     149            if ( false === $terms ) {
     150                throw new \Exception(
     151                    sprintf(
     152                        '%s: Failed getting post terms with: %s',
     153                        __FUNCTION__,
     154                        $wpdb->last_error
     155                    )
     156                );
     157            }
     158
     159
     160            foreach ( $cache_missed_post_ids as $post_id ) {
     161
     162                if ( ! array_key_exists( $post_id, $posts ) ) {
     163                    // post not found, skip it
     164                    continue;
     165                }
     166
     167                try {
     168
     169                    $current_post      = $posts[ $post_id ];
     170
     171                    $current_postmeta  = [];
     172                    foreach ( $postmeta as $pm ) {
     173                        if ( $post_id == $pm->post_id && preg_match( '/^wallets_/', $pm->meta_key ) ) {
     174                            $current_postmeta[ $pm->meta_key ] = $pm->meta_value;
     175                        }
     176                    }
     177
     178                    $current_post_term_slugs = array_values(
     179                        array_map(
     180                            function( $t ) {
     181                                return $t->slug;
     182                            },
     183                            array_filter(
     184                                $terms,
     185                                function( $pm ) use ( $current_post ) {
     186                                    return $current_post->ID == $pm->object_id && "{$current_post->post_type}_tags" == $pm->taxonomy;
     187                                }
     188                            )
     189                        )
     190                    );
     191
     192                    switch ( $posts[ $post_id ]->post_type ) {
     193
     194                        case 'wallets_wallet':
     195
     196                            $wallet = Wallet::from_values(
     197                                $current_post->ID,
     198                                $current_post->post_title,
     199                                $current_post->post_status,
     200                                $current_postmeta
     201                            );
     202
     203                            // save to cache
     204                            self::$object_cache[ $post_id ] = $wallet;
     205
     206                            break;
     207
     208                        case 'wallets_currency':
     209
     210
     211                            $currency = Currency::from_values(
     212                                $current_post->ID,
     213                                $current_post->post_title,
     214                                $current_postmeta,
     215                                $current_post_term_slugs
     216                            );
     217
     218                            // save to cache
     219                            self::$object_cache[ $post_id ] = $currency;
     220
     221                            break;
     222
     223                        case 'wallets_tx':
     224
     225                            $tx = Transaction::from_values(
     226                                $current_post->ID,
     227                                $current_post->post_title,
     228                                $current_post->post_status,
     229                                $current_post->post_parent,
     230                                $current_postmeta,
     231                                $current_post_term_slugs
     232                            );
     233
     234                            // save to cache
     235                            self::$object_cache[ $post_id ] = $tx;
     236
     237                            break;
     238
     239                        case 'wallets_address':
     240
     241                            $address = Address::from_values(
     242                                $current_post->ID,
     243                                $current_post->post_title,
     244                                $current_postmeta,
     245                                $current_post_term_slugs
     246                            );
     247
     248                            // save to cache
     249                            self::$object_cache[ $post_id ] = $address;
     250
     251                            break;
     252
     253                    }
     254
     255                } catch ( \Exception $e ) {
     256
     257                    error_log(
     258                        sprintf(
     259                            '%s: Failed to instantiate object %d, due to: %s',
     260                            __FUNCTION__,
     261                            $post_id,
     262                            $e->getMessage()
     263                        )
     264                    );
     265
     266                    continue;
     267                }
     268
     269            }
     270
     271        }
     272
     273        maybe_restore_blog();
     274
     275        $result = array_values( array_intersect_key( self::$object_cache, array_flip( $post_ids ) ) );
     276
     277        if ( get_ds_option( 'wallets_disable_cache' ) ) {
     278            self::$object_cache = [];
     279        }
     280
     281        return $result;
     282    }
     283
    30284    /**
    31285     * Load a Wallet, Currency, Transaction or Address from its custom post entry.
  • wallets/trunk/post-types/class-address.php

    r2973854 r3089855  
    115115
    116116    /**
     117     * Array of tags.
     118     *
     119     * Tags correspond to term slugs in the wallets_address_tags taxonomy.
     120     *
     121     * @var ?string[]
     122     */
     123    private $tags = null;
     124
     125    /**
     126     * Factory to construct an address in one go from database values.
     127     *
     128     * @param int $post_id The ID of the post in the database
     129     * @param string $post_title The post's title to be used as address label
     130     * @param array $postmeta Key-value pairs
     131     * @param string[] $tags The slugs of the terms on taxonomy wallets_address_tags
     132     *
     133     * @return Address The constructed instance of the address object
     134     */
     135    public static function from_values( int $post_id, string $post_title, array $postmeta, array $tags ): Address {
     136
     137        $address = new self;
     138
     139        // populate fields
     140        $address->post_id  = $post_id;
     141        $address->address  = $postmeta['wallets_address'] ?? '';
     142        $address->extra    = $postmeta['wallets_extra'] ?? '';
     143        $address->type     = $postmeta['wallets_type'] ?? 'withdrawal';
     144        $address->label    = $post_title;
     145
     146        $currency_id = $postmeta['wallets_currency_id'] ?? null;
     147        if ( $currency_id ) {
     148            try {
     149                $address->currency = Currency::load( $currency_id ) ?? null;
     150            } catch ( \Exception $e ) {
     151                $address->currency = null;
     152            }
     153        }
     154
     155        $user_id = absint( $postmeta['wallets_user'] ?? null );
     156        if ( $user_id ) {
     157            $address->user = new \WP_User( $user_id );
     158        }
     159
     160        $address->tags = $tags;
     161
     162        return $address;
     163    }
     164
     165    /**
     166     * Retrieve many addresses by their post_ids.
     167     *
     168     * Any post_ids not found are skipped silently.
     169     *
     170     * @param int[] $post_ids The post IDs
     171     * @param ?string $unused Do not use. Ignored.
     172     *
     173     * @return Address[] The addresses
     174     * @throws \Exception If DB access or instantiation fails.
     175     *
     176     * @since 6.2.6 Introduced.
     177     */
     178    public static function load_many( array $post_ids, ?string $unused = null ): array {
     179        return parent::load_many( $post_ids, 'wallets_address' );
     180    }
     181
     182    /**
    117183     * Load an Address from its custom post entry.
    118184     *
     
    120186     * @see Post_Type::load()
    121187     * @return Address
     188     * @throws \Exception If not found or failed to instantiate.
    122189     */
    123190    public static function load( int $post_id ): Address {
    124         maybe_switch_blog();
    125 
    126         $post = get_post( $post_id );
    127 
    128         if ( ! $post ) {
    129             maybe_restore_blog();
    130             throw new \InvalidArgumentException( sprintf( "%s with %d does not exist!", __CLASS__, $post_id ) );
    131         }
    132 
    133         if ( 'wallets_address' != $post->post_type ) {
    134             maybe_restore_blog();
    135             throw new \InvalidArgumentException( "Post $post_id is not an address!" );
    136         }
    137 
    138         $address = new self;
    139         $address->post_id     = $post->ID;
    140         $address->address     = get_post_meta( $post->ID, 'wallets_address', true );
    141         $address->extra       = get_post_meta( $post->ID, 'wallets_extra', true );
    142         $address->type        = get_post_meta( $post->ID, 'wallets_type', true );
    143         $address->label       = $post->post_title;
    144 
    145         $currency_id = get_post_meta( $post->ID, 'wallets_currency_id', true );
    146         if ( $currency_id ) {
    147             try {
    148                 $address->currency = Currency::load( $currency_id );
    149             } catch ( \Exception $e ) {
    150                 $address->currency = null;
    151             }
    152         }
    153 
    154         $user_id = absint( get_post_meta( $post->ID, 'wallets_user', true ) );
    155         if ( $user_id ) {
    156             $address->user = new \WP_User( $user_id );
    157         }
    158 
    159         maybe_restore_blog();
    160 
    161         return $address;
     191        $one = self::load_many( [ $post_id ] );
     192        if ( 1 !== count( $one ) ) {
     193            throw new \Exception( 'Not found' );
     194        }
     195        foreach ( $one as $o ) {
     196            return $o;
     197        }
    162198    }
    163199
     
    276312                maybe_switch_blog();
    277313
    278                 $term_ids = [];
     314
     315                $term_ids   = [];
     316                $this->tags = [];
    279317
    280318                foreach ( array_unique( array_filter( $value ) ) as $tag_slug ) {
     
    312350                            );
    313351                        }
     352
     353                        $this->tags[] = $value;
     354
    314355                    }
    315356                }
     
    362403            case 'tags':
    363404
     405                if ( ! is_null( $this->tags ) ) {
     406                    return $this->tags;
     407                }
     408
    364409                maybe_switch_blog();
    365410
     
    368413                maybe_restore_blog();
    369414
    370                 return array_map(
     415                $this->tags = array_map(
    371416                    function( $tag ) {
    372417                        return $tag->slug;
     
    374419                    $tags
    375420                );
     421
     422                return $this->tags;
    376423
    377424            default:
     
    646693                    if ( $screen->post_type == 'wallets_address' && $screen->base == 'post' && isset( $post ) && is_object( $post ) ):
    647694                        $address = Address::load( $post->ID );
     695                        $address->__get( 'tags' );
    648696                        if ( in_array( 'archived', $address->tags ) ):
    649697                            ?>
     
    738786        if ( ! $post ) return;
    739787
     788        if ( 'wallets_address' !== $post->post_type ) return;
     789
    740790        try {
    741791            $address = self::load( $post->ID );
    742792        } catch ( \Exception $e ) {
    743             $address = new self;
     793            error_log(
     794                sprintf(
     795                    '%s: Cannot instantiate %d to show metaboxes, due to: %s',
     796                    __CLASS__,
     797                    $post->ID,
     798                    $e->getMessage()
     799                )
     800            );
     801            return;
    744802        }
    745803
     
    12881346            echo ucfirst( $address->type );
    12891347
     1348            $address->__get( 'tags' );
    12901349            if ( in_array( 'archived', $address->tags ) ):
    12911350                echo ' ';
  • wallets/trunk/post-types/class-currency.php

    r3015422 r3089855  
    7979 * Fiat currencies should have the `fiat` taxonomy term (tag) assigned to them.
    8080 *
    81  *      $fiat_currencies = load_currencies( get_currency_ids( 'fiat' ) );
     81 *      $fiat_currencies = Currency::load_many( get_currency_ids( 'fiat' ) );
    8282 *
    8383 * # Get all cryptocurrencies (i.e. not fiat)
    8484 *
    85  *      $cryptocurrencies = load_currencies(
     85 *      $cryptocurrencies = Currency::load_many(
    8686 *          array_diff(
    8787 *              get_currency_ids(), // gets all currency IDs
     
    263263
    264264    /**
     265     * Array of tags.
     266     *
     267     * Tags correspond to term slugs in the wallets_currency_tags taxonomy.
     268     *
     269     * @var ?string[]
     270     */
     271    private $tags = null;
     272
     273    /**
     274     * Factory to construct a currency in one go from database values.
     275     *
     276     * @param int $post_id The ID of the post in the database
     277     * @param string $post_title The post's title to be used as currency name
     278     * @param array $postmeta Key-value pairs
     279     * @param string[] $tags The slugs of the terms on taxonomy wallets_currency_tags
     280     *
     281     * @return Currency The constructed instance of the currency object
     282     */
     283    public static function from_values( int $post_id, string $post_title, array $postmeta, array $tags ): Currency {
     284
     285        $currency = new self;
     286
     287        // populate fields
     288        $currency->post_id               = $post_id;
     289        $currency->name                  = $post_title ?? '';
     290        $currency->symbol                = $postmeta['wallets_symbol'] ?? '';
     291        $currency->decimals              = absint( $postmeta['wallets_decimals'] ?? 0 );
     292        $currency->pattern               = $postmeta['wallets_pattern'] ?? '%f';
     293        $currency->coingecko_id          = $postmeta['wallets_coingecko_id'] ?? '';
     294        $currency->contract_address      = $postmeta['wallets_contract_address'] ?? '';
     295        $currency->rates                 = unserialize( $postmeta['wallets_rates'] ?? [] );
     296        $currency->min_withdraw          = absint( $postmeta['wallets_min_withdraw'] ?? 0 );
     297        $currency->max_withdraw          = absint( $postmeta['wallets_max_withdraw'] ?? 0 );
     298        $currency->max_withdraw_per_role = (array) unserialize( $postmeta['wallets_max_withdraw_per_role'] ?? [], [ 'allowed_classes' => false ] );
     299        $currency->fee_deposit_site      = absint( $postmeta['wallets_fee_deposit_site'] ?? 0 );
     300        $currency->fee_move_site         = absint( $postmeta['wallets_fee_move_site'] ?? 0 );
     301        $currency->explorer_uri_tx       = $postmeta['wallets_explorer_uri_tx'] ?? '';
     302        $currency->explorer_uri_add      = $postmeta['wallets_explorer_uri_add'] ?? '';
     303
     304
     305        if ( ! is_array( $currency->rates ) ) {
     306            $currency->rates = [];
     307        }
     308
     309        $wallet_id = $postmeta['wallets_wallet_id'] ?? null;
     310        if ( $wallet_id ) {
     311            try {
     312                $currency->wallet = Wallet::load( $wallet_id ) ?? null;
     313            } catch ( \Exception $e ) {
     314                $currency->wallet = null;
     315            }
     316        }
     317
     318        $currency->tags = $tags;
     319
     320        return $currency;
     321    }
     322
     323    /**
     324     * Retrieve many currencies by their post_ids.
     325     *
     326     * Any post_ids not found are skipped silently.
     327     *
     328     * @param int[] $post_ids The post IDs
     329     * @param ?string $unused Do not use. Ignored.
     330     *
     331     * @return Currency[] The currencies
     332     * @throws \Exception If DB access or instantiation fails.
     333     *
     334     * @since 6.2.6 Introduced.
     335     */
     336    public static function load_many( array $post_ids, ?string $unused = null ): array {
     337        return parent::load_many( $post_ids, 'wallets_currency' );
     338    }
     339
     340    /**
    265341     * Load a Currency object from its custom post entry.
    266342     *
     
    268344     * @see Post_Type::load()
    269345     * @return Currency
     346     * @throws \Exception If not found or failed to instantiate.
    270347     */
    271348    public static function load( int $post_id ): Currency {
    272         maybe_switch_blog();
    273 
    274         $post = get_post( $post_id );
    275 
    276         if ( ! $post ) {
    277             maybe_restore_blog();
    278             throw new \InvalidArgumentException( sprintf( "%s with %d does not exist!", __CLASS__, $post_id ) );
    279         }
    280 
    281         if ( ! 'wallets_currency' == $post->post_type ) {
    282             maybe_restore_blog();
    283             throw new \InvalidArgumentException( "Post $post_id is not a currency!" );
    284         }
    285 
    286         $currency = new self;
    287         $currency->post_id               = $post->ID;
    288         $currency->name                  = $post->post_title;
    289         $currency->symbol                = get_post_meta( $post->ID, 'wallets_symbol', true );
    290         $currency->decimals              = get_post_meta( $post->ID, 'wallets_decimals', true );
    291         $currency->pattern               = get_post_meta( $post->ID, 'wallets_pattern', true );
    292         $currency->coingecko_id          = get_post_meta( $post->ID, 'wallets_coingecko_id', true );
    293         $currency->contract_address      = get_post_meta( $post->ID, 'wallets_contract_address', true );
    294         $currency->rates                 = get_post_meta( $post->ID, 'wallets_rates', true );
    295         $currency->min_withdraw          = absint( get_post_meta( $post->ID, 'wallets_min_withdraw',          true ) );
    296         $currency->max_withdraw          = absint( get_post_meta( $post->ID, 'wallets_max_withdraw',          true ) );
    297         $currency->max_withdraw_per_role = (array) get_post_meta( $post->ID, 'wallets_max_withdraw_per_role', true )  ;
    298         $currency->fee_deposit_site      = absint( get_post_meta( $post->ID, 'wallets_fee_deposit_site',      true ) );
    299         $currency->fee_move_site         = absint( get_post_meta( $post->ID, 'wallets_fee_move_site',         true ) );
    300         $currency->fee_withdraw_site     = absint( get_post_meta( $post->ID, 'wallets_fee_withdraw_site',     true ) );
    301         $currency->icon_url              = get_the_post_thumbnail_url( $post->ID );
    302         $currency->explorer_uri_tx       = get_post_meta( $post->ID, 'wallets_explorer_uri_tx', true );
    303         $currency->explorer_uri_add      = get_post_meta( $post->ID, 'wallets_explorer_uri_add', true );
    304 
    305         if ( ! is_array( $currency->rates ) ) {
    306             $currency->rates = [];
    307         }
    308 
    309         $wallet_id = get_post_meta( $post->ID, 'wallets_wallet_id', true );
    310         if ( $wallet_id ) {
    311             try {
    312                 $currency->wallet = Wallet::load( $wallet_id );
    313             } catch ( \Exception $e ) {
    314                 $currency->wallet = null;
    315             }
    316         }
    317 
    318         maybe_restore_blog();
    319 
    320         return $currency;
     349        $one = self::load_many( [ $post_id ] );
     350        if ( 1 !== count( $one ) ) {
     351            throw new \Exception( 'Not found' );
     352        }
     353        foreach ( $one as $o ) {
     354            return $o;
     355        }
    321356    }
    322357
     
    464499
    465500            case 'decimals':
    466                 if ( has_transactions( $this ) && $value != $this->decimals ) {
     501                if ( $this->post_id && has_transactions( $this ) && $value != $this->decimals ) {
    467502                    throw new \Exception( "Cannot change the number of decimals for currency $this->name because it already has transactions." );
    468503                }
     
    496531                maybe_switch_blog();
    497532
    498                 $term_ids = [];
     533                $term_ids   = [];
     534                $this->tags = [];
    499535
    500536                foreach ( array_unique( array_filter( $value ) ) as $tag_slug ) {
     
    527563                    }
    528564
     565                    $this->tags[] = $value;
    529566                }
    530567
     
    568605
    569606            case 'icon_url':
     607
     608                if ( is_null( $this->icon_url ) ) {
     609                    $this->icon_url = get_the_post_thumbnail_url( $this->post_id ) ?? '';
     610                }
     611
    570612                /**
    571613                 * Coin/currency icon URL filter.
     
    628670            case 'tags':
    629671
     672                if ( ! is_null( $this->tags ) ) {
     673                    return $this->tags;
     674                }
     675
    630676                maybe_switch_blog();
    631677
     
    634680                maybe_restore_blog();
    635681
    636                 return array_map(
     682                $this->tags = array_map(
    637683                    function( $tag ) {
    638684                        return $tag->slug;
     
    640686                    $tags
    641687                );
     688
     689                return $this->tags;
    642690
    643691            case 'extra_field_name':
     
    661709     */
    662710    public function is_fiat(): bool {
     711
     712        if ( is_null( $this->tags ) ) {
     713            $this->__get( 'tags' );
     714        }
     715
     716
    663717        if ( in_array( 'fiat', $this->tags ) ) {
    664718            return true;
     
    770824
    771825        $platform = null;
     826
     827        if ( is_null( $this->tags ) ) {
     828            $this->__get( 'tags' );
     829        }
    772830
    773831        foreach ( $this->tags as $tag ) {
     
    11231181        if ( ! $post ) return;
    11241182
     1183        if ( 'wallets_currency' !== $post->post_type ) return;
     1184
    11251185        try {
    11261186            $currency = self::load( $post->ID );
    11271187        } catch ( \Exception $e ) {
    1128             $currency = new self;
     1188            error_log(
     1189                sprintf(
     1190                    '%s: Cannot instantiate %d to show metaboxes, due to: %s',
     1191                    __CLASS__,
     1192                    $post->ID,
     1193                    $e->getMessage()
     1194                )
     1195            );
     1196            return;
    11291197        }
    11301198
  • wallets/trunk/post-types/class-transaction.php

    r3070693 r3089855  
    203203
    204204    /**
    205      * Transacton comment.
     205     * Transaction comment.
    206206     *
    207207     * Free text string, stored on the post_title column in the DB.
     
    273273    private $parent_id = 0;
    274274
     275
     276    /**
     277     * Array of tags.
     278     *
     279     * Tags correspond to term slugs in the wallets_tx_tags taxonomy.
     280     *
     281     * @var ?string[]
     282     */
     283    private $tags = null;
     284
    275285    /**
    276286     * Whether the TX data has changed since last DB load.
     
    283293
    284294    /**
    285      * Load a Transaction from its custom post entry.
    286      *
    287      * @inheritdoc
    288      * @see Post_Type::load()
    289      * @return Transaction
    290      */
    291     public static function load( int $post_id ): Transaction {
    292         maybe_switch_blog();
    293 
    294         $post = get_post( $post_id );
    295 
    296         if ( ! $post ) {
    297             maybe_restore_blog();
    298             throw new \InvalidArgumentException( sprintf( "%s with %d does not exist!", __CLASS__, $post_id ) );
    299         }
    300 
    301         if ( 'wallets_tx' != $post->post_type ) {
    302             maybe_restore_blog();
    303             throw new \InvalidArgumentException( "Post $post_id is not a transaction!" );
    304         }
     295     * Factory to construct a transaction in one go from database values.
     296     *
     297     * @param int $post_id The ID of the post in the database
     298     * @param string $post_title The post's title to be used as transaction comment
     299     * @param string $post_status The status of the post, to populate the transaction state field
     300     * @param string $post_parent The post's parent post, for transaction counterparts
     301     * @param array $postmeta Key-value pairs
     302     * @param string[] $tags The slugs of the terms on taxonomy wallets_tx_tags
     303     *
     304     * @return Transaction The constructed instance of the transaction object
     305     */
     306    public static function from_values( int $post_id, string $post_title, string $post_status, int $post_parent, array $postmeta, array $tags ): Transaction {
    305307
    306308        $tx = new self;
    307         $tx->post_id    = $post->ID;
    308         $tx->category   = get_post_meta( $post->ID, 'wallets_category', true );
    309         $tx->txid       = get_post_meta( $post->ID, 'wallets_txid', true );
    310 
    311         $address_id     = get_post_meta( $post->ID, 'wallets_address_id', true );
     309
     310        // populate fields
     311        $tx->post_id = $post_id;
     312        $tx->category   = $postmeta['wallets_category'] ?? 'move';
     313        $tx->txid       = $postmeta['wallets_txid'] ?? '';
     314
     315        $address_id = $postmeta['wallets_address_id'] ?? null;
    312316        if ( $address_id ) {
    313317            try {
    314                 $tx->address = Address::load( $address_id );
     318                $tx->address = Address::load( $address_id ) ?? null;
    315319            } catch ( \Exception $e ) {
    316320                $tx->address = null;
    317321            }
    318322        }
    319         $currency_id = get_post_meta( $post->ID, 'wallets_currency_id', true );
     323
     324        $currency_id = $postmeta['wallets_currency_id'] ?? null;
    320325        if ( $currency_id ) {
    321326            try {
    322                 $tx->currency = Currency::load( $currency_id );
     327                $tx->currency = Currency::load( $currency_id ) ?? null;
    323328            } catch ( \Exception $e ) {
    324329                $tx->currency = null;
    325330            }
    326331        }
    327         $tx->amount     = intval( get_post_meta( $post->ID, 'wallets_amount', true ) );
    328         $tx->fee        = intval( get_post_meta( $post->ID, 'wallets_fee', true ) );
    329         $tx->chain_fee  = absint( get_post_meta( $post->ID, 'wallets_chain_fee', true ) );
    330         $tx->comment    = $post->post_title;
    331         $tx->block      = absint( get_post_meta( $post->ID, 'wallets_block', true ) );
    332         $tx->timestamp  = absint( get_post_meta( $post->ID, 'wallets_timestamp', true ) );
    333         $tx->nonce      = get_post_meta( $post->ID, 'wallets_nonce', true );
    334         $tx->error      = get_post_meta( $post->ID, 'wallets_error', true );
    335         $tx->parent_id  = absint( $post->post_parent );
    336 
    337         switch ( $post->post_status ) {
     332
     333        $tx->amount    = intval( $postmeta['wallets_amount'] ?? 0 );
     334        $tx->fee       = intval( $postmeta['wallets_fee'] ?? 0 );
     335        $tx->chain_fee = absint( $postmeta['wallets_chain_fee'] ?? 0 );
     336        $tx->comment   = $post_title;
     337        $tx->block     = absint( $postmeta['wallets_block'] ?? 0);
     338        $tx->timestamp = absint( $postmeta['wallets_timestamp'] ?? 0 );
     339        $tx->nonce     = $postmeta['wallets_nonce'] ?? '';
     340        $tx->error     = $postmeta['wallets_error'] ?? '';
     341        $tx->parent_id = $post_parent;
     342
     343        switch ( $post_status ) {
    338344            case 'publish':
    339345                $tx->status = 'done';
     
    350356        }
    351357
    352         $user_id = absint( get_post_meta( $post->ID, 'wallets_user', true ) );
     358        $user_id = absint( $postmeta['wallets_user'] );
    353359        if ( $user_id ) {
    354360            $tx->user = new \WP_User( $user_id );
    355361        }
    356362
    357         maybe_restore_blog();
     363        $tx->tags = $tags;
    358364
    359365        return $tx;
     366    }
     367
     368    /**
     369     * Retrieve many transactions by their post_ids.
     370     *
     371     * Any post_ids not found are skipped silently.
     372     *
     373     * @param int[] $post_ids The post IDs
     374     * @param ?string $unused Do not use. Ignored.
     375     *
     376     * @return Transaction[] The transactions
     377     * @throws \Exception If DB access or instantiation fails.
     378     *
     379     * @since 6.2.6 Introduced.
     380     */
     381    public static function load_many( array $post_ids, ?string $unused = null ): array {
     382        return parent::load_many( $post_ids, 'wallets_tx' );
     383    }
     384
     385    /**
     386     * Load a Transaction from its custom post entry.
     387     *
     388     * @inheritdoc
     389     * @see Post_Type::load()
     390     * @return Transaction
     391     * @throws \Exception If not found or failed to instantiate.
     392     */
     393    public static function load( int $post_id ): Transaction {
     394        $one = self::load_many( [ $post_id ] );
     395        if ( 1 !== count( $one ) ) {
     396            throw new \Exception( 'Not found' );
     397        }
     398        foreach ( $one as $o ) {
     399            return $o;
     400        }
    360401    }
    361402
     
    645686            maybe_switch_blog();
    646687
    647             $term_ids = [];
     688            $term_ids   = [];
     689            $this->tags = [];
    648690
    649691            foreach ( array_unique( array_filter( $value ) ) as $tag_slug ) {
     
    681723                        );
    682724                    }
     725
     726                    $this->tags[] = $value;
    683727                }
    684728
     
    745789            case 'tags':
    746790
     791                if ( ! is_null( $this->tags ) ) {
     792                    return $this->tags;
     793                }
     794
    747795                maybe_switch_blog();
    748796
     
    751799                maybe_restore_blog();
    752800
    753                 return array_map(
     801                $this->tags = array_map(
    754802                    function( $tag ) {
    755803                        return $tag->slug;
    756804                    },
    757                     $terms
     805                    $tags
    758806                );
     807
     808                return $this->tags;
    759809
    760810            case 'is_dirty':
     
    13461396        if ( ! $post ) return;
    13471397
     1398        if ( 'wallets_tx' !== $post->post_type ) return;
     1399
    13481400        try {
    13491401            $tx = self::load( $post->ID );
    13501402        } catch ( \Exception $e ) {
    1351             $tx = new self;
     1403            error_log(
     1404                sprintf(
     1405                    '%s: Cannot instantiate %d to show metaboxes, due to: %s',
     1406                    __CLASS__,
     1407                    $post->ID,
     1408                    $e->getMessage()
     1409                )
     1410            );
     1411            return;
    13521412        }
    13531413
  • wallets/trunk/post-types/class-wallet.php

    r3020838 r3089855  
    119119
    120120    /**
     121     * Factory to construct a wallet in one go from database values.
     122     *
     123     * @param int $post_id The ID of the post in the database
     124     * @param string $post_title The post's title to be used as wallet name
     125     * @param array $postmeta Key-value pairs
     126     *
     127     * @return Wallet The constructed instance of the wallet object
     128     */
     129    public static function from_values( int $post_id, string $post_title, string $post_status, array $postmeta ): Wallet {
     130
     131        $wallet = new self;
     132
     133        // populate fields
     134        $wallet->post_id = $post_id;
     135        $wallet->name             = $post_title;
     136        $wallet->is_enabled       = 'publish' === $post_status;
     137
     138        // Due to a bug introduced in an earlier version,
     139        // the settings are double-serialized,
     140        // once by WordPress and once by the plugin :(
     141        $wallet->adapter_settings = unserialize(
     142            unserialize(
     143                $postmeta['wallets_adapter_settings'] ?? 's:6:"a:0:{}";',
     144                [ 'allowed_classes' => false ]
     145            ),
     146            [ 'allowed_classes' => false ]
     147        );
     148
     149        $adapter_class = $postmeta['wallets_adapter_class'] ?? '';
     150        if ( class_exists( $adapter_class ) ) {
     151              $wallet->adapter = new $adapter_class( $wallet );
     152        }
     153
     154
     155        return $wallet;
     156    }
     157
     158    /**
     159     * Retrieve many walletts by their post_ids.
     160     *
     161     * Any post_ids not found are skipped silently.
     162     *
     163     * @param int[] $post_ids The post IDs
     164     * @param ?string $unused Do not use. Ignored.
     165     *
     166     * @return Wallet[] The wallets
     167     * @throws \Exception If DB access or instantiation fails.
     168     *
     169     * @since 6.2.6 Introduced.
     170     */
     171    public static function load_many( array $post_ids, ?string $unused = null ): array {
     172        return parent::load_many( $post_ids, 'wallets_wallet' );
     173    }
     174
     175    /**
    121176     * Load a Wallet from its custom post entry.
    122177     *
    123178     * @inheritdoc
    124179     * @see Post_Type::load()
    125      * @throws \InvalidArgumentException If the post id is not valid.
    126180     * @return Wallet
     181     * @throws \Exception If not found or failed to instantiate.
    127182     */
    128183    public static function load( int $post_id ): Wallet {
    129         maybe_switch_blog();
    130 
    131         $post = get_post( $post_id );
    132 
    133         if ( ! $post ) {
    134             throw new \InvalidArgumentException( sprintf( "%s with %d does not exist!", __CLASS__, $post_id ) );
    135         }
    136 
    137         if ( ! ( $post && 'wallets_wallet' == $post->post_type ) ) {
    138             throw new \InvalidArgumentException( "Post $post_id is not a wallet!" );
    139         }
    140 
    141         $wallet = new self;
    142         $wallet->post_id           = $post->ID;
    143         $wallet->name              = $post->post_title;
    144         $wallet->is_enabled        = $post->post_status == 'publish';
    145         $wallet->adapter_settings  = unserialize( get_post_meta( $post->ID, 'wallets_adapter_settings', true ) );
    146         if ( ! is_array( $wallet->adapter_settings ) ) {
    147             $wallet->adapter_settings = [];
    148         }
    149         $adapter_class             = get_post_meta( $post->ID, 'wallets_adapter_class', true );
    150         if ( class_exists( $adapter_class ) ) {
    151             $wallet->adapter = new $adapter_class( $wallet );
    152         }
    153 
    154         maybe_restore_blog();
    155 
    156         return $wallet;
     184        $one = self::load_many( [ $post_id ] );
     185        if ( 1 !== count( $one ) ) {
     186            throw new \Exception( 'Not found' );
     187        }
     188        foreach ( $one as $o ) {
     189            return $o;
     190        }
    157191    }
    158192
     
    399433        if ( ! $post ) return;
    400434
     435        if ( 'wallets_wallet' !== $post->post_type ) return;
     436
    401437        try {
    402438            $wallet = self::load( $post->ID );
    403439        } catch ( \Exception $e ) {
    404             $wallet = new self;
     440            error_log(
     441                sprintf(
     442                    '%s: Cannot instantiate %d to show metaboxes, due to: %s',
     443                    __CLASS__,
     444                    $post->ID,
     445                    $e->getMessage()
     446                )
     447            );
     448            return;
    405449        }
    406450
  • wallets/trunk/readme.txt

    r3070693 r3089855  
    22Contributors: dashedslug
    33Donate link: https://flattr.com/profile/dashed-slug
    4 Tags: wallet, bitcoin, cryptocurrency, altcoin, coin, money, e-money, e-cash, deposit, withdraw, account, API
     4Tags: wallet, bitcoin, cryptocurrency, altcoin, custodial
    55Requires at least: 6.0
    6 Tested up to: 6.5.2
     6Tested up to: 6.5.3
    77Requires PHP: 7.0
    8 Stable tag: 6.2.5
     8Stable tag: 6.2.6
    99License: GPLv2 or later
    1010License URI: http://www.gnu.org/licenses/gpl-2.0.html
     
    337337
    338338== Changelog ==
     339
     340= 6.2.6 =
     341- Change: When the plugin loads batches of Wallets, Currencies, Transactions, or Addresses, it now does it faster and with less SQL queries to the DB to improve performance.
     342- Add: New built-in object cache to further improve DB performance. Can be turned off in general settings.
     343-
    339344
    340345= 6.2.5 =
     
    485490- Remove: The frontend Widgets have been removed. Please use the shortcodes instead.
    486491
    487 = 5.0.18 =
    488 - Fix: The shortcode `[wallets_depositextra template="static_textonly" symbol="???"]` now displays address extra info correctly if such info is present.
    489 - Fix: Another issue related to the display of the cold storage screen.
    490 
    491 = 5.0.17 =
    492 - Fix: Issue while sending emails to names that contain a comma symbol (,) is now fixed.
    493 - Fix: Prevent issue in cold storage screen that caused error in PHP8 and warnings in PHP7 or earlier.
    494 - Change: Referral links in cold storage screen are now updated.
    495 
    496 = 5.0.16 =
    497 - Improve: When crypto ticker symbols clash with fiat ticker symbols, fiat symbols take priority for now. This issue will be ultimately fixed in the next major release.
    498 - Improve: Using blockchair as the default block explorer for multiple well-known cryptocurrencies.
    499 - Fix: Added guard clause to suppress minor warning in admin editor.
    500 
    501 = 5.0.15 =
    502 - Improve: The exchange rates for coins with a dot in their symbol (e.g. `USDT.ECRC20`) are those of the coin with symbol only the characters before the dot (e.g. `USDT`). This mostly helps the CoinPayments adapter.
    503 - Fix: Bad logic in `wallets_transaction` action prevented confirmation counts from updating on withdrawal transaction notifications. Is now fixed.
    504 
    505 = 5.0.14 =
    506 - Fix: If, during a withdrawal, a coin adapter fails due to a `phpcurl` timeout, consider the withdrawal done, not failed. Fixes double-spends with Monero or TurtleCoin forks on slow wallets.
    507 - Fix: In some situations, email notifications to admins about transaction verifications were not being sent. This is now fixed.
    508 - Change: By default, withdrawals are now executed once, for extra safety. This number of retries can be changed in the cron job settings.
    509 - Fix: If a coin adapter throws on `get_block_height()`, this no longer crashes the "Coin Adapters" admin screen.
    510 
    511 = 5.0.13 =
    512 - Fix: Bug with network_active plugin in the admin screens "Deposit Addresses" and "User Balances", is now fixed. Some data from other blogs was previously not shown.
    513 - Fix: The Bitcoin RPC coin adapter now recommends that the admin enters into the `.conf` file: `rpcbind=IP` where `IP` is the address of the hot wallet, not the WordPress host.
    514 - Fix: Typo in documentation.
    515 - Add: Link to pre-release notes for the upcoming *Bitcoin and Altcoin Wallets 6.0.0*.
    516 
    517 = 5.0.12 =
    518 - Fix: Reverting change from 5.0.11. The original way to store data was to always maintain one set of tables for all the sites in a network. See release notes for details.
    519 
    520 = 5.0.11 =
    521 - Fix: When activating the plugin on a single site, on a network WordPress installation, the plugin now creates the independent DB tables correctly, for each site on the network.
    522 
    523 = 5.0.10 =
    524 - Add: PHPdoc for the `wallets_get_template_part` as per the WordPress coding style.
    525 - Add: New button in `[wallets_withdraw]` and `[wallets_move]` now allows users to easily transact their entire available balance without typing the amount.
    526 
    527 = 5.0.9 =
    528 - Fix: Issue with deposit timestamps not being inserted into some DBs is now fixed.
    529 - Fix: Issue with `[wallets_transactions template="rows"]` introduced in version `5.0.6` is now fixed.
    530 - Fix: Previously issuing an internal transfer (move) via the PHP API with `skip_confirm=true` would not trigger email notifications, is now fixed.
    531 
    532 = 5.0.8 =
    533 - Fix: Maximum number of symbol characters changed from 8 to 10. Allows for `USDT.ERC20`.
    534 - Improve: Some DB columns now use the latin1_bin character set to save space and help with DB indexing. Affects new installs.
    535 - Add: The debug tab in the admin dashboard now reports the type of Object Cache that is currently active.
    536 - Improve: In the coin adapters list, the withdrawal lock icon is now accompanied by text. This solves issues where the combination of font and screen antialiasing makes the state of the padlock difficult to read.
    537 
    538 = 5.0.7 =
    539 - Fix: Issue with shortcode `[wallets_deposit template="list"]`, introduced in `5.0.6` is now fixed.
    540 - Fix: Dynamic shortcodes `[wallets_deposit]` and `[wallets_withdraw]` apply the HTML classes `crypto-coin` and `fiat-coin` according to the selected coin in those UIs, not the page-wide selected coin, which could be a custom coin (Fiat Coin adapter extension).
    541 
    542 = 5.0.6 =
    543 - Add: Many elements in the frontend templates now have the HTML classes `crypto-coin` and `fiat-coin` to allow differential styling of fiat and crypto coin information.
    544 - Fix: When full node adapter RPC settings are saved, the cached value of the adapter status is evicted, forcing an immediate refresh of the adapter status in the admin screens.
    545 - Fix: Already minified JQuery UI CSS is no longer re-minified.
    546 - Add: Several new shortcode templates and shortcodes allow for displaying "textonly" values. e.g. `[wallets_api_key template="textonly"]` displays the user's API key in a `<span>` tag that can be inserted in text paragraphs.
    547 - Fix: Some HTML classes that were previously doubled are now fixed, e.g. `deposit-deposit`, etc.
    548 
    549 = 5.0.5 =
    550 - Change: In confirmation/notification templates, `###USER_LOGIN###`/`###OTHER_USER_LOGIN###` is no longer described as "account nickname", since it is the `user_login` and not the `nickname`.
    551 - Add: In confirmation/notification templates,` ###USER_NICKNAME###`/`###OTHER_USER_NICKNAME###` is introduced as a variable that is substituted for the user's nickname.
    552 - Change: Confirmation/notification templates now use the variable `###EXTRA_DESCRIPTION###` to accurately display the name of the extra description (such as Payment ID, Memo, etc.)
    553 - Add: New column in coin adapters list indicates max block height up to which the wallet is synced for compatible coin adapters.
    554 - Fix: Coingecko exchange rates are now loaded with asynchronous buffering, to prevent high memory usage.
    555 - Add: New TurtleCoin adapter is showcased in the "About" admin screen.
    556 
    557 = 5.0.4 =
    558 - Fix: In `[wallets_balance template="static"]` the symbol next to the equivalent (fiat) amount was incorrect, is now fixed.
    559 - Add: Variable substitutions in email templates now allow for new variables: `###USER_LOGIN###`, `###OTHER_USER_LOGIN###`, `###USER_NICENAME###`, `###OTHER_USER_NICENAME###`, `###DISPLAY_NAME###`, `###OTHER_DISPLAY_NAME###`
    560 - Change: The variable substitution `###USER_ACCOUNT###` is now deprecated. New email templates use the user's display name by default (`###DISPLAY_NAME###`).
    561 
    562 = 5.0.3 =
    563 - Fix: Exchange rates subsystem no longer attempts to access the old public CoinMarketCap API that has been recently discontinued. An API key is now required, no longer optional.
    564 - Improve: The efficiency of CoinMarketCap API queries has been improved, both in terms of performance, and API debits used.
    565 
    566 = 5.0.2 =
    567 - Improve: More JavaScript assets are not loaded if not needed in current front-end page. This also prevents unneeded JSON-API hits when not logged in.
    568 - Add: Map files for minified `knockout-validation.js` and `sprintf.js` assets are now added.
    569 - Add: Instruct users to use square brackets when entering IPv6 IP addresses.
    570 - Fix: Undefined warning in dashboard prevented TX count totals from being shown, is now fixed.
    571 - Fix: Warning shown in logs when stats on previous cron run were not available, is now fixed.
    572 - Fix: When plugin is NOT network-activated on a multisite installation, the cron job trigger URL now displays the correct sub-site domain.
    573 - Fix: Transaction summaries dashboard widget, introduced in version `5.0.0`, is now shown in network admin dashboard if plugin is network-activated.
    574 - Change: Upgraded included library `knockout.js` from `3.5.0-rc2` to `3.5.1`.
    575 - Change: Upgraded included library `knockout.validation.js` from `2.0.3` to `2.0.4`.
    576 - Change: Upgraded included library `bs58check.js` from `2.1.2` to latest commit.
    577 - Change: When plugin is network-activated, the Admin Transactions list screen displays domains without a trailing slash.
    578 
    579 = 5.0.1 =
    580 - Add: The coin adapters admin page now includes a helpful link to a page explaining the concept and showcasing the available coin adapters.
    581 - Improve: The template loader introduced in `5.0.0` is improved to allow use by other extensions.
    582 - Change: The JavaScript code that detects HTML comments that have been stripped by minifiers now outputs to the browser console, not alert box.
    583 - Fix: When adding a deposit UI via the Widgets admin screen, the default size for the QR code is no longer 10 pixels. It is now blank, which sets the size automatically to match the container.
    584 - Fix: Incompatibility with *Two Factor Authentication* plugin that was originally fixed in `4.3.1` regressed in version `5.0.0` but is now fixed again.
    585 - Fix: Remove unminified copy of jQuery UI stylesheet.
    586 - Fix: A CSS issue previously made it impossible to remove wallet widgets from the admin widget area, on desktop screens. This is now fixed.
    587 
    588 = 5.0.0 =
    589 - Improve: The UIs are now more easily overridable by themes. A template loader can load markup for these UIs from a theme or child theme, falling back to the plugin's templates if needed.
    590 - Change: The `wallets_views_dir` attribute and filter are removed. Use theme templates instead to provide your custom markup.
    591 - Add: The `[wallets_withdraw]` shortcode can now scan addresses from QR code in devices that support it.
    592 - Add: The opacity of UIs while loading data over AJAX can now be controlled in Customizer.
    593 - Add: The border radius of UIs (corner roundness) can now be controlled in Customizer.
    594 - Add: The colors used in `[wallets_transactions]` shortcodes to signify transaction status can now be changed in Customizer.
    595 - Add: Admin dashboard widget now shows multiple tabs with statistics on recent transactions.
    596 - Add: When the plugin is network-active across a multisite install, the admin transactions list shows extra column *Site*.
    597 - Improve: All operations that modify transaction data now also refresh the time on the `updated_time` column.
    598 - Improve: Better integration with *Simple History* plugin. Transactions are now logged with clearer information, including links to user profiles and block explorers.
    599 - Improve: The cold storage admin screens now also display the amount of funds locked in unaccepted & pending transactions.
    600 - Add: The third-party service `coincap.io` is now available as an *Exchange Rates* provider.
    601 - Improve: In admin transactions list screen, amounts are now in fixed-font and align vertically for easier visual inspection.
    602 - Improve: For Bitcoin-like adapters, the RPC secret is not shown in the markup, but bullets are shown instead. Improves security.
    603 - Add: The `[wallets_api_key]` shortcode can now be used as a widget.
    604 - Fix: Adapters for fiat coins are no longer shown in the cold storage section, as these adapters are not backed by wallets.
    605 - Improve: The Bitcoin address validator used in `[wallets_withdraw]` now correctly allows Bitcoin testnet addresses. Useful for testing using testnet.
    606 - Improve: When creating database tables for the first time, the WordPress default is used for character collation and encoding.
    607 - Fix: When Bittrex is used as an *Exchange Rates* provider, if the last price is not available, the plugin falls back to using the average of the Bid/Ask values, or one of the two values if only one is available. Helps determine exchange rates in low liquidity markets.
    608 - Improve: In the `[wallets_balance template="list"]` UI, the text "Show zero balances" is clickable and toggles the checkbox. Improves usability.
    609 - Improve: The plugin will now warn the user in the frontend if HTML comments have been minified, as this is a common pitfall for new users.
    610 - Improve: In the debug tab of the admin dashboard, memory values are now shown with thousand separators and units (bytes) for easier visual inspection.
    611 - Change: Bitcoin-like adapters now rescan the entire wallet's transaction list from the start weekly rather than monthly. This is a fail-safe mechanism that detects transactions that would otherwise slip through undetected if curl calls from `walletnotify` were to fail for any reason.
    612 - Fix: Issue in email notifications for deposits, where the fees would not be shown correctly, is now fixed.
    613 - Add: The cron-related debug information from the admin dashboard is now also shown in the admin cron job settings screen for easier reference.
    614 - Change: *Tradesatoshi* is removed from list of *Exchange Rate providers* as the service is shut down.
    615 - Change: JavaScript assets are now loaded only in pages where they are needed. Improves frontend performance.
    616 - Fix: Issue with writing out CSVs when exporting transactions is now fixed.
    617 - Improve: When manually adding a transaction using the `wallets_transactions` action, it is now possible to specify an initial transaction status for withdrawals.
    618 - Fix: In admin adapters screen, sorting by pending withdrawals no longer triggers a warning in the debug logs.
    619 - Fix: When user requests a new deposit address via the `[wallets_deposit]` shortcode, the other deposit addresses are now marked as *old*, and only the new one is *current*.
    620 - Add: The `[wallets_balance template="list"]` UI now includes a column for *unavailable balance*, i.e. balance that is locked in pending transactions or trades.
    621 - Fix: The plugin now correctly calculates amount of wallet balance that is unavailable due to staking in more wallets, including PotCoin and Dash.
    622 - Change: The plugin now warns the admin if the available balance is less than 5% of the total balance (previously the threshold was 1%).
    623 - Fix: Some error messages that get printed only to the debug log are no longer translatable.
    624 - Improve: Reduce number of calls to `is_plugin_active_for_network()`.
    625 - Fix: Some HTML markup errors in sidebar widgets are now fixed.
    626 
    627 = 4.4.8 =
    628 - Fix: The capability repair cron job, introduced in `4.4.4` is improved to be more fail-safe. If no admin user has `manage_wallets`, it now assigns `manage_wallets` to all admins and to the Administrator role. Prevents admins from being locked out.
    629 - Fix: CoinMarketCap signup link (for API access) is now updated.
    630 - Improve: Admin menu icon follows style guide more closely (is a data-uri encoded SVG).
    631 - Fix: The JSON-API requests are now excluded from the service worker's cache if the *SuperPWA* plugin is installed.
    632 
    633 = 4.4.7 =
    634 - Fix: Workaround that restores compatibility with the Bitcoin ABC full node wallet (Bitcoin Cash), due to improperly deprecated accounts feature in that wallet (github issue #360).
    635 
    636 = 4.4.6 =
    637 - Add: If a withdrawal cannot proceed due to low hot wallet balance, the withdrawal remains in pending state (would previously fail), and the site's admin or admins receive emails about this.
    638 - Add: Italian translations for frontend, submitted by *Fabio Perri*, webnetsolving@gmail.com, https://www.transifex.com/user/profile/Fabio.Perri/
    639 - Fix: When reloading the User balances page, the ordering is now predictable and stays the same.
    640 - Add: User balances can now be sorted by balance and coin.
    641 - Fix: Affiliate link to trezor was broken in cold storage pages, now fixed.
    642 
    643 = 4.4.5 =
    644 - Fix: Removed Novaexchange and Cryptopia from exchange rates providers since their APIs are now unavailable.
    645 - Improve: Attempt to disable PHP max execution time while importing transactions from csv files. Can help with importing very large files.
    646 - Add: The `[wallets_move]` shortcode will now auto-suggest usernames of people that the current user has had internal transfers with before.
    647 - Improve: Reduce HTTP timeout when retrieving exchange rates.
    648 
    649 = 4.4.4 =
    650 - Fix: Several settings can now be saved on network-active installation, previously could not be saved.
    651 - Add: If no users in the Administrator role have the `manage_wallets` capability, this capability is assigned to the Administrator role.
    652 - Add: It is now allowed again to modify the capabilities of the Administrator user role via the *Wallets* &rarr; *Capabilities* screen.
    653 
    654 = 4.4.3 =
    655 - Fix: Issue where moment.js library was not loaded in 4.4.2 is now fixed.
    656 - Fix: When cron job selects old transactions to cancel, it now takes the local timezone into account.
    657 - Fix: When cron job selects old transactions to autoconfirm, it now takes the local timezone into account.
    658 - Fix: When cron job selects old transactions to aggregate, it now takes the local timezone into account.
    659 
    660 = 4.4.2 =
    661 - Improve: Code quality improved throughout, guided by a CodeRisk audit.
    662 - Improve: Included knockout.js library is updated to `v3.5.0` because it's needed for the Exchange extension.
    663 - Fix: Included moment.js library is updated to `2.24.0`.
    664 
    665 = 4.4.1 =
    666 - Fix: Issue introduced in `4.4.0` that prevented initiating new transactions in most cases, is now fixed.
    667 
    668 = 4.4.0 =
    669 - Add: It is now possible to display amount equivalencies in fiat currencies in confirmation/notification emails.
    670 - Improve: Variable substitutions are now more uniform between confirmations and notifications.
    671 - Add: The recommended `.conf` settings for Bitcoin core take into account the latest changes in 18.0.0 where `rpcbind` and `rpcauth` are mandatory.
    672 - Add: New cron job setting "Allow running on AJAX requests". Is on by default, can be turned off (e.g. if it slows down WooCommerce too much).
    673 - Fix: Use `esc_textarea()` where appropriate.
    674 
    675 = 4.3.5 =
    676 - Fix: The user-confirmation link can no longer resurrect transactions that have been cancelled by an admin. Only unconfirmed transactions can now be confirmed via the confirmation link.
    677 - Add: New option to send a Bcc copy of *all* emails to the admin(s). All users with the `manage_wallets` capability are notified if the option is on.
    678 
    679 = 4.3.4 =
    680 - Fix: On cron job runs, transactions on Bitcoin-like RPC API wallets are now scanned more efficiently.
    681 - Add: The cron job tasks that scan for transactions on Bitcoin-like RPC API wallets now report a lot more detail when verbose debugging is turned on.
    682 
    683 = 4.3.3 =
    684 - Fix: The frontend now checks to see if the selected coin exists before rendering its icon in more view templates. Avoids some JavaScript errors.
    685 - Fix: In frontend UIs, when transaction amount minus fees is negative, "insufficient balance was shown". Added new validation error message in this case.
    686 - Fix: In frontend UIs, validation error for less than minimum withdrawal amount is now given higher priority.
    687 
    688 = 4.3.2 =
    689 - Add: The shortcode `[wallets_balance template="list"]` now includes a "Show zero balances" checkbox.
    690 - Fix: The frontend now checks to see if the selected coin exists before rendering its icon. This avoids a JavaScript error.
    691 - Fix: Undefined variable PHP error in multi-site cron prevented logging.
    692 - Fix: All options in network-active installations under *Wallets* &rarr; *Confirms* are now being saved correctly.
    693 
    694 = 4.3.1 =
    695 - Fix: The jquery-qrcode.js library is only loaded in screens where the `[wallets_deposit]` shortcode is shown, and only if QR Codes are enabled.
    696 - Fix: If a different jquery-qrcode.js library is already loaded, the plugin will use that and not load its own copy. Helps with compatibility with `two-factor-authentication` plugin.
    697 - Fix: Add some guard clauses so that warnings are not printed out to the logs.
    698 - Fix: When an admin received notification about a pending withdrawal with no tags, the tags are no longer shown as `###TAGS###` but the string `n/a/` is shown in the email.
    699 
    700 = 4.3.0 =
    701 - Add: Admin can now set the site-wide default coin for frontend UI screens.
    702 - Add: Admin can now set a page-specific or post-specific default coin for frontend UI screens.
    703 - Fix: Fixed a bug in the `[wallets_deposit template="static" symbol="XYZ"]` form of the deposit shortcodes, where the qr code shown was that of the current coin, not the coin specified by the "symbol" attribute.
    704 
    705 = 4.2.2 =
    706 - Add: New button in *Exchange Rates* admin page clear any stale exchange rates and forces the plugin to download new data.
    707 - Add: The result of `wp_using_ext_object_cache()` is now reported in the debug information shown in the admin dashboard.
    708 - Add: Calling `do_cron` via the JSON API now also forces expired transients to be deleted from the DB (the `delete_expired_transients` WordPress action is fired).
    709 - Change: The plugin now loads the frontend libraries `sprintf.js` and `moment.js` always. This helps the Exchange extension display public market data even if a user is not logged in.
    710 
    711 = 4.2.1 =
    712 - Improve: When calling version 3 of the JSON API using an API key, the `__wallets_user_id` GET argument no longer needs to be specified. It is inferred from the value of the secret key.
    713 
    714 = 4.2.0 =
    715 - Add: New capability `access_wallets_api` controls whether a user can access the JSON API using key-based authentication.
    716 - Add: Users with `access_wallets_api` are shown their API key in their user profile admin screen.
    717 - Add: The `get_nonces` JSON API endpoint returns the API key when accessed via browser (with cookie-based authentication).
    718 - Add: The `do_reset_apikey` JSON API endpoint can be used to reset the API key to a new random value.
    719 - Add: The JSON API can now be accessed with an API key. Details in the accompanying documentation.
    720 - Add: The `[wallets_api_key]` shortcode shows the API key to the user and allows the key to be reset.
    721 - Change: Withdrawals with the RPC-based coin adapters no longer fail in some edge cases where wallets are picky about the amount format ("Invalid amount error").
    722 
    723 = 4.1.0 =
    724 - Add: Control the frontend UI styling using the WordPress Customizer.
    725 - Fix: The JavaScript library `jquery-qrcode` is only loaded if the user is logged in and has the capability `has_wallets`.
    726 - Change: Now using the latest version `1.1.1` of the JavaScript library `sprintf.js`.
    727 
    728 = 4.0.6 =
    729 - Add: When unable to render a shortcode due to missing permissions or other problems, the error message displayed is now configurable via a WordPress filter.
    730 - Fix: The adapters list admin table no longer writes a warning to the logs if the total hot wallet balance is unavailable due to a bad network connection.
    731 - Add: The QR code in the `[wallets_deposit]` shortcode can contain a URI-style representation of the deposit address plus any optional payment id, if the coin adapter permits it.
    732 - Fix: Important bug with storing exchange rates from fixer.io to the DB, previously caused invalid exchange rates, now fixed.
    733 - Fix: The `[wallets_rates template="static"]` shortcode no longer displays an exchange rate of 1, between the selected fiat currency and itself.
    734 - Fix: Coin icons are now displayed in frontend UIs with the same size even if the files have different dimensions.
    735 
    736 = 4.0.5 =
    737 - Fix: Issue introduced in `4.0.0` that caused internal transactions to stay in pending state and not fail if there was not enough available balance.
    738 - Fix: Bug that prevented BuddyPress notifications from being sent.
    739 - Fix: Issue that caused a warning about cron jobs not running to show, if the admin visited the admin screens at the exact moment the cron job was running.
    740 - Add: Official dashed-slug twitter feed added to the *About* seection of the admin screens.
    741 
    742 = 4.0.4 =
    743 - Fix: Bug introduced in `4.0.3` that prevented admin from enabling `sweetalert.js` modal dialogs now fixed.
    744 - Add: The `[wallets_rates]` shortcode now takes an optional argument "decimals" to control how many decimal digits to display exchange rates with.
    745 - Improve: Code that parses fixer.io exchange rates improved. Will now consider the site-wide default fiat currency when requesting prices.
    746 - Fix: Parser for cryptocompare list of currencies is now safer (produces less warnings).
    747 - Fix: Parser for cryptocompare currency prices is now safer (produces less warnings).
    748 - Fix: Several PHPdoc errors and other minor bugs fixed using static code analysis with phan.
    749 - Change: Social links to Google+ page are now removed, since the platform is to be decommissioned.
    750 
    751 = 4.0.3 =
    752 - Add: Use sweetalert.js for modal dialogs. This can be turned off to fall back to standard JavaScript alerts.
    753 - Fix: When requesting specific coins by name from CoinGecko or CoinMarketCap, URL-encode the names to avoid issues with special characters.
    754 - Improve: Cron jobs prioritized so that critical tasks run first.
    755 - Fix: The `[wallets_deposit]` shortcode no longer displays fiat currency deposit reference codes.
    756 - Fix: The `[wallets_withdraw]` shortcode no longer displays fiat currency information and does not interfere with shortcodes intended for fiat currencies.
    757 - Fix: The Exchange rates data stored in the DB is now validated to make sure it is of type array. Addresses previous issue where debug view outputs were saved as string.
    758 - Fix: When withdrawing using a JSON-RPC wallet API, do not convert the amount to float. This solves precision/rounding errors that previously would raise an exception.
    759 
    760 = 4.0.2 =
    761 - Add: Verbosity option introduced in `4.0.1` now also writes memory debug info while processing email notifications and while executing transactions.
    762 - Fix: Fixed bug introduced in `4.0.0` in Exchange Rates admin screen where the "Save Changes" button would also save the debug view contents in the DB as options.
    763 
    764 = 4.0.1 =
    765 - Fix: Exchange rate provider responses do not get cached unless necessary. Reduces load on transient storage (DB).
    766 - Fix: Never run cron job more than once per request.
    767 - Add: Verbosity option controls whether memory debug info is written out to the WordPress log while running cron obs.
    768 - Add: Verbosity option controls whether memory debug info is written out to the WordPress log while retrieving exchange rate data.
    769 - Add: Display cron-job related memory debug info in the dashboard.
    770 - Add: Display PHP `memory_limit` ini setting and `WP_MEMORY_LIMIT` constant in dashboard.
    771 
    772 = 4.0.0 =
    773 - Add: New PHP API filter `wallets_api_available_balance` now retrieves the balance of a user/coin pair that is not currently reserved in pending transactions or exchange trades.
    774 - Add: When placing a new `move` or `withdraw` transaction, the new *available balance* is checked, rather than the total account balance.
    775 - Add: When executing a pending `move` or `withdraw` transaction, the new *available balance* is checked, rather than the total account balance.
    776 - Add: The `[wallets_balance]` shortcode now also displays the *available balance* if it is different from the *total balance*.
    777 - Add: The *user profile* section now displays both the total and the available balance for each coin that a user holds.
    778 - Add: The *User Balances* admin screen now displays both the total and the available balance for each coin that a user holds.
    779 - Change: Always show coin selection dropdown in frontend, even if only one coin is available.
    780 - Change: In `[wallets_withdraw]` UI only show coins that are not known to be fiat. For fiat withdrawals, use `[wallets_fiat_withdraw]` in the upcoming release of the Fiat Coin Adapter.
    781 - Change: CoinMarketCap exchange rates provider can now use an API key to conform with latest changes to the 3rd party API. Only retrieves information about enabled coins, thus reducing bandwidth requirements and improving performance. Falls back to retrieving exchange rates for top 100 coins if no API key is provided.
    782 - Improve: Coingecko exchange rates provider can now retrieve information about only enabled coins, thus reducing bandwidth requirements and improving performance.
    783 - Improve: In Exchange Rates admin page, the debug views contents can now be easily copied to the clipboard.
    784 - Improve: In cases where a theme has loaded an old version of knockout.js, the plugin does not strictly require the `ko.tasks` object. (However it is recommended that the latest version of knockout is used with the plugin.)
    785 - Change: When placing a new `move` or `withdraw` transaction, the plugin no longer uses MySQL table locks as these are not strictly necessary. The hazard for race conditions is at transaction execution, not placement.
    786 - Fix: `do_move` JSON API now accepts optional `__wallets_move_tags` argument again.
    787 - Improve: `do_move` and `do_withdraw` JSON APIs no longer write to logs if the *comment* optional argument is not specified.
    788 - Improve: `do_move` and `do_withdraw` JSON APIs check explicitly if required arguments are passed.
    789 - Change: Cron job is now using custom-built semaphore locks instead of relying on MySQL table locks when executing `move` and `withdraw` transactions. This allows for extensions to hook into the new filter `wallets_api_available_balance` and do DB queries while the protected code is running.
    790 - Improve: Compacted some CSS rules.
    791 
    792 = 3.9.4 =
    793 - Add: Can now force the plugin to generate deposit addresses for all users in advance.
    794 - Improve: When creating DB tables for the first time, the MySQL DB is explicitly instructed to use InnoDB.
    795 - Add: Warn admins if the DB tables are using a non-transactional DB storage engine, such as MyISAM.
    796 - Add: Warn admins if *W3 Total Cache* is misconfigured so that it can interfere with the JSON API.
    797 - Add: Warn admins if *WP Super Cache* is misconfigured so that it can interfere with the JSON API.
    798 - Fix: In the frontend withdrawal form UI, if no amount is entered, there is no longer a validation error shown.
    799 - Improve: If for some reason a wallet responds to a `getnewaddress` RPC command with an empty string (no address), this error is now logged.
    800 - Fix: Several errors related to email sending failures are now logged.
    801 - Fix: In deposit notification emails, the deposited amount is no longer shown as `0`.
    802 - Improve: If the notification API receives an invalid TXID or block hash, the TXID or hash is logged.
    803 
    804 = 3.9.3 =
    805 - Add: When performing a cold storage withdrawal, it is now possible to specify an extra destination argument such as Payment ID, Memo, etc.
    806 - Fix: When enabling an RPC coin adapter and unlocking it with a passphrase at the same time, the plugin no longer crashes.
    807 - Fix: Frontend coin images now have alt texts to conform to HTML standard.
    808 - Fix: After an internal transfer or a withdrawal is successfully submitted, the form UI no longer shows a validation error on the emptied amount field.
    809 - Fix: In the frontend internal transfers or withdrawals forms, there is now a validation error if the total amount to be transacted is less than what would need to be paid in fees.
    810 - Fix: The frontend error message "No currencies are currently enabled." is no longer momentarily shown before the currencies are loaded, but only if the information is loaded and there are actually no coins.
    811 - Fix: The *Disable transients (debug)* setting can now be updated in multisite installs.
    812 - Fix: The `wallets_coin_icon_url_SYMBOL` filters now affect coin icons as they are shown in menu items, static frontend UIs and in the cold storage admin section.
    813 - Fix: The notification actions `wallets_withdraw` and `wallets_move_*` now report the latest status and retries count for all transactions; previously the reported status was "pending".
    814 
    815 = 3.9.2 =
    816 - Fix: Eliminate a JS error caused when a theme has already loaded an old version of knockout.js.
    817 - Add: Show an error message in frontend UIs when there are no coin adapters online.
    818 - Fix: Can now cancel withdrawals again from the admin interface (bug was introduced in 3.9.0).
    819 - Fix: Eliminated some PHP warnings in the notifications mechanism.
    820 - Fix: The instructions for resolving connectivity to an RPC wallet now recommend using the latest JSON API version in the notification URLs.
    821 - Add: New debug option "transients broken" can now circumvent the use of transients throughout the plugin, at the cost of decreased performance.
    822 
    823 = 3.9.1 =
    824 - Fix: When activating/deactivating exchange rates providers, all rates are now deleted so that no stale rates remain in DB.
    825 - Improve: Coin adapters are now checked somewhat less frequently for a "responding" state to improve performance.
    826 - Improve: Remove some unneeded CSS from reload button.
    827 - Add: Link to EasyCron service.
    828 - Fix: Added guard clause to cron job that checks RPC wallets for past transactions. No longer logs a warning if no transactions are found.
    829 
    830 = 3.9.0 =
    831 - Add: New static templates for the following shortcodes: <code>[wallets_deposit]</code>, <code>[wallets_balance]</code>, <code>[wallets_transactions]</code>, <code>[wallets_account_value]</code>, <code>[wallets_rates]</code>.
    832 - Add: Static template for <code>[wallets_transactions]</code> can now filter displayed transactions based on categories and/or tags.
    833 - Improve: Widget form of the UIs is now refactored and improved. User input is accepted to reflect additions in allowed shortcode attributes.
    834 - Add: If a shortcode cannot be rendered due to some error, a meaningful error message is shown in the frontend.
    835 - Add: Admin table listing user balances.
    836 - Add: Admin table listing user deposit addresses.
    837 - Add: New PHP API endpoint to cancel transactions.
    838 - Add: New PHP API endpoint to retry cancelled transactions.
    839 - Fix: Custom menu item for displaying balances did not render correctly in twenty-nineteen theme, now fixed.
    840 - Add: Can now disable automatic cron job triggering, by setting "Run every" to "(never)".
    841 - Fix: Filter <code>wallets_api_deposit_address</code> now correctly checks the capabilities of the user when called with a user argument (other than current user).
    842 - Improve: Performance of admin transaction list rendering improved.
    843 - Improve: Performance improvements in exchange rates code, when the price of a coin in the default fiat currency is not the same as that provided by the exchange rate provider service.
    844 
    845 = 3.8.0 =
    846 - Improve: Massively simplified cron mechanism. All cron tasks are unified and they all run on shutdown.
    847 - Improve: Plugin bails out of executing cron tasks if PHP execution time is nearing <code>max_execution_time</code> minus 5 seconds.
    848 - Improve: Cron tasks can now be triggered via a custom URL. Useful in conjunction with <code>WP_DISABLE_CRON</code>.
    849 - Change: Cron tasks do not auto-trigger if trigerring is disabled. Instead, a warning is displayed.
    850 - Improve: On a network-activated multisite installation with too many blogs, the plugin will only process tasks for a few blogs on each run.
    851 - Fix: When an admin cancels a deposit, such as a fiat coin adapter deposit, the deposit can now be re-executed if the admin retries the deposit.
    852 - Fix: In deposit notifications/emails, the amount displayed is the net amount deposited (i.e. no longer includes the transaction fee.)
    853 - Fix: In the coin adapters list, the displayed total amount of fees paid no longer includes deposit fees, since these are external to the site's wallet.
    854 - Fix: On a network-activated multisite installation, the coin adapter setting "Min withdraw" is now saved.
    855 - Add: When a user is deleted by an admin, their transactions and deposit addresses are now deleted. Any user balance is deallocated and returns to the site.
    856 - Fix: Guard clause protects against warning for missing optional `qrsize` argument to the deposit widget.
    857 - Fix: The coin icon is now shown in the coin adapter settings admin page.
    858 - Improve: All amounts in the coin adapters list admin page are shown as dashes if equal to zero, to improve visibility of non-zero values.
    859 - Fix: When current user does not have the <code>has_wallets</code> capability, the balances menu item is not rendered.
    860 - Improve: Failed withdrawal notifications/emails now include any error messages originating from the wallet to aid in troubleshooting.
    861 - Change: Deposit QR codes are no longer rendered for fiat coins. The deposit codes are only shown as text.
    862 
    863 = 3.7.4 =
    864 - Improve: JavaScript var `walletsUserData.recommendApiVersion` can be used by extensions to access latest version of JSON API.
    865 - Add: Deposits can now be cancelled.
    866 - Add: Cron job can now auto-cancel transactions that have remained in an unconfirmed or pending state for too long (default: cancel after 24 hours).
    867 
    868 = 3.7.3 =
    869 - Fix: The `wallets_api_balance` filter now counts unconfirmed and pending withdrawals towards a user's current balance.
    870 - Change: Argument "confirmed" of the `wallets_api_balance` filter is removed. The filter always returns confirmed balances.
    871 - Change: When first activating the plugin, the built-in Bitcoin node adapter is disabled by default.
    872 - Fix: The `[wallets_rates]` shortcode no longer displays identity rates of the form XYZ = 1 XYZ.
    873 - Improve: Better application of a fix for themes that improperly use the Select2 library.
    874 
    875 = 3.7.2 =
    876 - Add: New option in cron settings, allows cron to only run if `HTTP_REFERER` is not set (i.e. if triggered from curl via a system cron). This can help with frontend performance.
    877 - Change: Exchange rates view (`[wallets_rates]`) now displays exchange rates with 4 decimals (previously 2).
    878 - Change: Maximum amount of cron batch size is now 1000. Comes with a warning about setting the value too high.
    879 - Add: Source maps for the minified versions of JavaScript code are now added and are available to browser debugging consoles.
    880 - Fix: Image for reload button now works even if the site home is in a subdirectory of the domain.
    881 - Fix: Footer in admin screens no longer blocks click events on elements that appear in the same row as the footer's panel.
    882 - Fix: Added timeouts in all AJAX calls; this should prevent `net::ERR_NETWORK_IO_SUSPENDED` errors.
    883 - Improve: Safer code in dashboard widget while detecting other installed dashed-slug extensions.
    884 
    885 = 3.7.1 =
    886 - Add: `[wallets_move]` and `[wallets_withdraw]` shortcodes now also display amount to be paid after fees.
    887 - Fix: Problem where QR code was not rendered on first page load, introduced in `3.7.0`.
    888 - Fix: Unicode glyph on reload button introduced in `3.7.0` was not visible on some mobile devices, is now an image.
    889 - Add: New filters introduced in `3.7.0` on the output of the `get_coins_info` JSON API now also affect older versions of the API.
    890 
    891 = 3.7.0 =
    892 - Change: JSON API latest version is now `3`. To use previous versions, first enabled legacy APIs in the plugin's settings.
    893 - Add: Users can now use the `[wallets_deposit]` UI to request a new deposit address. Old addresses are retained.
    894 - Add: New "Reload data from server" button on all UIs requests a fresh copy of displayed data from the server immediately.
    895 - Change: `get_coins_info` is no longer cached at the browser level. This allows for manual reload of server data.
    896 - Change: `get_coins_info` no longer returns the superfluous `deposit_address_qrcode_uri` field. The deposit address is used in QR codes directly. This saves on transmitted data.
    897 - Change: The Coin Adapter class no longer provides an adapter setting for minconf. This is done at the RPC Coin Adapter level. This has no effect to the end user at the moment.
    898 - Add: New filters provide ability to override coin name and coin icon URL as reported by `get_coins_info`. Examples in the accompanying documentation.
    899 
    900 = 3.6.7 =
    901 Fix: Important bug in balance calculation, introduced in `3.6.5` regarding withdrawal fees. All fees are now being properly accounted for.
    902 
    903 = 3.6.6 =
    904 - Fix: Change in DB schema allows installation on very old MySQL databases that don't allow over 1000 characters in index. (Error 1071: Specified key was too long)
    905 - Fix: Do not attempt to unlock RPC wallets with passphrase if coin adapter is disabled.
    906 
    907 = 3.6.5 =
    908 - Fix: Better balance algo, includes all trading fees in calculation.
    909 - Add: Deposits can now have comments (needed for upcoming fiat coin adapter).
    910 - Improve: moment.js localization now matches WordPress locale (affects all time translations, including faucet).
    911 - Improve: In *Exchange Rates* admin menu, exchange rates in debug views are sorted alphabetically, allowing easier inspection.
    912 - Improve: If plugin recieves notification about an invalid TXID or blockid, handles error silently, writing a warning to the logs.
    913 - Add: In user profiles screen, deposit addresses also display extra info such as Payment ID, Memo, etc.
    914 - Fix: In user profiles screen, deposit addresses are no longer shown as links if no explorer URI is available.
    915 
    916 = 3.6.4 =
    917 - Fix: Fees on deposits coming from the CoinPayments adapter were not subtracted from user balances. (Important!)
    918 - Fix: When the recipient of an internal transaction gets an email notification, the email now displays a positive amount, minus any fees paid by the sender.
    919 - Improve: DB table string columns are shorter. This allows for default Unicode collation to be used without hitting a limit on index size in MySQL < 5.6
    920 
    921 - Add: New introductory YouTube video added to readme file.
    922 
    923 = 3.6.3 =
    924 - Add: New filter `wallets_user_data` allows for adding data to JavaScript global variable `walletsUserData`.
    925 - Improve: Move and withdraw UIs are now based on an HTML table layout.
    926 - Improve: AJAX calls no longer pass unneeded path info to the request URI.
    927 - Improve: Safer loading of transaction UI fragments file (does not depend on current directory).
    928 - Improve: Updated to latest versions of all 3rd party libraries: `bs58check` 2.1.2, `moment.js` 2.22.2, latest `jquery-qrcode`.
    929 - Improve: Dismissible notices in the admin screens now respect the `DISABLE_NAG_NOTICES` constant.
    930 - Fix: Can now set a minimum confirmation count in coin adapter settings when plugin is network-activated on a multisite install.
    931 - Fix: All DB queries are now prepended by a flush of the DB object. Error reporting can no longer report stale DB errors from previous queries.
    932 - Fix: Some minor HTML validation errors now fixed.
    933 - Fix: Coin icons in dropdowns and menu items now all display in the same size.
    934 
    935 = 3.6.2 =
    936 - Fix: Invalid HTML in `[wallets_balance template="list"]` was causing problems with page layout.
    937 - Add: `[wallets_deposit]` shortcode accepts optional argument `qrsize` to set dimension of QR code in pixels. e.g. `[wallets_deposit qrsize="120"]`
    938 - Improve: `[wallets_transactions template="default"]` is now rendered more efficiently thanks to `<!-- ko if -->` statements.
    939 
    940 = 3.6.1 =
    941 - Improve: Import/export CSV function now lets an admin to export transactions and reimport them to a new system where the users have different user IDs. Users are represented by emails in the CSV file.
    942 - Improve: Debug info in Dashboard screen is only shown to users with the `manage_wallets` or `activate_plugins` capability (i.e. admins).
    943 - Fix: Typo in `[wallets_transactions]` shortcode where the "wa" string was erroneously included in the markup.
    944 
    945 = 3.6.0 =
    946 - Add: The `default` template of the `[wallets_transactions]` shortcode now accepts a list of columns as an optional argument.
    947 - Add: New shortcode `[wallets_rates]` displays exchange rates of online coins.
    948 - Add: New shortcode `[wallets_total_balances]` displays the total sum of user balances per each coin.
    949 - Add: When a transaction fails due to an error, the admin or admins can be notified by email.
    950 - Add: When a transaction requires admin confirmation, the admin or admins can be notified by email.
    951 - Add: When a user is about to receive an internal transaction that is not yet approved, the recipient user can be notified by email.
    952 - Add: Administrator can set all unconfirmed transactions to be auto-confirmed after a specified number of days.
    953 - Add: *Transactions* page in admin screens now has a new column, amount without fees.
    954 - Improve: In *Transactions* page, long tx comments are now displayed with ellipsis to save screen space. Hover with the mouse to see entire text.
    955 - Add: The *Bitcoin and Altcoin Wallets* section in a user's profile screen can be hidden. A new capability, `view_wallets_profile` controls this.
    956 - Add: Adapters list admin screen now has a new column that shows total amount of fees paid to the site wallet.
    957 - Fix: Cryptocompare.com exchange rates provider no longer generates an invalid API call when no coins are enabled.
    958 
    959 = 3.5.6 =
    960 - Improve: Adapters list now warns user if more than 99% of hot wallet coins are not available, such as when staking entire balance.
    961 - Improve: In RPC (full node) coin adapters, the calls `get_balance()` and `get_unavailable_balance()` are cached for performance.
    962 - Improve: In RPC (full node) coin adapters, performance of the discovery of past TXIDs via `listtransactions` is vastly improved.
    963 - Improve: In RPC (full node) coin adapters, discovery of past TXIDs no longer uses `listreceivedbyaddress` or `listunspent` as they are redundant.
    964 - Change: DB schema now allows coin symbols with up to 8 characters (was 5).
    965 - Fix: JSON API calls now allow coin symbols that contain digits (0-9).
    966 - Add: Balances list view (`[wallets_balance view="list"]`) now also displays fiat amounts if possible.
    967 - Fix: When a transaction is performed without a comment attached, the comment is now shown as 'n/a' in notifications.
    968 - Add: Suggestion in admin screens footer for rating the plugin on WordPress.org.
    969 
    970 = 3.5.5 =
    971 - Add: User can explicitly select default fiat currency to be "none" or "site default".
    972 - Add: Admin can explicitly select default fiat currency to be "none".
    973 - Add: If effective fiat currency is "none", make sure that no fiat amounts are displayed.
    974 - Fix: QR code in the `[wallets_deposit]` shortcode no longer exceeds boundaries if drawing area is small.
    975 - Fix: Notification messages no longer display coin symbols twice next to transacted amounts.
    976 - Add: Plugin "About" section and `readme.txt` now know about the Exchange extension.
    977 
    978 = 3.5.4 =
    979 - Add: Exchange rates provider for coingecko.com
    980 - Add: Coin adapters list displays wallet balance unavailable for withdrawal next to available wallet balance.
    981 - Improve: Successful cold storage withdrawals now report TXID. Message includes links for address and TXID to relevant blockexplorer.
    982 - Fix: Bug in checkbox under full node coin adapter settings about skipping rewards generated from mining (introduced in 3.5.3).
    983 
    984 = 3.5.3 =
    985 - Add: Full node coin adapters now skip rewards generated from mining. PoS rewards must be skipped, PoW rewards can be included.
    986 
    987 = 3.5.2 =
    988 - Fix: Issue with generated rewards for PoS coins, that previously appeared as extra deposits.
    989 - Add: Exchange rates provider for cryptocompare.com
    990 - Change: Default exchange rates providers after first installing the plugin are fixer and cryptocompare.
    991 - Add: Email notifications can be turned off for individual users via their profile admin page.
    992 - Fix: User profile pages only display wallets-related section for users with `has_wallets` capability.
    993 - Change: QR-code URIs for most coins only include address string and no name. This is safer. Coins that require a full URI still have it.
    994 
    995 = 3.5.1 =
    996 - Add: Can now hook to frontend events for running JavaScript after coin data is loaded. See documentation for details.
    997 - Improve: Frontend UIs now start with 50% opacity while coin data is not yet loaded from the JSON API.
    998 
    999 = 3.5.0 =
    1000 - Add: Support for keeping an "Audit Log" of transactions using the plugin "Simple History", if the plugin is installed.
    1001 - Add: Deposit address can be copied to clipboard in one click in the `[wallets_deposit]` shortcode UI.
    1002 - Add: Debug info in dashboard widget now lists versions of all installed extensions.
    1003 - Add: When clicking on "Renew deposit addresses" there is now a confirmation prompt.
    1004 - Change: Removed the unused `amount_str` and `fee_str` fields from the `get_coins_info` JSON API call to save bandwidth.
    1005 - Fix: Bug when renewing deposit addresses where action GET argument would remain in admin URL. Now argument is removed with redirect.
    1006 - Fix: Backend no longer inserts a request for withdrawal with no address specified.
    1007 - Fix: No longer uses USDT_BTC for USD_BTC exchange rate for Bittrex.
    1008 - Improve: "Cron job might be disabled" warning only shows after 4 hours of no cron. This avoids most false positives in dev environments.
    1009 - Fix: Added `rel="noopener noreferrer"` to all external redirect links with `target="_blank"`.
    1010 - Change: Added Google analytics tracking codes to all links to dashed-slug.net for BI.
    1011 - Improve: Added `required="required"` to admin input fields that are required to have a value.
    1012 - Improve: Added `required="required"` to frontend input fields that are required to have a value.
    1013 - Improve: Notifications code refactored and improved.
    1014 - Improve: Applied many code styling suggestions using CodeSniffer and the WordPress ruleset.
    1015 - Improve: Information in readme.txt is more up-to-date.
    1016 
    1017 = 3.4.2 =
    1018 Fix: Race condition hazard that could compromise the security of this plugin now fixed. This is an IMPORTANT SECURITY UPDATE.
    1019 
    1020 = 3.4.1 =
    1021 - Fix: Admin can now select to not use any exchange rates if not needed.
    1022 - Fix: More correct algorithm for calculating exchange rate between any two currencies. Does graph traversal and finds a path between known exchange rates.
    1023 - Change: If a fiat currency has the same symbol as a known cryptocurrency, its exchange rate data is discarded to avoid confusing the rate calculations.
    1024 - Fix: User preference for a fiat currency now takes precedence again over site-wide default.
    1025 
    1026 = 3.4.0 =
    1027 - Change: To use the fixer.io service users must now provide an API key. This is now possible.
    1028 - Change: The fixer.io service is accessed at most once per hour.
    1029 - Improve: Can now enable multiple exchange rates providers simultaneously.
    1030 - Change: Simplified hooks for adding exchange rates manually. See https://gist.github.com/alex-georgiou/492196184f206002c864225180ca8fbb
    1031 - Improve: When an exchange rates provider is disabled, its data remains on the DB, while any data that comes from enabled providers is kept updated.
    1032 - Improve: Exchange rates admin page now displays data counts to aid debugging.
    1033 
    1034 = 3.3.6 =
    1035 - Fix: Prevent SQL error on failed transactions "BIGINT UNSIGNED value is out of range".
    1036 
    1037 = 3.3.5 =
    1038 - Fix: Prevent browser caches from retrieving old assets (js,css). Plugin version is now part of filenames as well as in the `ver` GET parameter. Solves problems with some CDNs and plugins that discard the version parameter.
    1039 - Add: Better schema index checks. Will report an error to the admin if any DB constraint is not in place.
    1040 - Improve: Withdrawals are now first marked as done and then actually performed. If wallet returns error then withdrawal is marked as failed. Prevents double spend in the very unlikely event of a network disconnect while the transaction is being sent to the wallet.
    1041 - Fix: Division by zero error fixed in the Cold Storage deposit screens.
    1042 - Fix: For coins that have extra info (e.g. Monero Payment ID, Ripple Destination Tag), display both in Cold Storage deposit screen.
    1043 
    1044 = 3.3.4 =
    1045 - Fix: Bug that prevented updating confirmation counts of deposits coming from transactions with multiple outputs, introduced in 3.3.2.
    1046 
    1047 = 3.3.3 =
    1048 - Improve: Front-end performance increase due to deferred updates in knockout framework.
    1049 - Fix: Erroneous "Insufficient Balance" validator message in frontend when balance is actually sufficient.
    1050 - Fix: A CSS issue with the frontend validator messages that would cause visual elements to jump up and down on the page.
    1051 - Improve: Updated packages moment.js library to the latest version.
    1052 - Improve: If a transaction cannot be inserted to the DB, also print out the last DB error message in the logs to assist debugging.
    1053 
    1054 = 3.3.2 =
    1055 - Fix: Allow incoming transactions with multiple outputs, where the outputs are deposit addresses for more than one users of the plugin.
    1056 
    1057 = 3.3.1 =
    1058 - Change: Transaction time in *Wallets* &rarr; *Transactions* list is now shown in local timezone, not UTC.
    1059 - Add: Transaction time in email notifications can now be shown in local timezone with value ###CREATED_TIME_LOCAL###.
    1060 - Add: Transaction time in email confirmations can now be shown in local timezone ###CREATED_TIME_LOCAL###.
    1061 - Add: Widgets can now be used with alternative UI templates.
    1062 - Add: The sender's name and address for email notifications and confirmations can now be set in the admin settings. If set, it overrides the default.
    1063 - Change: Proportional fees in all RPC adapters (including the multiadapter extension) now have five decimal places instead of three.
    1064 
    1065 = 3.3.0 =
    1066 - Add: Suggests a text fragment for inclusion into the site's privacy policy (GDPR requirement).
    1067 - Add: Hooks into the personal data exporter tool and exports a user's deposit addresses and transaction IDs (GDPR requirement).
    1068 - Add: Hooks into the personal data eraser tool and deletes a user's deposit addresses and transaction IDs (GDPR requirement).
    1069 - Add: Admin transactions list can now be sorted by: status, admin confirmation, user confirmation. Thanks to James (Tiranad @ BTCDraft) for providing patch.
    1070 - Fix: When the `[wallets_move]` form fields are reset to empty, after a successful transaction request, the user field is also reset to empty.
    1071 - Improve: Hides some columns from upcoming "trade" transactions that will become relevant when the trading extension is released.
    1072 
    1073 = 3.2.0 =
    1074 - Add: Shortcodes now take extra attribute, allow for choosing alternative UI templates.
    1075 - Add: Alternative transactions view with `[wallets_transactions template="rows"]`.
    1076 - Add: Alternative balances view as list with `[wallets_balance template="list"]`.
    1077 - Add: Alternative deposit addresses view as list with `[wallets_deposit template="list"]`.
    1078 - Add: Can now set minimum withdrawal amount as a coin adapter setting. Enforced in frontend validation and backend processing.
    1079 - Improve: Frontend withdraw and move UIs now validate amounts against max user balance.
    1080 - Change: `get_coins_info` JSON API now returns the list of coins sorted by name.
    1081 - Fix: Bug in cold storage admin screens for multisite intstallations.
    1082 - Fix: More cross-compatible DDL phrasing for enum value in SQL schema.
    1083 
    1084 = 3.1.3 =
    1085 - Add: New shortcode `[wallets_account_value]` displays the total account value in the selected fiat currency.
    1086 - Improve: Display TXIDs and addresses as links only if they are alphanumeric, in frontent and backend transaction lists.
    1087 - Fix: Some strings now made translatable.
    1088 
    1089 = 3.1.2 =
    1090 - Fix: Incompatibility with frontend JavaScript code and Internet Explorer 11.
    1091 - Improve: Old transaction aggregation is less verbose in the logs. Does not write anything if there are no transactions to aggregate.
    1092 - Improve: Frontend form submit buttons are not clickable while there are other pending queries. This prevents accidental multiple submits of the same tx.
    1093 
    1094 = 3.1.1 =
    1095 - Fix: Non-default DB table prefix in old transaction aggregation cron job, introduced in 3.1.0.
    1096 
    1097 = 3.1.0 =
    1098 - Add: Old transaction aggregation cron job to save DB space.
    1099 - Add: Easily refresh deposit addresses via the adapters list screen.
    1100 - Fix: Better guard clause in Bitcoin withdrawal address validator JavaScript.
    1101 
    1102 = 3.0.3 =
    1103 - Fix: Better logic that controls flushing of JSON API rewrite rules. This had caused incompatibility with "multilanguage" plugin by BestWebSoft.
    1104 - Improve: The `[wallets_transactions]` UI no longer displays an empty table if there are no transactions to display. A dash is shown instead.
    1105 - Add: The debug info widget in the admin dashboard now reports the web server name and version.
    1106 - Change: Internal support for "trade" transactions. These will be needed for the upcoming exchange extension.
    1107 
    1108 = 3.0.2 =
    1109 - Add: Exchange rates can now be pulled from the CoinMarketCap API.
    1110 - Add: Coin icons are now displayed in the front-end UIs.
    1111 - Fix: Safer exchange rates code in case of connectivity issues.
    1112 - Fix: No longer display "cancel" button next to deposits, since these cannot be cancelled.
    1113 - Fix: No longer reset the default coin in the frontend whenever the coin info is reloaded.
    1114 - Change: The readme now points to the new SEO-frinedly name for the YouTube channel.
    1115 
    1116 = 3.0.1 =
    1117 - Fix: Do not throw an alert box error in frontend when an AJAX request is cancelled by the browser, if the user clicks on a new link while the request is in transit.
    1118 
    1119 = 3.0.0 =
    1120 - Add: New improved PHP API for working with wallets, based on WordPress actions and filters. See documentation for details.
    1121 - Change: The previous PHP API is still functional but is now marked as deprecated.
    1122 - Add: The JSON APIs are now versioned, to allow for stable improvements.
    1123 - Add: New version 2 of the JSON API does not include the `get_users_info` call which divulged user login names. Accepts usernames or emails as destination user in `do_move` action.
    1124 - Change: Previous version 1 of the JSON API is available only if "legacy APIs" option is enabled in the frontend settings.
    1125 - Improve: Frontend no longer performs synchronous AJAX requests on the main thread. This fixes the issue where the UI would temporarily "freeze".
    1126 - Improve: The `[wallets_move]` shortcode now accepts the recipient user as a username or email. This was previously a dropdown and was causing scaling problems.
    1127 - Improve: The coins data structure in the wallets frontend is now indexed, resulting in better JavaScript performance throughout the frontend code.
    1128 - Fix: Nonces provided with the `get_nonces` JSON API call are no longer cached. Caching would sometimes cause stale nonces to be used, resulting in request forgery errors.
    1129 - Improve: The knockout JavaScript code now uses the `rateLimit` extender in favor of the deprecated `throttle` extender.
    1130 
    1131 = 2.13.7 =
    1132 - Improve: More kinds of transactions can be cancelled via the admin interface.
    1133 - Improve: More kinds of transactions can be retried via the admin interface.
    1134 - Fix: Avoid race condition that sometimes prevented the fix to the Select2 issue originally addressed in 2.13.5 .
    1135 - Fix: Make sure that JavaScript withdrawal address validators are always functions before calling them.
    1136 - Fix: The option to switch off frontend reloading of coin info when page regains visibility can now be changed in multisite installs.
    1137 
    1138 = 2.13.6 =
    1139 - Add: Added stocks.exchange exchange rates provider.
    1140 - Add: Option to switch off frontend reloading of coin info when page regains visibility.
    1141 - Add: Spanish language translation for frontend contributed by Javier Enrique Vargas Parra <jevargas@uniandes.edu.co>.
    1142 - Change: NovaExchange rates provider re-enabled after announcement that the exchange will not be decommissioned.
    1143 - Improve: Multiple calls to the same exchange rates API endpoint are no longer repeated.
    1144 - Improve: Suggested curl notify commands for full node wallets now include the -k switch to bypass problems with invalid SSL certificates.
    1145 
    1146 = 2.13.5 =
    1147 - Fix: User no more allowed to enter invalid polling intervals such as an empty string, resulting in frontend slowdown.
    1148 - Fix: The filter `wallets__sprintf_pattern_XYZ` modifies the amounts display pattern in the `[wallets_transactions]` shortcode.
    1149 - Fix: The filter `wallets__sprintf_pattern_XYZ` modifies the amounts display pattern in the special balances menu item.
    1150 - Fix: Dropdowns in front-end are now not affected by the Select2 JavaScript library (compatibility with AdForest theme and possibly more).
    1151 - Add: Transaction category and status values are now translatable and filterable in the `[wallets_transactions]` shortcode.
    1152 - Improve: Updated Greek language translation to reflect changes above.
    1153 
    1154 = 2.13.4 =
    1155 - Add: Frontend sprintf pattern for cryptocurrency amounts can now be overridden via a WordPress filter (see manual).
    1156 - Fix: Improved detection of wallet lock status for wallets that have support only for `getinfo` command and not `getwalletinfo`.
    1157 
    1158 = 2.13.3 =
    1159 - Improve: No longer requires the mbstring PHP module to be installed.
    1160 - Add: Live polling on the frontend can now be turned off by setting the time intervals to 0.
    1161 - Add: The debug panel in the admin dashboard now reports if PHP modules relevant to the plugin are loaded or not.
    1162 - Add: The debug panel in the admin dashboard now reports which plugin extensions are activated or network-activated.
    1163 
    1164 = 2.13.2 =
    1165 - Add: Admin option to manually disable JSON API response compression with zlib.
    1166 - Improve: Zlib compression status is not altered if HTTP response headers are already sent.
    1167 
    1168 = 2.13.1 =
    1169 - Add: After confirming a transaction via an email link, the user can be redirected to a page that the admin indicates. See Wallets &rarr; Confirms &rarr; Redirect after confirmation.
    1170 - Improve: Semantic HTTP status codes returned after clicking on confirmation links.
    1171 - Improve: Frontend does not popup an error if some wallet capabilities are disabled.
    1172 - Improve: JSON API uses compressed encoding if the UA accepts it and the PHP zlib extension is installed.
    1173 - Improve: Some internal code improvements in the adapter list.
    1174 
    1175 = 2.13.0 =
    1176 - Add: Coin adapters can be in a locked or unlocked state. Locked adapters cannot process withdrawals. Adapters can be unlocked by entering a secret PIN or passphrase.
    1177 - Add: All frontend text is now modifiable via WordPress filters. See the documentation for filter names and example code.
    1178 - Improve: Successful and failed transactions trigger WordPress actions. See the documentation for action names and example code.
    1179 - Fix: An incompatibility with PHP 5.4 is resolved. Note that it is not recommended to install the plugin on PHP versions that have reached end-of-life.
    1180 - Add: WordPress actions allow themes to add markup before and after any frontend UI form. See the documentation for action names.
    1181 - Fix: Internal transaction IDs no longer link to any block explorers.
    1182 - Add: After submitting a transaction, the user is notified to check their e-mail, if an e-mail confirmation is required.
    1183 - Add: Dismissible notice informing users to upgrade the cloud wallet adapters for compatibility with this version.
    1184 
    1185 = 2.12.2 =
    1186 - Fix: Disallow internal transactions and withdrawals if amount - fees is negative.
    1187 - Fix: 'Invalid amount' error when withdrawing invalid amount to RPC adapters - contributed by https://github.com/itpao25
    1188 - Fix: Better CSS selector specificity in `[wallets_transactions]` rows. Solves issues with some themes.
    1189 
    1190 = 2.12.1 =
    1191 - Improve: The `[wallets_balance]` shortcode shows fiat amounts below the actual crypto amount, not on mouse hover.
    1192 - Improve: The `[wallets_move]` and `[wallets_withdraw]` shortcodes do not show ugly NaN (Not a Number) values on insufficient data.
    1193 - Fix: The `[wallets_deposit]` shortcode would not show the QR-Code on first page load, before the current coin was changed. Now fixed.
    1194 - Fix: The exchange rates API is now extendable. For sample code see http://adbilty.me/HBVX5tx
    1195 
    1196 = 2.12.0 =
    1197 - Add: Frontend now displays up-to-date information via polling. Polling intervals are controlled by the admin.
    1198 - Change: The QR-code on/off switch is now found in the new *Frontend Settings* admin screen.
    1199 - Add: Admin can now choose a default fiat currency for users who have not made a selection in their WordPress profile screens.
    1200 - Fix: Error when withdrawing from unlocked RPC wallets (i.e. without a passphrase)
    1201 - Add: Arabic language translation for frontend contributed by Ed <support@2gogifts.com>
    1202 - Improve: Nonces API is now filterable by extensions. Filter name is: `wallets_api_nonces`.
    1203 
    1204 = 2.11.2 =
    1205 - Fix: Prices for Bitcoin cash reported by some exchanges as "BCC" are now changed to "BCH" to avoid confusion with BitConnect.
    1206 - Fix: Bug when saving buddypress notifications in multisite.
    1207 - Change: JSON API now does not throw when encountering an unknown action. Allows for extensions to define their own actions.
    1208 
    1209 = 2.11.1 =
    1210 - Fix: Deposit fees were not being inserted to the DB (would affect the CoinPayments adapter).
    1211 - Improve: In network-activated multisite, exchange rates are now shared accross sites. Improves performance.
    1212 - Fix: When user has not selected a base currency in their profile, the default is now USD. Previously was undefined, which caused fiat amounts to not be displayed.
    1213 - Fix: When user profile displays deposit addresses, it can now also handle currencies with an extra payment id field in their deposit address. (affects Monero, Ripple, Steem, etc).
    1214 - Fix: The default withdraw fees for Bitcoin core are now set to 0.001 when first installing the plugin.
    1215 
    1216 = 2.11.0 =
    1217 - Add: Addresses and TXIDs are now links to blockexplorer sites.
    1218 - Add: Cryptocurrency amounts are also shown in a user-selected fiat currency, default: USD.
    1219 - Improve: Comment fields are now multi-line, allow for more info.
    1220 - Add: All RPC adapters can now connect to wallets that are encrypted with a passphrase.
    1221 - Add: All RPC adapters can now connect to wallets via SSL RPC.
    1222 - Fix: Exchange rates caching mechanism would some times report stale data, is now fixed.
    1223 
    1224 = 2.10.6 =
    1225 - Fix: Widget titles are now translatable.
    1226 - Fix: Exceptions thrown by coin adapters no longer break user profile rendering.
    1227 - Add: German translations for frontend contributed by eMark Team <kontakt@deutsche-emark.de>.
    1228 
    1229 = 2.10.5 =
    1230 - Fix: Plugin now works even if theme causes the frontend `wp` JavaScript object to not exist.
    1231 - Fix: String localization is now working.
    1232 - Add: String localization now split into frontend and backend. See documentation for details.
    1233 - Add: Greek language translations for frontend.
    1234 
    1235 = 2.10.4 =
    1236 - Fix: Setting capabilities in network-activated multisite installs now modifies capabilities accross the network.
    1237 - Add: Plugin warns user if needed PHP extensions are not installed.
    1238 - Add: Admins can now view their own deposit addresses and balances in their user profile screen.
    1239 - Improve: Bumped included `bs58check` library from 2.0.2 to 2.1.0.
    1240 
    1241 = 2.10.3 =
    1242 - Add: Admins with `manage_wallets` can now view deposit addresses and balances of users in the user profile screen.
    1243 - Improve: Better cache control in the JSON API.
    1244 - Fix: Bug with the `get_transactions` API where data was not returned when using the friendly URI form instead of GET parameters.
    1245 - Fix: Warnings, errors and other notices that are relevant to wallet managers are now only shown to users with `manage_wallets`.
    1246 - Fix: Invalid `get_transactions` JSON API request with NaN argument while the frontend UI initializes.
    1247 - Add: Instructions for downloading the documentation added in the about section.
    1248 
    1249 = 2.10.2 =
    1250 - Add: Exchange rates are now available over tor. Only enable this if running WordPress on an Onion hidden service.
    1251 - Add: Exchange rates can be turned off if not needed to improve performance.
    1252 - Add: User is warned if the DB table indices are corrupted.
    1253 - Improve: Adding/updating transaction rows does not depend on the DB constraints.
    1254 - Improve: Exchange rates are decompressed using PHP curl, not via the compress.zlib filter.
    1255 - Fix: Misleading textual description of the IP setting in RPC adapters.
    1256 - Fix: Small bug with error reporting in JSON adapters.
    1257 
    1258 = 2.10.1 =
    1259 - Fix: More DB columns changed to ASCII. Saves space, plus fixes "Specified key was too long" on some databases and server locales.
    1260 - Improve: Frontend observables `withdraw_fee` and `move_fee` changed to camelCase to match other observables.
    1261 - Add: Debug log markers at uninstall script boundaries. Should aid in troubleshooting.
    1262 
    1263 = 2.10.0 =
    1264 - Improve: Better knockout bindings in frontend. Bindings applied to UI elements only, not entire page. Allows for playing nice with other knockout code.
    1265 - Add: The wallets viewmodel is now available for inheritance under the global wp object. Allows for extensions that modify the UI.
    1266 - Add: Tradesatoshi added to list of available exchange rate providers.
    1267 - Fix: Issue where database tables were not created on new installs.
    1268 - Fix: Race condition between uninstall script and cron job that caused unaccepted transactions to transition into pending state.
    1269 - Improve: Bumped the included knockout distribution to latest version, 3.5.0-pre.
    1270 
    1271 = 2.9.0 =
    1272 - Add: Notifications can now be sent either as emails or as BuddyPress private messages (or both).
    1273 - Fix: When upgrading database schema, suppress logging of some errors that are to be expected.
    1274 
    1275 = 2.8.2 =
    1276 - Fix: Bug introduced in 2.8.1 where deposits could be duplicated in some situations.
    1277 
    1278 = 2.8.1 =
    1279 - Add: Changes throughout the plugin for currencies that use additional information in transactions besides a destination address (e.g. Monero, Ripple, etc).
    1280 - Fix: Some issues with language domains in translated strings.
    1281 - Fix: QR code only shown for currencies where deposit makes sense.
    1282 - Add: NovaExchange will be shown as unavailable as an exchange rate provider after 2018-02-28.
    1283 
    1284 = 2.8.0 =
    1285 - Add: Admins can cancel internal transactions.
    1286 - Add: Admins can retry cancelled internal transactions.
    1287 - Improve: Exchange rates are now not slowing down the system. Better caching mechanism. Runs on PHP shutdown.
    1288 - Add: YoBit and Cryptopia exchanges added as exchange rate sources.
    1289 - Add: Exchange rate sources are now pluggable (see PDF documentation).
    1290 - Add: Dashboard debug info now includes commit hash and plugin version.
    1291 - Fix: Bug with failsafe mechanism for systems where WP Cron is not running, introduced in 2.7.4
    1292 - Improve: When not connected, the internal Bitcoin core plugin now suggests a salted password for the bitcoin.conf file, using the rpcauth= argument.
    1293 
    1294 = 2.7.4 =
    1295 - Add: Failsafe mechanism for systems where WP Cron is not running.
    1296 - Add: Panel with useful system debug info in Dashboard area. Users can copy and paste it when requesting support.
    1297 - Add: Show warning about old WordPress or PHP versions.
    1298 
    1299 = 2.7.3 =
    1300 - Fix: Incompatibility with PHP 5.3 introduced in 2.7.2.
    1301 - Improve: More efficient pulling of bittrex exchange rates.
    1302 
    1303 = 2.7.2 =
    1304 - Add: Exchange rates API now uses a choice of Bittrex, Poloniex or Novaexchange APIs.
    1305 - Add: Blockchain.info donation button in about section.
    1306 - Add: SteamIt social link in about section.
    1307 
    1308 = 2.7.1 =
    1309 - Fix: Bug where wrong coin address was displayed in cold storage section.
    1310 - Add: Cold storage section now links to wiki page.
    1311 - Add: All extensions now listed in About section.
    1312 
    1313 = 2.7.0 =
    1314 - Add: Cold storage section, allowing easy addition and withdrawal of funds to and from external wallets.
    1315 - Improve: Uninstalling and re-installing the plugin now fixes the SQL table schemas if they are missing or damaged.
    1316 
    1317 = 2.6.3 =
    1318 - Add: When coin adapters report a new status for an existing transaction, the plugin can now update the status of the transaction.
    1319 
    1320 = 2.6.2 =
    1321 - Fix: SQL formatting issue.
    1322 - Add: Text descriptions for adapter HTTP settings.
    1323 - Add: JSON coin adapter base class now does more verbose error reporting on API communication errors.
    1324 - Improve: Actions in transactions list admin screen are now buttons.
    1325 
    1326 = 2.6.1 =
    1327 - Fix: Query formatting issue.
    1328 
    1329 = 2.6.0 =
    1330 - Fix: Added back Knockout.js that was missing due to changes in 2.5.4 (oops!)
    1331 - Add: Functions for pulling exchange rates are now in wallets core, available for all extensions.
    1332 
    1333 = 2.5.4 =
    1334 - Fix: `do_move()` checks the balance of sender, not current user.
    1335 - Fix: Menu item now shows balance(s) of current user, not total wallet balance(s).
    1336 - Improve: Knockout.js assets are now local, not served from CDN.
    1337 - Add: FAQ section about supported coins.
    1338 
    1339 = 2.5.3 =
    1340 - Fix: Issues with frontend JavaScript code that would prevent popups from being displayed.
    1341 - Fix: Issue where in some situations cached assets (JS, CSS) from older plugin versions were being used.
    1342 - Improve: Better markup for balances menu item.
    1343 - Add: Many common questions added to the FAQ section.
    1344 
    1345 = 2.5.2 =
    1346 - Fix: Compatibility issue with PHP < 5.5
    1347 - Fix: More correct markup in balances nav menu item.
    1348 
    1349 = 2.5.1 =
    1350 - Fix: Minor JavaScript issue that prevented the frontend from working correctly with some coin adapters.
    1351 
    1352 = 2.5.0 =
    1353 - Add: Balance information can now be inserted into WordPress menus. See *Appearance* &rarr; *Menus*.
    1354 - Add: Pluggable validation mechanism for withdrawal addresses. Bitcoin addresses validated against `bs58check`.
    1355 - Add: Fees to be paid are now updated dynamically as soon as a user types in an amount.
    1356 - Improve: Massive refactoring in the knockout.js code.
    1357 - Fix: get_balance memoization now works correctly for multiple invocations of different users.
    1358 
    1359 = 2.4.6 =
    1360 - Fix: Bug in balance checks.
    1361 
    1362 = 2.4.5 =
    1363 - Improve: Paid fees are now deducted from the amount that users enter in the withdrawal and internal transfer UIs.
    1364 - Add: Fees now have a fixed component and a component that is proportional to the transacted amount.
    1365 - Add: Coin adapter settings now display descriptions.
    1366 
    1367 = 2.4.4 =
    1368 - Improve: Adapters now live in their own special panel.
    1369 - Add: About page with social actions and latest news.
    1370 - Add: Doublecheck to see if WordPress cron is executing and inform user if not.
    1371 
    1372 = 2.4.3 =
    1373 - Improve: Adapter list now shows both funds in wallets and funds in user accounts
    1374 - Improve: In adapters list, coin name, coin icon and coin symbol are now merged into one "Coin" column
    1375 - Add: Usernames in transaction list are links to user profiles
    1376 - Add: Link to support forum from plugin list
    1377 - Add: Added mention of Electrum coin adapter in FAQ section
    1378 
    1379 = 2.4.2 =
    1380 - Improve: `get_new_address()` deprecated in favor of `get_deposit_address()`.
    1381 - Add: `do_move()` can now do a funds transfer from users other than the current one.
    1382 - Fix: Bug where a DB transaction started after a funds transfer is now fixed.
    1383 
    1384 = 2.4.1 =
    1385 - Fix: When performing actions in transactions admin panel, redirect to that same panel without the action arguments (allows page refresh).
    1386 - Add: PHPdoc for new helper functions introduced in 2.4.0
    1387 - Add: Text warning about security best practices regarding RPC API communications over untrusted networks.
    1388 
    1389 
    1390 = 2.4.0 =
    1391 - Add: On multisite installs, the plugin can be *network-activated*.
    1392 - Add: Feature extensions (WooCommerce, EventsManager, Tip the Author, etc) can now place withdrawals or transfers that do not require confirmations.
    1393 - Fix: Broken "Settings" link in plugins list replaced with a working "Wallets" link.
    1394 
    1395 = 2.3.6 =
    1396 - Add: When a user requests to withdraw to a known deposit address of another user, an internal move transaction is performed instead.
    1397 - Improve: Frontend transactions in `[wallets_transactions]` are sorted by descending created time.
    1398 - Improve: Admin transactions list defaults to sorted by descending created time.
    1399 - Add: If a coin adapter does not override the sprintf format for amounts, the format now includes the coin's symbol letters.
    1400 - Fix: Uncaught exception when user-unapproving a transaction in admin when it corresponds to a currently disabled adapter.
    1401 - Fix: Uncaught exception when performing `wallets_transaction` action on a currently disabled adapter.
    1402 - Fix: Suppress a logs warning in `Dashed_Slug_Wallets_Coin_Adapter::server_ip()`.
    1403 
    1404 = 2.3.5 =
    1405 - Fix: Withdrawals to addresses that are also deposit addresses on the same system are no longer allowed.
    1406 - Fix: Email notifications for successful withdrawals now correctly report the transaction ID.
    1407 - Fix: Email notifications for failed withdrawals do not report a transaction ID since it does not exist.
    1408 
    1409 = 2.3.4 =
    1410 - Improve: Confirmation links can be clicked even if user not logged in.
    1411 - Add: When a transaction is user unaccepted via admin, a new confirmation email is sent.
    1412 - Fix: Unused code cleanup
    1413 
    1414 = 2.3.3 =
    1415 - Fix: Deposit notifications restored after being disabled in 2.3.2
    1416 - Fix: Only send confirmation emails if DB insert succeeds
    1417 
    1418 = 2.3.2 =
    1419 - Fix: Issue introduced in 2.3.0 where pending (not executed) withdrawals to the same address would fail.
    1420 - Fix: Unhandled exception when sending a notification email while the corresponding adapter is disabled.
    1421 - Change: CSV import feature only imports transactions with "done" status to maintain DB consistency.
    1422 
    1423 = 2.3.1 =
    1424 - Fix: Issue where on some systems MySQL tables were not being updated correctly, resulting in user balances appearing as 0.
    1425 
    1426 = 2.3.0 =
    1427 - Add: Administrator panel to show all transactions in the system.
    1428 - Change: The `.csv` import functionality is now moved to the transactions admin panel.
    1429 - Change: Transaction requests are now decoupled from transaction executions. They are executed by cron jobs in batches of configurable size and frequency.
    1430 - Add: Transactions can require confirmation by an administrator with `manage_wallets`.
    1431 - Add: Transactions can require the user to click on a link sent by email.
    1432 - Add: Failed transactions are retried a configurable number of times.
    1433 - Add: Transaction retries can be reset by an administrator with `manage_wallets`.
    1434 - Add: Users can now be notified by email if their transaction fails.
    1435 - Add: Frontend transactions lists (wallets_transactions UI) now show the TXID.
    1436 - Add: Frontend transaction lists (wallets_transactions UI) are now color coded based on transaction state.
    1437 - Fix: The minimum number of confirmations reported by get_minconf() was always `1` instead of the user-supplied value.
    1438 - Change: Performance improvement in the code that calculates balances for users (function `get_balance()`).
    1439 - Change: Internal transfers that cause two row inserts are now surrounded by a DB lock and atomic transaction to ensure consistency even in case of an unexpected error.
    1440 
    1441 = 2.2.5 =
    1442 - Fix: Administrator capabilities were erroneously being erased in 2.2.4 when editing other role capabilities
    1443 
    1444 = 2.2.4 =
    1445 - Add: User is warned if DISABLE_WP_CRON is set.
    1446 - Fix: Administrator is now unable to remove capabilities from self for safety.
    1447 - Fix: Fees fields were being cleared when the clear button was pressed or after a successful transaction.
    1448 - Fix: Suppress duplicate warnings in logs when inserting existing user address
    1449 - Fix: Moment.js third-party lib was being reminified.
    1450 
    1451 = 2.2.3 =
    1452 - Add: Multisite (aka network) installs now supported
    1453 - Improve: If user does not have wallets capability the frontend is not burdened with wallets scripts or styles
    1454 - Fix: Transactions table has horizontal scrolls (especially useful in the transactions widget)
    1455 - Fix: Added empty `index.php` files in all directories for added security.
    1456 
    1457 = 2.2.2 =
    1458 - Fix: Do not popup error to users who are not logged in
    1459 
    1460 = 2.2.1 =
    1461 - Add: Deposit addresses now also shown as QR-Codes
    1462 - Add: After import show both successful and unsuccessful transaction counts
    1463 - Fix: Users now are not allowed to transfer funds to self
    1464 - Fix: E-mail notifications withdrawals would show timestamps, now show human-readable date/time
    1465 
    1466 = 2.2.0 =
    1467 - Change: Improved coin adapters API. All current adapters need update to the 2.2.0 API.
    1468 - Add: Accompanying PDF documentation now provides instructions for creating a coin adapter (for developers).
    1469 - Fix: Improved front-end error reporting in some cases.
    1470 - Fix: Plugin would not activate on MySQL DBs with collation utf8mb4*
    1471 - Improve: If the PHP cURL module is not installed, any RPC adapters are automatically disabled and the user is warned.
    1472 
    1473 = 2.1.2 =
    1474 - Fix: Errors were not being reported on frontend. (JSON API now always returns status 200 OK even if carrying an error message.)
    1475 
    1476 = 2.1.1 =
    1477 - Add: The capabilities matrix is now hookable by extensions
    1478 - Add: Internal transfers can now have unlimited descriptive tags assigned
    1479 - Fix: The `get_users_info` JSON API now retrieves only users who have capability `has_wallets`
    1480 
    1481 = 2.1.0 =
    1482 - Add: Capabilities feature lets you assign capabilities to user roles
    1483 - Add: E-mail notifications are now admin-configurable
    1484 - Add: Frontend Widgets
    1485 - Change: Settings tab is now cron tab
    1486 - Change: Better code organisation
    1487 
    1488 = 2.0.2 =
    1489 - Add: Link to homepage and settings page from plugin list
    1490 - Fix: When altering cron duration from admin screens cron job is correctly rescheduled
    1491 - Fix: Cron job is now unscheduled on plugin deactivation
    1492 - Fix: Uninstall script now correctly unschedules cron job
    1493 - Fix: Safer user ID detection (does not depend on `wp_load` action)
    1494 - Fix: Using `sprintf` format from adapter in error messages
    1495 - Fix: Typo in error message when insufficient balance for withdraw/move
    1496 - Improve: Better code organisation for admin screens
    1497 - Improve: Safer inserting of new addresses in `wallets_address` action
    1498 
    1499 = 2.0.1 =
    1500 - Fix: Dates in the [wallets_transactions] UI were not showing correctly in Internet Explorer
    1501 - Improve: Refactored the withdrawal API for compatibility with changes to block.io adapter 1.0.2
    1502 
    1503 = 2.0.0 =
    1504 - Add: Generalised `wp_cron` mechanism lets coin adapters perform any periodic checks via an optional `cron()` method.
    1505 - Improve: Various improvements to the coin adapter API. `list_transactions` was removed in favor of the generic `cron()` method.
    1506 - Add: The `bitcoind` and other RPC API coin adapters do not depend on the notification API to discover deposits.
    1507 - Add: Better admin UI explaining how fees operate.
    1508 - Add: Adapters can now optionally notify the plugin of user-address mappings with the `wallets_address` action
    1509 - Add: The plugin now warns the admin about using SSL.
    1510 - Fix: The `bitcoind` built-in adapter now works smoother with the `bittiraha` lightweight wallet.
    1511 - Fix: Improved user `get_balance()` in terms of performance and robustness.
    1512 - Fix: Bitcoin RPC API adapter only binds to notification API set to enabled.
    1513 - Fix: Catching an exception when notified about transaction to unknown address.
    1514 - Fix: When transaction tables are locked to perform atomic transactions, the `wp_options` table is available to coin adapters.
    1515 
    1516 = 1.2.0 =
    1517 - Add: Multiple coin adapters per extension and per coin (see release notes)
    1518 - Add: Fail-safe mechanism that periodically checks for deposits that have not been recorded.
    1519 - Add: New setting panel in admin for settings that are not specific to any coin adapters.
    1520 - Fix: Exceptions thrown during failed deposit notifications are now caught.
    1521 
    1522 = 1.1.0 =
    1523 - Add: Compatibility with the `prasos/bittiraha-walletd` lightweight wallet (see FAQ).
    1524 - Fix: Users who are not logged in are not nagged with an alert box. Shortcode UIs now display "Must be logged in" message instead.
    1525 - Simplified the adapters list. There will be an entire admin panel about the removed information in a future version.
    1526 - Add: Adapters list now gives meaningful errors for unresponsive coin adapters.
    1527 
    1528 = 1.0.6 =
    1529 - Made compatible with PHP versions earlier than 5.5
    1530 - Added warning in readme about running on PHP versions that have reached end-of-life
    1531 
    1532 = 1.0.5 =
    1533 - Deactivate button not shown for built in Bitcoin adapter
    1534 - Added video tutorial to readme
    1535 
    1536 = 1.0.4 =
    1537 - Recommends the configurations needed in your `bitcoin.conf`
    1538 - Does not recommend command line arguments to `bitcoind` any more
    1539 - Updated install instructions in `readme.txt`
    1540 
    1541 = 1.0.3 =
    1542 - Fixed issue where deactivating any plugin would fail due to nonce error
    1543 
    1544 = 1.0.2 =
    1545 - Clearer disclaimer
    1546 - Fixed a broken link
    1547 
    1548 = 1.0.1 =
    1549 - Fixed some string escaping issues
    1550 
    1551 = 1.0.0 =
    1552 - Accounting
    1553 - bitcoind connectivity
    1554 - PHP API
    1555 - JSON API
    1556 - Front-end shortcodes
    1557 - CSV Import/Export
    1558 
    1559492== Upgrade Notice ==
    1560493
    1561 Version `6.2.5` is a bugfix release.
     494Version `6.2.6` improves performance of the plugin, by batching and caching plugin access to database objects.
    1562495
    1563496== Donating ==
  • wallets/trunk/wallets.php

    r3070693 r3089855  
    33 * Plugin Name:         Bitcoin and Altcoin Wallets
    44 * Description:         Custodial cryptocurrency wallets.
    5  * Version:             6.2.5
     5 * Version:             6.2.6
    66 * Plugin URI:          https://www.dashed-slug.net/bitcoin-altcoin-wallets-wordpress-plugin
    77 * Requires at least:   6.0
Note: See TracChangeset for help on using the changeset viewer.