File ManagerCurrent Directory: wp-content/plugins/wpforms/src/Pro/Tasks/ActionsLinux appserver-0d5e4f1e-php-cc8da225320a42ba9b7d66cba40b1f03 6.6.123+ #1 SMP PREEMPT_DYNAMIC Wed Mar 11 09:04:28 UTC 2026 x86_64Edit File: MigrationPaymentEntriesTask.php <?php namespace WPForms\Pro\Tasks\Actions; use WPForms\Tasks\Task; use WPForms\Tasks\Tasks; /** * The payment entries migration task. * * @since 1.8.2 */ class MigrationPaymentEntriesTask extends Task { /** * Action name for this task. * * @since 1.8.2 */ const ACTION = 'wpforms_process_migration_payment_entries'; /** * Status option name. * * @since 1.8.2 */ const STATUS = 'wpforms_process_migration_payment_entries_status'; /** * Start status. * * @since 1.8.2 */ const START = 'start'; /** * In progress status. * * @since 1.8.2 */ const IN_PROGRESS = 'in progress'; /** * Completed status. * * @since 1.8.2 */ const COMPLETED = 'completed'; /** * Chunk size to use. * Specifies how many entries are processed in one DB request. * * @since 1.8.2 */ const CHUNK_SIZE = 1000; /** * Chunk size of the migration task. * Specifies how many entry ids to load at once for further conversion. * * @since 1.8.2 */ const TASK_CHUNK_SIZE = self::CHUNK_SIZE * 10; /** * Maximum size of data that can be transferred in one db request. * We consider that 4194304 (4M) is an appropriate value for most of cases. * * @since 1.8.2 */ const MAX_ALLOWED_PACKET = 4194304; /** * Temporary table name. * * @since 1.8.2 * * @var string */ private $temp_table_name; /** * Class constructor. * * @since 1.8.2 */ public function __construct() { parent::__construct( self::ACTION ); } /** * Initialize the task. * * @since 1.8.2 */ public function init() { global $wpdb; // Get a task status. $status = get_option( self::STATUS ); // This task is run in \WPForms\Pro\Migrations\Upgrade182::run(), // and started in \WPForms\Migrations\UpgradeBase::run_async(). // Bail out if a task is not started or completed. if ( ! $status || $status === self::COMPLETED ) { return; } $this->temp_table_name = "{$wpdb->prefix}wpforms_temp_payment_entries"; if ( ! $this->is_allowed() ) { return; } // Register hooks. $this->hooks(); // Add new only if none exists. if ( wpforms()->obj( 'tasks' )->is_scheduled( self::ACTION ) !== false ) { return; } if ( $status === self::START ) { // Mark that a task is in progress. update_option( self::STATUS, self::IN_PROGRESS ); // Init migration. $this->init_migration(); } } /** * Determine whether a task is allowed (can be initialized). * * @since 1.8.2 * * @return bool */ private function is_allowed() { return wpforms()->obj( 'tasks' ) && wpforms()->obj( 'entry' ) && wpforms()->obj( 'payment' ) && wpforms()->obj( 'payment_meta' ); } /** * Register hooks. * * @since 1.8.2 */ private function hooks() { // Register the migrate action. add_action( self::ACTION, [ $this, 'migrate' ] ); // Register after process queue action. add_action( 'action_scheduler_after_process_queue', [ $this, 'after_process_queue' ] ); } /** * Init migration. * * @since 1.8.2 */ private function init_migration() { // Get all payment entries. $count = $this->get_unprocessed_payment_entry_ids(); if ( ! $count ) { $this->drop_temp_table(); return; } $index = 0; while ( $index < $count ) { // We do not use Task class here as we do not need meta. So, we reduce number of DB requests. as_enqueue_async_action( self::ACTION, [ $index ], Tasks::GROUP ); $index += self::TASK_CHUNK_SIZE; } $this->maybe_raise_mysql_max_allowed_packet(); } /** * Get payment entry ids. * Store them in a temporary table. * * @since 1.8.2 * * @return int */ private function get_unprocessed_payment_entry_ids() { global $wpdb; $this->create_temp_table(); $entry_handler = wpforms()->obj( 'entry' ); $payment_handler = wpforms()->obj( 'payment' ); // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->query( "INSERT INTO $this->temp_table_name (entry_id) SELECT entry_id FROM $entry_handler->table_name WHERE type = 'payment' AND entry_id NOT IN ( SELECT entry_id FROM $payment_handler->table_name )" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared return $wpdb->rows_affected; } /** * Create a temporary table. * * @since 1.8.2 */ private function create_temp_table() { $this->drop_temp_table(); $query = "CREATE TABLE $this->temp_table_name ( id bigint(20) AUTO_INCREMENT, entry_id bigint(20) NOT NULL, PRIMARY KEY (id) )"; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta( $query ); } /** * Drop a temporary table. * * @since 1.8.2 */ private function drop_temp_table() { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->query( "DROP TABLE IF EXISTS $this->temp_table_name" ); } /** * Migrate entries. * * @since 1.8.2 * * @param int $action_index Action index. */ public function migrate( $action_index ) { global $wpdb; // Using OFFSET makes a way longer request, as MySQL has to access all rows before OFFSET. // We follow very fast way with indexed column (id > $action_index). // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $entry_ids = $wpdb->get_col( $wpdb->prepare( "SELECT entry_id FROM $this->temp_table_name WHERE id > %d LIMIT %d", $action_index, self::TASK_CHUNK_SIZE ) ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $i = 0; $entry_ids_count = count( $entry_ids ); // This cycle is twice less memory consuming than array_chunk( $entry_ids ). while ( $i < $entry_ids_count ) { $entry_ids_chunk = array_slice( $entry_ids, $i, self::CHUNK_SIZE ); $this->migrate_payment_data( $entry_ids_chunk ); $i += self::CHUNK_SIZE; } } /** * Migrate payment data to the new table. * * @since 1.8.2 * * @param array $entry_ids List of entry ids. */ private function migrate_payment_data( $entry_ids ) { global $wpdb; $payments = []; $metadata = []; foreach ( $this->get_entries_by_ids( $entry_ids ) as $entry ) { if ( empty( $entry->meta ) ) { continue; } $payment_data = $this->transform_payment_data( $entry ); $payment_status = strtolower( $entry->status ); if ( in_array( $payment_status, [ 'active', 'completed' ], true ) ) { $payment_status = 'processed'; } // Prepare a payment record for inserting into the table. $payments[] = $wpdb->prepare( '( %d, %s, %f, %f, %f, %s, %d, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %d )', $entry->form_id, $payment_status, $payment_data['payment_total'], 0, // There were no discounts for migrated payments. $payment_data['payment_total'], strtoupper( substr( $payment_data['payment_currency'], 0, 3 ) ), $entry->entry_id, $payment_data['payment_gateway'], $payment_data['payment_type'], $payment_data['payment_mode'], substr( $payment_data['payment_transaction'], 0, 40 ), substr( $payment_data['payment_customer'], 0, 40 ), substr( $payment_data['payment_subscription'], 0, 40 ), $payment_data['payment_subscription'] ? 'not-synced' : '', '', // Placeholder for title. $entry->date, $entry->date_modified, 1 // All migrated payments are published. ); // Collect payments meta for using in separate DB queries. $metadata[] = [ 'subscription_period' => $payment_data['payment_period'], 'payment_note' => $payment_data['payment_note'], 'payment_recipient' => $payment_data['payment_recipient'], 'receipt_number' => $payment_data['receipt_number'], 'user_id' => $entry->user_id, 'user_agent' => $entry->user_agent, 'user_uuid' => $entry->user_uuid, 'ip_address' => $entry->ip_address, 'is_migrated' => 1, // It might be useful for determining whether a payment has been migrated. ]; } if ( empty( $payments ) ) { return; } // Chaining syntax are used here since an order of methods call is important. $this->insert_payments( $payments )->insert_metadata( $metadata ); } /** * Retrieve list of entries by their IDs. * * @since 1.8.2 * * @param array $ids Entries IDs. * * @return array */ private function get_entries_by_ids( $ids ) { global $wpdb; $entry_handler = wpforms()->obj( 'entry' ); $entry_ids_list = implode( ',', $ids ); // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( "SELECT entry_id, form_id, user_id, status, meta, date, date_modified, ip_address, user_agent, user_uuid FROM $entry_handler->table_name WHERE entry_id IN ( $entry_ids_list )" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching return is_array( $wpdb->last_result ) ? $wpdb->last_result : []; } /** * Transform legacy payment meta. * * @since 1.8.2 * * @param object $entry Single entry. * * @return array */ private function transform_payment_data( $entry ) { static $defaults; if ( ! isset( $defaults ) ) { $defaults = [ 'payment_total' => 0, 'payment_currency' => '', 'payment_gateway' => '', 'payment_type' => '', 'payment_mode' => '', 'payment_transaction' => '', 'payment_customer' => '', 'payment_subscription' => '', 'payment_period' => '', 'payment_note' => '', 'payment_recipient' => '', 'receipt_number' => '', ]; } $payment_data = json_decode( $entry->meta, true ); if ( ! is_array( $payment_data ) ) { return $defaults; } $payment_data = wp_parse_args( $payment_data, $defaults ); // Prepare a payment gateway. // In the past, it was stored as a payment type. $payment_data['payment_gateway'] = $payment_data['payment_type']; // Prepare a payment type. $payment_data['payment_type'] = ! empty( $payment_data['payment_subscription'] ) ? 'subscription' : 'one-time'; // Prepare a payment mode. // Convert the legacy `production` to `live` mode. $payment_data['payment_mode'] = in_array( $payment_data['payment_mode'], [ 'production', 'live' ], true ) ? 'live' : 'test'; return $payment_data; } /** * Insert records into the main payments table. * * @since 1.8.2 * * @param array $payments Payments are ready for using in DB query. * * @return MigrationPaymentEntriesTask */ private function insert_payments( $payments ) { global $wpdb; $payment_handler = wpforms()->obj( 'payment' ); $values = implode( ', ', $payments ); // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( "INSERT INTO $payment_handler->table_name ( form_id, status, subtotal_amount, discount_amount, total_amount, currency, entry_id, gateway, type, mode, transaction_id, customer_id, subscription_id, subscription_status, title, date_created_gmt, date_updated_gmt, is_published ) VALUES $values" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching return $this; } /** * Insert records into the payments meta table. * * @since 1.8.2 * * @param array $metadata List of payments metadata. */ private function insert_metadata( $metadata ) { global $wpdb; if ( empty( $wpdb->insert_id ) ) { return; } // This is the first payment ID inserted in the last DB query. $payment_id = $wpdb->insert_id; $values = []; foreach ( $metadata as $payment_meta ) { foreach ( $payment_meta as $meta_key => $meta_value ) { if ( wpforms_is_empty_string( $meta_value ) ) { continue; } $values[] = $wpdb->prepare( '( %d, %s, %s )', $payment_id, $meta_key, $meta_value ); } // Increment payment ID. $payment_id++; } if ( empty( $values ) ) { return; } $values = implode( ', ', $values ); $payment_meta_handler = wpforms()->obj( 'payment_meta' ); // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( "INSERT INTO $payment_meta_handler->table_name ( payment_id, meta_key, meta_value ) VALUES $values" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching } /** * Raise the maximum size of one packet if it is possible. * * @since 1.8.2 */ private function maybe_raise_mysql_max_allowed_packet() { global $wpdb; // The length of the query is defined by MAX_ALLOWED_PACKET variable. // We try to raise MAX_ALLOWED_PACKET variable to more appropriate value specified in self::MAX_ALLOWED_PACKET. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $max_allowed_packet = (int) $wpdb->get_var( "SHOW VARIABLES LIKE 'MAX_ALLOWED_PACKET'", 1 ); $new_max_allowed_packet = self::MAX_ALLOWED_PACKET; // Bail out if the current MAX_ALLOWED_PACKET variable is good enough. if ( $new_max_allowed_packet <= $max_allowed_packet ) { return; } $is_suppressed = $wpdb->suppress_errors; $wpdb->suppress_errors(); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->query( "SET GLOBAL MAX_ALLOWED_PACKET = $new_max_allowed_packet" ); $wpdb->suppress_errors( $is_suppressed ); } /** * After process queue action. * * @since 1.8.2 */ public function after_process_queue() { if ( wpforms()->obj( 'tasks' )->is_scheduled( self::ACTION ) ) { return; } $this->drop_temp_table(); // Mark that a task is completed. update_option( self::STATUS, self::COMPLETED ); } } Upload File Directory Listing NameTypeSizeActions.. (Parent Directory)DirEntryEmailCSVCleanupTask.phpFile6.79 KB Rename | Delete | EditMigration176Task.phpFile5.69 KB Rename | Delete | EditMigration190Task.phpFile5.51 KB Rename | Delete | EditMigrationPaymentEntriesTask.phpFile14.45 KB Rename | Delete | EditPurgeTemplateEntryTask.phpFile3.42 KB Rename | Delete | Edit