File "Migration.php"
Full Path: /home/capoeirajd/www/wp-content/plugins/wpforms-lite/src/Integrations/ConstantContact/V3/Migration/Migration.php
File size: 17.33 KB
MIME-type: text/x-php
Charset: utf-8
<?php
namespace WPForms\Integrations\ConstantContact\V3\Migration;
use WP_Post;
use RuntimeException;
use WPForms_Constant_Contact;
use WPForms\Integrations\ConstantContact\V3\Core;
use WPForms\Integrations\ConstantContact\V3\Auth;
use WPForms\Integrations\ConstantContact\V3\Api\Api;
use WPForms\Integrations\ConstantContact\V3\ConstantContact;
/**
* Migration class.
*
* The loader for the rest of classes in the namespace and manager
* of the migration process.
*
* @since 1.9.3
*/
class Migration {
/**
* List of migrated list ids in v2 => v3 format.
*
* @since 1.9.3
*
* @var array
*/
private $lists = [];
/**
* New account data.
*
* @since 1.9.3
*
* @var array
*/
private $new_account;
/**
* Form data and settings.
*
* @since 1.9.3
*
* @var array
*/
private $form_data;
/**
* Index of the first name custom field in the new account.
*
* @since 1.9.3
*
* @var int|null
*/
private $first_name_index;
/**
* Index of the last name custom field in the new account.
*
* @since 1.9.3
*
* @var int|null
*/
private $last_name_index;
/**
* Init.
*
* @since 1.9.3
*/
public function init() {
$this->force_migration();
if ( ConstantContact::get_current_version() >= 3 ) {
return;
}
$this->display_prompt();
$this->hooks();
}
/**
* Hooks.
*
* @since 1.9.3
*/
private function hooks() {
// Add ajax action.
add_action( 'wp_ajax_wpforms_constant_contact_migration_prompt', [ $this, 'ajax_start_migration' ] );
add_action( 'update_option_wpforms_providers', [ $this, 'update_providers_options_after' ], 10, 2 );
add_filter( 'wpforms_integrations_constant_contact_v3_auth_create_account_data', [ $this, 'migrate_account_finish' ] );
}
/**
* Force migration.
*
* @since 1.9.3
*/
private function force_migration() {
if ( ! wpforms_is_admin_page( 'settings', 'integrations' ) ) {
return;
}
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$key = 'constant_contact-force-migration';
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! isset( $_GET[ $key ] ) ) {
return;
}
if ( isset( $_SERVER['REQUEST_URI'] ) ) {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$_SERVER['REQUEST_URI'] = remove_query_arg( $key, wp_unslash( $_SERVER['REQUEST_URI'] ) );
}
delete_option( ConstantContact::VERSION_OPTION );
}
/**
* Display migration prompt.
*
* @since 1.9.3
*/
private function display_prompt() {
if ( ! wpforms_is_admin_page( 'settings', 'integrations' ) ) {
return;
}
if ( $this->migrated_accounts_exist() ) {
return;
}
$notice_obj = wpforms()->obj( 'notice' );
if ( ! $notice_obj ) {
return;
}
$notice_obj::error(
wp_kses(
sprintf(
/* translators: %1$s - link to the migration page, %2$s - closing HTML tag. */
__( 'You need to migrate your existing forms to the new version of the Constant Contact addon. Please %1$s click here%2$s to start the migration.', 'wpforms-lite' ),
'<a href="#" rel="noopener noreferrer" id="wpforms-settings-constant-contact-v3-migration-prompt-link">',
'</a>'
),
[
'a' => [
'href' => [],
'rel' => [],
'id' => [],
],
]
)
);
}
/**
* Replace account ID if it was migrated.
*
* @since 1.9.3
*
* @param array $new_account New account data.
*
* @return array
*/
public function migrate_account_finish( array $new_account ): array {
$accounts = wpforms_get_providers_options( Core::SLUG );
foreach ( $accounts as $account_id => $account ) {
if (
$account['email'] === $new_account['email']
&& ! empty( $account['accounts'] )
) {
$new_account['id'] = $account_id;
$this->new_account = $new_account;
$this->migrate_forms( $account );
break;
}
}
return $new_account;
}
/**
* Finish migration by setting the version to 3.
*
* @since 1.9.3
*/
public static function finish_migration() {
update_option( ConstantContact::VERSION_OPTION, 3 );
}
/**
* Update providers options after migration.
*
* @since 1.9.3
*
* @param mixed $old_value Old providers options.
* @param mixed $new_value New providers options.
*
* @noinspection PhpUnusedParameterInspection
*/
public function update_providers_options_after( $old_value, $new_value ) {
if ( empty( wpforms_get_providers_options( 'constant-contact' ) ) ) {
self::finish_migration();
return;
}
if ( ! is_array( $new_value ) || empty( $new_value[ Core::SLUG ] ) ) {
return;
}
if ( $this->migrated_accounts_exist() ) {
return;
}
self::finish_migration();
}
/**
* Check if some migrated accounts have been already created.
*
* @since 1.9.3
*
* @return bool
*/
private function migrated_accounts_exist(): bool {
$accounts = wpforms_get_providers_options( Core::SLUG );
foreach ( $accounts as $account ) {
if ( ! empty( $account['accounts'] ) ) {
return true;
}
}
return false;
}
/**
* Migrate all accounts.
*
* @since 1.9.3
*/
public function ajax_start_migration() {
check_ajax_referer( Auth::NONCE, 'nonce' );
if ( ! wpforms_current_user_can() ) {
wp_send_json_error( esc_html__( 'You do not have permission to perform this action.', 'wpforms-lite' ) );
}
$accounts = wpforms_get_providers_options();
// No accounts to migrate.
if ( empty( $accounts['constant-contact'] ) ) {
self::finish_migration();
wp_send_json_success();
}
foreach ( $accounts['constant-contact'] as $account_id => $account ) {
$this->migrate_account_start( $account_id, $account, $accounts );
}
// If no accounts were migrated because v2 accounts were invalid, we switch to the new version.
if ( empty( $accounts[ Core::SLUG ] ) ) {
self::finish_migration();
wp_send_json_success();
}
update_option( 'wpforms_providers', $accounts );
wp_send_json_success();
}
/**
* Migrate a specific v2 account to v3.
*
* @since 1.9.3
*
* @param string $account_id Account ID.
* @param array $account Current account data.
* @param array $accounts List of all providers' accounts.
*/
private function migrate_account_start( string $account_id, array $account, array &$accounts ) {
static $migrated_access_tokens = [];
// It was possible to create an account without an access token.
if ( empty( $account['access_token'] ) ) {
return;
}
// It was possible to create a few accounts with the same access token.
// We merge them into one in the new version.
if ( isset( $migrated_access_tokens[ $account['access_token'] ] ) ) {
$created_account_id = $migrated_access_tokens[ $account['access_token'] ];
$accounts['constant-contact-v3'][ $created_account_id ]['accounts'][] = $account_id;
return;
}
$email = $this->get_account_email( $account );
// We skip an account if we can't receive email, in the case the access_token isn't valid.
if ( empty( $email ) ) {
return;
}
$migrated_access_tokens[ $account['access_token'] ] = $account_id;
$accounts['constant-contact-v3'][ $account_id ] = [
'id' => $account_id,
'accounts' => [ $account_id ],
'access_token' => $account['access_token'],
'date' => 0,
'label' => $account['label'] ?? $email,
'email' => $email,
];
}
/**
* Get email from an account.
*
* @since 1.9.3
*
* @param array $account Account data.
*
* @return string
*/
private function get_account_email( array $account ): string {
$old_provider = new WPForms_Constant_Contact();
$old_provider->access_token = $account['access_token'];
$account_info = $old_provider->get_account_information();
if ( is_wp_error( $account_info ) ) {
return '';
}
return $account_info['email'] ?? '';
}
/**
* Migrate forms.
*
* @since 1.9.3
*
* @param array $old_account Old account.
*
* @return void
*/
private function migrate_forms( array $old_account ) {
if ( ! isset( $old_account['accounts'], $old_account['access_token'] ) ) {
return;
}
$forms = $this->get_forms( (array) $old_account['accounts'] );
if ( empty( $forms ) ) {
return;
}
$this->lists = $this->get_lists_xhref( $this->new_account, $old_account['access_token'] );
foreach ( $forms as $form ) {
$this->migrate_form( $form );
}
}
/**
* Get migrated forms.
*
* @since 1.9.3
*
* @param array $old_account_ids Old v2 account ids.
*
* @return array
* @noinspection SqlResolve
*/
private function get_forms( array $old_account_ids ): array {
global $wpdb;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$forms = $wpdb->get_col(
$wpdb->prepare(
'SELECT ID FROM ' . $wpdb->posts . ' WHERE post_type = "wpforms" AND post_content REGEXP %s',
implode( '|', $old_account_ids )
)
);
if ( empty( $forms ) ) {
return [];
}
$form_ids = array_map( 'absint', $forms );
$form_obj = wpforms()->obj( 'form' );
if ( ! $form_obj ) {
return [];
}
return (array) $form_obj->get(
'',
[
'numberposts' => -1,
'orderby' => 'post__in',
'post__in' => $form_ids,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'no_found_rows' => true,
]
);
}
/**
* Copy connections from v2 to v3 in proper format.
*
* @since 1.9.3
*
* @param WP_Post $form Form object.
*/
private function migrate_form( WP_Post $form ) {
$this->form_data = wpforms_decode( $form->post_content );
// Nothing to migrate.
if ( empty( $this->form_data['providers']['constant-contact'] ) ) {
return;
}
$migrated_connections = $this->form_data['providers'][ Core::SLUG ] ?? [];
// All connections were migrated but account migration was interrupted by timeout or an error.
if ( count( $this->form_data['providers']['constant-contact'] ) === count( $migrated_connections ) ) {
return;
}
$this->form_data['providers'][ Core::SLUG ] = array_merge( $migrated_connections, $this->get_new_connections() );
$form_obj = wpforms()->obj( 'form' );
if ( ! $form_obj ) {
return;
}
$form_obj->update( $this->form_data['id'], $this->form_data );
}
/**
* Modify v2 connections to v3.
*
* @since 1.9.3
*
* @return array
*/
private function get_new_connections(): array {
$old_connections = $this->form_data['providers']['constant-contact'] ?? [];
$new_connections = [];
foreach ( $old_connections as $connection_id => $connection ) {
$new_connection_id = str_replace( 'connection_', '', $connection_id );
$connection = wp_parse_args(
$connection,
[
'connection_name' => '',
'account_id' => '',
'list_id' => '',
'fields' => [],
'conditional_logic' => '',
'conditional_type' => '',
'conditionals' => [],
]
);
// The connection is related to another account, skip it.
if ( $this->new_account['id'] !== $connection['account_id'] ) {
continue;
}
reset( $this->lists );
$new_connections[ $new_connection_id ] = [
'id' => $new_connection_id,
'name' => $connection['connection_name'],
'account_id' => $connection['account_id'],
'action' => 'subscribe',
'list' => $this->lists[ $connection['list_id'] ] ?? key( $this->lists ),
'email' => explode( '.', $connection['fields']['email'] ?? '' )[0],
'fields_meta' => $this->get_connection_custom_fields( $connection['fields'] ),
'conditional_logic' => $connection['conditional_logic'],
'conditional_type' => $connection['conditional_type'],
'conditionals' => $connection['conditionals'],
];
}
return $new_connections;
}
/**
* Get custom fields.
*
* @since 1.9.3
*
* @param array $custom_fields Custom fields v2.
*
* @return array
*/
private function get_connection_custom_fields( array $custom_fields ): array {
$fields_meta = [];
$custom_fields = $this->sort_custom_fields( $custom_fields );
foreach ( $custom_fields as $key => $value ) {
if ( $key === 'email' ) {
continue;
}
$value_parts = explode( '.', $value );
$field_id = $value_parts[0];
if ( wpforms_is_empty_string( $field_id ) ) {
continue;
}
$fields_meta = $this->update_fields_meta( $fields_meta, $field_id, $key, $value_parts );
}
return $fields_meta;
}
/**
* Move $custom_fields['full_name'] at the beginning of the array.
*
* Thanks to this, if first name and last name are defined, next iterations
* of this array will replace full_name - backward compatibility sustained.
*
* @since 1.9.3
*
* @param array $custom_fields Custom fields.
*
* @return array
*/
private function sort_custom_fields( array $custom_fields ): array {
if ( ! isset( $custom_fields['full_name'] ) || wpforms_is_empty_string( $custom_fields['full_name'] ) ) {
return $custom_fields;
}
$full_name = $custom_fields['full_name'];
unset( $custom_fields['full_name'] );
return [ 'full_name' => $full_name ] + $custom_fields;
}
/**
* Update fields meta.
*
* @since 1.9.3
*
* @param array $fields_meta Fields meta.
* @param string $field_id Field ID.
* @param string $key Key.
* @param array $value_parts Value parts.
*
* @return array
*/
private function update_fields_meta( array $fields_meta, string $field_id, string $key, array $value_parts ): array {
if ( $this->form_data['fields'][ $field_id ]['type'] === 'name' ) {
$name_field = $this->handle_name_field( $fields_meta, $field_id, $key, $value_parts );
if ( is_array( $name_field ) ) {
return $name_field;
}
$field_id = $name_field;
}
$keys_to_rename = [
'work_phone' => 'phone',
'url' => $this->get_url_field_id(),
];
$new_key = $keys_to_rename[ $key ] ?? $key;
$fields_meta[ $this->get_meta_next_index( $fields_meta, $new_key ) ] = [
'name' => $new_key,
'field_id' => $field_id,
];
return $fields_meta;
}
/**
* Handle name field.
*
* @since 1.9.3
*
* @param array $fields_meta Fields meta.
* @param string $field_id Field ID.
* @param string $key Key.
* @param array $value_parts Value parts.
*
* @return string|array
*/
private function handle_name_field( array $fields_meta, string $field_id, string $key, array $value_parts ) {
if ( $value_parts[1] === 'value' ) {
$value_parts[1] = 'full';
}
if ( $key === 'full_name' ) {
return $this->update_full_name( $fields_meta, $field_id, $value_parts );
}
$field_id .= '.' . $value_parts[1];
return $field_id;
}
/**
* Update full name meta.
*
* @since 1.9.3
*
* @param array $fields_meta Fields meta.
* @param string $field_id Field ID.
* @param array $value_parts Value parts.
*
* @return array
*/
private function update_full_name( array $fields_meta, string $field_id, array $value_parts ): array {
$field = $this->form_data['fields'][ $field_id ] ?? [];
$is_simple = ! isset( $field['format'] ) || $field['format'] === 'simple';
$first_name_field_id = $is_simple ? $field_id . '.' . $value_parts[1] : $field_id . '.first';
$fields_meta[] = [
'name' => 'first_name',
'field_id' => $first_name_field_id,
];
$this->first_name_index = count( $fields_meta ) - 1;
if ( $is_simple ) {
return $fields_meta;
}
$last_name_field_id = $field_id . '.last';
$fields_meta[] = [
'name' => 'last_name',
'field_id' => $last_name_field_id,
];
$this->last_name_index = count( $fields_meta ) - 1;
return $fields_meta;
}
/**
* Get next index for a custom field.
*
* @since 1.9.3
*
* @param array $fields_meta Fields meta.
* @param string $key Key.
*/
private function get_meta_next_index( array $fields_meta, string $key ): int {
if ( $key === 'first_name' ) {
return $this->first_name_index ?? count( $fields_meta );
}
if ( $key === 'last_name' ) {
return $this->last_name_index ?? count( $fields_meta );
}
return count( $fields_meta );
}
/**
* Get URL custom field ID from the new account.
*
* Returns the id in the new format.
*
* @since 1.9.3
*
* @return string
*/
private function get_url_field_id(): string {
static $field_id;
if ( $field_id ) {
return $field_id;
}
$custom_fields = ( new Api( $this->new_account ) )->get_custom_fields( 'custom_field_id', 'name' );
$field_id = $custom_fields['custom_field_1'] ?? $this->register_url_field();
return $field_id;
}
/**
* Get an array of list v2 ids to v3 ids.
*
* @since 1.9.3
*
* @param array $new_account New account data.
* @param string $access_token_v2 Access token for v2.
*
* @return array
*
* @throws RuntimeException Can't receive v2 lists and finish migration.
*/
private function get_lists_xhref( array $new_account, string $access_token_v2 ): array {
$old_provider = new WPForms_Constant_Contact();
$old_provider->access_token = $access_token_v2;
$old_lists = $old_provider->api_lists();
if ( is_wp_error( $old_lists ) ) {
throw new RuntimeException( esc_html__( 'Can\'t receive v2 lists and finish migration.', 'wpforms-lite' ) );
}
return ( new Api( $new_account ) )->get_contact_list_xrefs( (array) $old_lists );
}
/**
* Register URL custom field.
*
* @since 1.9.3
*
* @return string
*/
private function register_url_field(): string {
return ( new Api( $this->new_account ) )->register_custom_field( 'Website / URL' );
}
}