_results( $meta_query ); if ( ! is_array( $meta_data_rows ) || empty( $meta_data_rows ) ) { return array(); } foreach ( $meta_data_rows as $migrate_row ) { if ( ! isset( $to_migrate[ $migrate_row->entity_id ] ) ) { $to_migrate[ $migrate_row->entity_id ] = array(); } if ( ! isset( $to_migrate[ $migrate_row->entity_id ][ $migrate_row->meta_key ] ) ) { // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key $to_migrate[ $migrate_row->entity_id ][ $migrate_row->meta_key ] = array(); } $to_migrate[ $migrate_row->entity_id ][ $migrate_row->meta_key ][] = $migrate_row->meta_value; } return $to_migrate; } /** * Helper method to get already migrated records. Will be used to find prevent migration of already migrated records. * * @param array $entity_ids List of entity ids to check for. * * @return array Already migrated records. */ private function get_already_migrated_records( array $entity_ids ): array { global $wpdb; $destination_table_name = $this->schema_config['destination']['meta']['table_name']; $destination_id_column = $this->schema_config['destination']['meta']['meta_id_column']; $destination_entity_id_column = $this->schema_config['destination']['meta']['entity_id_column']; $destination_meta_key_column = $this->schema_config['destination']['meta']['meta_key_column']; $destination_meta_value_column = $this->schema_config['destination']['meta']['meta_value_column']; $entity_id_type_placeholder = MigrationHelper::get_wpdb_placeholder_for_type( $this->schema_config['destination']['meta']['entity_id_type'] ); $entity_ids_placeholder = implode( ',', array_fill( 0, count( $entity_ids ), $entity_id_type_placeholder ) ); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare $data_already_migrated = $this->db_get_results( $wpdb->prepare( " SELECT $destination_id_column meta_id, $destination_entity_id_column entity_id, $destination_meta_key_column meta_key, $destination_meta_value_column meta_value FROM $destination_table_name destination WHERE destination.$destination_entity_id_column in ( $entity_ids_placeholder ) ORDER BY destination.$destination_entity_id_column ", $entity_ids ) ); // phpcs:enable $already_migrated = array(); foreach ( $data_already_migrated as $migrate_row ) { if ( ! isset( $already_migrated[ $migrate_row->entity_id ] ) ) { $already_migrated[ $migrate_row->entity_id ] = array(); } // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key, WordPress.DB.SlowDBQuery.slow_db_query_meta_value if ( ! isset( $already_migrated[ $migrate_row->entity_id ][ $migrate_row->meta_key ] ) ) { $already_migrated[ $migrate_row->entity_id ][ $migrate_row->meta_key ] = array(); } $already_migrated[ $migrate_row->entity_id ][ $migrate_row->meta_key ][] = array( 'id' => $migrate_row->meta_id, 'meta_value' => $migrate_row->meta_value, ); // phpcs:enable } return $already_migrated; } /** * Classify each record on whether to migrate or update. * * @param array $to_migrate Records to migrate. * @param array $already_migrated Records already migrated. * * @return array[] Returns two arrays, first for records to migrate, and second for records to upgrade. */ private function classify_update_insert_records( array $to_migrate, array $already_migrated ): array { $to_update = array(); $to_insert = array(); foreach ( $to_migrate as $entity_id => $rows ) { foreach ( $rows as $meta_key => $meta_values ) { // If there is no corresponding record in the destination table then insert. // If there is single value in both already migrated and current then update. // If there are multiple values in either already_migrated records or in to_migrate_records, then insert instead of updating. if ( ! isset( $already_migrated[ $entity_id ][ $meta_key ] ) ) { if ( ! isset( $to_insert[ $entity_id ] ) ) { $to_insert[ $entity_id ] = array(); } $to_insert[ $entity_id ][ $meta_key ] = $meta_values; } else { if ( 1 === count( $meta_values ) && 1 === count( $already_migrated[ $entity_id ][ $meta_key ] ) ) { if ( $meta_values[0] === $already_migrated[ $entity_id ][ $meta_key ][0]['meta_value'] ) { continue; } if ( ! isset( $to_update[ $entity_id ] ) ) { $to_update[ $entity_id ] = array(); } $to_update[ $entity_id ][ $meta_key ] = array( 'id' => $already_migrated[ $entity_id ][ $meta_key ][0]['id'], // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value 'meta_value' => $meta_values[0], ); continue; } // There are multiple meta entries, let's find the unique entries and insert. $unique_meta_values = array_diff( $meta_values, array_column( $already_migrated[ $entity_id ][ $meta_key ], 'meta_value' ) ); if ( 0 === count( $unique_meta_values ) ) { continue; } if ( ! isset( $to_insert[ $entity_id ] ) ) { $to_insert[ $entity_id ] = array(); } $to_insert[ $entity_id ][ $meta_key ] = $unique_meta_values; } } } return array( $to_insert, $to_update ); } /** * Helper method to build query used to fetch data from source meta table. * * @param array $entity_ids List of entity IDs to build meta query for. * * @return string Query that can be used to fetch data. */ private function build_meta_table_query( array $entity_ids ): string { global $wpdb; $source_meta_table = $this->schema_config['source']['meta']['table_name']; $source_meta_key_column = $this->schema_config['source']['meta']['meta_key_column']; $source_meta_value_column = $this->schema_config['source']['meta']['meta_value_column']; $source_entity_id_column = $this->schema_config['source']['meta']['entity_id_column']; $order_by = "source.$source_entity_id_column ASC"; $where_clause = "source.`$source_entity_id_column` IN (" . implode( ', ', array_fill( 0, count( $entity_ids ), '%d' ) ) . ')'; $entity_table = $this->schema_config['source']['entity']['table_name']; $entity_id_column = $this->schema_config['source']['entity']['id_column']; $entity_meta_id_mapping_column = $this->schema_config['source']['entity']['source_id_column']; if ( isset( $this->schema_config['source']['excluded_keys'] ) && is_array( $this->schema_config['source']['excluded_keys'] ) ) { $key_placeholder = implode( ',', array_fill( 0, count( $this->schema_config['source']['excluded_keys'] ), '%s' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $source_meta_key_column is escaped for backticks, $key_placeholder is hardcoded. $exclude_clause = $wpdb->prepare( "source.$source_meta_key_column NOT IN ( $key_placeholder )", $this->schema_config['source']['excluded_keys'] ); $where_clause = "$where_clause AND $exclude_clause"; } // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare return $wpdb->prepare( " SELECT source.`$source_entity_id_column` as source_entity_id, entity.`$entity_id_column` as entity_id, source.`$source_meta_key_column` as meta_key, source.`$source_meta_value_column` as meta_value FROM `$source_meta_table` source JOIN `$entity_table` entity ON entity.`$entity_meta_id_mapping_column` = source.`$source_entity_id_column` WHERE $where_clause ORDER BY $order_by ", $entity_ids ); // phpcs:enable } }