diff --git a/.gitignore b/.gitignore
index bbba19d5cb..f9468e402f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,21 +1,22 @@
-.DS_Store
-TODO
-composer.phar
-/vendor
-tests/_output/*
-tests/acceptance.suite.yml
-tests/_support/_generated/*
-node_modules
-.env
-npm-debug.log
-!tasks/**
-/views/cache/**
-temp
-.idea
-mailpoet.zip
-tests/javascript/testBundles
-assets/css/*.css
-assets/js/*.js
-.vagrant
-lang
-.mp_svn
+.DS_Store
+TODO
+composer.phar
+/vendor
+tests/_output/*
+tests/acceptance.suite.yml
+tests/_support/_generated/*
+node_modules
+.env
+npm-debug.log
+!tasks/**
+/views/cache/**
+temp
+.idea
+mailpoet.zip
+tests/javascript/testBundles
+assets/css/*.css
+assets/js/*.js
+.vagrant
+lang
+.mp_svn
+/nbproject/
\ No newline at end of file
diff --git a/assets/css/src/admin.styl b/assets/css/src/admin.styl
index 866f4d26db..aae0060e1f 100644
--- a/assets/css/src/admin.styl
+++ b/assets/css/src/admin.styl
@@ -22,3 +22,5 @@
@require 'progress_bar'
@require 'subscribers'
+
+@require 'mp2migrator'
diff --git a/assets/css/src/mp2migrator.styl b/assets/css/src/mp2migrator.styl
new file mode 100644
index 0000000000..bddcfefb9f
--- /dev/null
+++ b/assets/css/src/mp2migrator.styl
@@ -0,0 +1,33 @@
+#logger
+ width: 100%
+ height: 300px
+ background-color: transparent
+ border: 0
+ border-top: 1px #aba9a9 solid
+ padding: 2px
+ overflow: scroll
+ resize: both
+ font-size: 0.85em
+ margin-top: 20px
+
+#progressbar
+ width: 50%
+ background-color: #d8d8d8
+ border-radius: 5px
+
+progressbar_color = #fecf23
+progressbar_gradient_to_color = #fd9215
+
+.ui-progressbar .ui-progressbar-value
+ height: 100%
+ background-color: progressbar_color
+ background-image: linear-gradient(to bottom, progressbar_color, progressbar_gradient_to_color)
+ border-radius: 3px
+ box-shadow: 0 1px 0 rgba(255,255,255,0.5) inset
+ border 0
+
+.mailpoet_progress_label
+ font-size: 15px
+
+.error_msg
+ color: #f00
diff --git a/assets/js/src/mp2migrator.js b/assets/js/src/mp2migrator.js
new file mode 100644
index 0000000000..ae03914f67
--- /dev/null
+++ b/assets/js/src/mp2migrator.js
@@ -0,0 +1,187 @@
+define('mp2migrator', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
+ 'use strict';
+ MailPoet.MP2Migrator = {
+
+ fatal_error: '',
+ is_logging: false,
+
+ startLogger: function () {
+ MailPoet.MP2Migrator.is_logging = true;
+ clearTimeout(MailPoet.MP2Migrator.displayLogs_timeout);
+ clearTimeout(MailPoet.MP2Migrator.updateProgressbar_timeout);
+ clearTimeout(MailPoet.MP2Migrator.update_wordpress_info_timeout);
+ setTimeout(MailPoet.MP2Migrator.updateDisplay, 1000)
+ },
+
+ stopLogger: function () {
+ MailPoet.MP2Migrator.is_logging = false;
+ },
+
+ updateDisplay: function () {
+ MailPoet.MP2Migrator.displayLogs();
+ MailPoet.MP2Migrator.updateProgressbar();
+ },
+
+ displayLogs: function () {
+ jQuery.ajax({
+ url: mailpoet_mp2_migrator.log_file_url,
+ cache: false
+ }).done(function (result) {
+ jQuery("#logger").html('');
+ result.split("\n").forEach(function (row) {
+ if(row.substr(0, 7) === '[ERROR]' || row.substr(0, 9) === '[WARNING]' || row === MailPoet.I18n.t('import_stopped_by_user')) {
+ row = '' + row + ''; // Mark the errors in red
+ }
+ // Test if the import is complete
+ else if(row === MailPoet.I18n.t('import_complete')) {
+ jQuery('#import-actions').hide();
+ jQuery('#upgrade-completed').show();
+ }
+ jQuery("#logger").append(row + "
\n");
+
+ });
+ jQuery("#logger").append('' + MailPoet.MP2Migrator.fatal_error + '' + "
\n");
+ }).always(function () {
+ if(MailPoet.MP2Migrator.is_logging) {
+ MailPoet.MP2Migrator.displayLogs_timeout = setTimeout(MailPoet.MP2Migrator.displayLogs, 1000);
+ }
+ });
+ },
+
+ updateProgressbar: function () {
+ jQuery.ajax({
+ url: mailpoet_mp2_migrator.progress_url,
+ cache: false,
+ dataType: 'json'
+ }).always(function (result) {
+ // Move the progress bar
+ var progress = 0;
+ if((result.total !== undefined) && (Number(result.total) !== 0)) {
+ progress = Math.round(Number(result.current) / Number(result.total) * 100);
+ }
+ jQuery('#progressbar').progressbar('option', 'value', progress);
+ jQuery('#progresslabel').html(progress + '%');
+ if(Number(result.current !== 0)) {
+ jQuery('#skip-import').hide();
+ }
+ if(MailPoet.MP2Migrator.is_logging) {
+ MailPoet.MP2Migrator.updateProgressbar_timeout = setTimeout(MailPoet.MP2Migrator.updateProgressbar, 1000);
+ }
+ });
+ },
+
+ startImport: function () {
+ MailPoet.MP2Migrator.fatal_error = '';
+ // Start displaying the logs
+ MailPoet.MP2Migrator.startLogger();
+
+ // Disable the import button
+ MailPoet.MP2Migrator.import_button_label = jQuery('#import').val();
+ jQuery('#import').val(MailPoet.I18n.t('importing')).attr('disabled', 'disabled');
+ // Hide the Skip button
+ jQuery('#skip-import').hide();
+ // Show the stop button
+ jQuery('#stop-import').show();
+
+ // Run the import
+ MailPoet.Ajax.post({
+ api_version: window.mailpoet_api_version,
+ endpoint: 'MP2Migrator',
+ action: 'import',
+ data: {
+ }
+ }).always(function () {
+ MailPoet.MP2Migrator.stopLogger();
+ MailPoet.MP2Migrator.updateDisplay(); // Get the latest information after the import was stopped
+ MailPoet.MP2Migrator.reactivateImportButton();
+ }).done(function (response) {
+ if(response) {
+ MailPoet.MP2Migrator.fatal_error = response.data;
+ }
+ }).fail(function (response) {
+ if(response.errors.length > 0) {
+ MailPoet.Notice.error(
+ response.errors.map(function (error) {
+ return error.message;
+ }),
+ {scroll: true}
+ );
+ }
+ });
+ return false;
+ },
+
+ reactivateImportButton: function () {
+ jQuery('#import').val(MailPoet.MP2Migrator.import_button_label).removeAttr('disabled');
+ jQuery('#stop-import').hide();
+ },
+
+ stopImport: function () {
+ jQuery('#stop-import').attr('disabled', 'disabled');
+ // Stop the import
+ MailPoet.Ajax.post({
+ api_version: window.mailpoet_api_version,
+ endpoint: 'MP2Migrator',
+ action: 'stopImport',
+ data: {
+ }
+ }).always(function () {
+ jQuery('#stop-import').removeAttr('disabled'); // Enable the button
+ MailPoet.MP2Migrator.reactivateImportButton();
+ MailPoet.MP2Migrator.updateDisplay(); // Get the latest information after the import was stopped
+ });
+ MailPoet.MP2Migrator.stopLogger();
+ return false;
+ },
+
+ skipImport: function () {
+ MailPoet.Ajax.post({
+ api_version: window.mailpoet_api_version,
+ endpoint: 'MP2Migrator',
+ action: 'skipImport',
+ data: {
+ }
+ }).done(function () {
+ MailPoet.MP2Migrator.gotoWelcomePage();
+ });
+ return false;
+ },
+
+ gotoWelcomePage: function () {
+ window.location.href = 'admin.php?page=mailpoet-welcome';
+ return false;
+ }
+
+ };
+
+ /**
+ * Actions to run when the DOM is ready
+ */
+ jQuery(function () {
+ jQuery('#progressbar').progressbar({value: 0});
+
+ // Import button
+ jQuery('#import').click(function() {
+ MailPoet.MP2Migrator.startImport();
+ });
+
+ // Stop import button
+ jQuery('#stop-import').click(function() {
+ MailPoet.MP2Migrator.stopImport();
+ });
+
+ // Skip import link
+ jQuery('#skip-import').click(function() {
+ MailPoet.MP2Migrator.skipImport();
+ });
+
+ // Go to welcome page
+ jQuery('#goto-welcome').click(function() {
+ MailPoet.MP2Migrator.gotoWelcomePage();
+ });
+
+ // Update the display
+ MailPoet.MP2Migrator.updateDisplay();
+ });
+
+});
diff --git a/lib/API/JSON/v1/MP2Migrator.php b/lib/API/JSON/v1/MP2Migrator.php
new file mode 100644
index 0000000000..67d851ae21
--- /dev/null
+++ b/lib/API/JSON/v1/MP2Migrator.php
@@ -0,0 +1,64 @@
+MP2Migrator = new \MailPoet\Config\MP2Migrator();
+ }
+
+ /**
+ * Import end point
+ *
+ * @param object $data
+ * @return object
+ */
+ public function import($data) {
+ try {
+ $process = $this->MP2Migrator->import($data);
+ return $this->successResponse($process);
+ } catch(\Exception $e) {
+ return $this->errorResponse(array(
+ $e->getCode() => $e->getMessage()
+ ));
+ }
+ }
+
+ /**
+ * Stop import end point
+ *
+ * @param object $data
+ * @return object
+ */
+ public function stopImport($data) {
+ try {
+ $process = $this->MP2Migrator->stopImport();
+ return $this->successResponse($process);
+ } catch(\Exception $e) {
+ return $this->errorResponse(array(
+ $e->getCode() => $e->getMessage()
+ ));
+ }
+ }
+
+ /**
+ * Skip import end point
+ *
+ * @param object $data
+ * @return object
+ */
+ public function skipImport($data) {
+ try {
+ $process = $this->MP2Migrator->skipImport();
+ return $this->successResponse($process);
+ } catch(\Exception $e) {
+ return $this->errorResponse(array(
+ $e->getCode() => $e->getMessage()
+ ));
+ }
+ }
+
+}
diff --git a/lib/Config/Changelog.php b/lib/Config/Changelog.php
index 7c0b61a1f9..59295e0beb 100644
--- a/lib/Config/Changelog.php
+++ b/lib/Config/Changelog.php
@@ -34,12 +34,23 @@ class Changelog {
$version = Setting::getValue('version', null);
$redirect_url = null;
- if($version === null) {
- // new install
- $redirect_url = admin_url('admin.php?page=mailpoet-welcome');
- } else if($version !== Env::$version) {
- // update
- $redirect_url = admin_url('admin.php?page=mailpoet-update');
+ $mp2_migrator = new MP2Migrator();
+ if(!in_array($_GET['page'], array('mailpoet-migration', 'mailpoet-settings')) && $mp2_migrator->isMigrationStartedAndNotCompleted()) {
+ // Force the redirection if the migration has started but is not completed
+ $redirect_url = admin_url('admin.php?page=mailpoet-migration');
+ } else {
+ if($version === null) {
+ // new install
+ if($mp2_migrator->isMigrationNeeded()) {
+ // Migration from MP2
+ $redirect_url = admin_url('admin.php?page=mailpoet-migration');
+ } else {
+ $redirect_url = admin_url('admin.php?page=mailpoet-welcome');
+ }
+ } else if($version !== Env::$version) {
+ // update
+ $redirect_url = admin_url('admin.php?page=mailpoet-update');
+ }
}
if($redirect_url !== null) {
diff --git a/lib/Config/Database.php b/lib/Config/Database.php
index cee44d7339..2756a7a09c 100644
--- a/lib/Config/Database.php
+++ b/lib/Config/Database.php
@@ -77,6 +77,7 @@ class Database {
$statistics_opens = Env::$db_prefix . 'statistics_opens';
$statistics_unsubscribes = Env::$db_prefix . 'statistics_unsubscribes';
$statistics_forms = Env::$db_prefix . 'statistics_forms';
+ $mapping_to_external_entities = Env::$db_prefix . 'mapping_to_external_entities';
define('MP_SETTINGS_TABLE', $settings);
define('MP_SEGMENTS_TABLE', $segments);
@@ -98,6 +99,7 @@ class Database {
define('MP_STATISTICS_OPENS_TABLE', $statistics_opens);
define('MP_STATISTICS_UNSUBSCRIBES_TABLE', $statistics_unsubscribes);
define('MP_STATISTICS_FORMS_TABLE', $statistics_forms);
+ define('MP_MAPPING_TO_EXTERNAL_ENTITIES_TABLE', $mapping_to_external_entities);
}
}
}
diff --git a/lib/Config/MP2Migrator.php b/lib/Config/MP2Migrator.php
new file mode 100644
index 0000000000..95481590f9
--- /dev/null
+++ b/lib/Config/MP2Migrator.php
@@ -0,0 +1,742 @@
+defineMP2Tables();
+ $log_filename = 'mp2migration.log';
+ $this->log_file = Env::$temp_path . '/' . $log_filename;
+ $this->log_file_url = Env::$temp_url . '/' . $log_filename;
+ $this->progressbar = new ProgressBar('mp2migration');
+ }
+
+ private function defineMP2Tables() {
+ global $wpdb;
+
+ if(!defined('MP2_CAMPAIGN_TABLE')) {
+ define('MP2_CAMPAIGN_TABLE', $wpdb->prefix . 'wysija_campaign');
+ }
+ if(!defined('MP2_CUSTOM_FIELD_TABLE')) {
+ define('MP2_CUSTOM_FIELD_TABLE', $wpdb->prefix . 'wysija_custom_field');
+ }
+ if(!defined('MP2_EMAIL_TABLE')) {
+ define('MP2_EMAIL_TABLE', $wpdb->prefix . 'wysija_email');
+ }
+ if(!defined('MP2_FORM_TABLE')) {
+ define('MP2_FORM_TABLE', $wpdb->prefix . 'wysija_form');
+ }
+ if(!defined('MP2_LIST_TABLE')) {
+ define('MP2_LIST_TABLE', $wpdb->prefix . 'wysija_list');
+ }
+ if(!defined('MP2_USER_TABLE')) {
+ define('MP2_USER_TABLE', $wpdb->prefix . 'wysija_user');
+ }
+ if(!defined('MP2_USER_LIST_TABLE')) {
+ define('MP2_USER_LIST_TABLE', $wpdb->prefix . 'wysija_user_list');
+ }
+ }
+
+ /**
+ * Test if the migration is already started but is not completed
+ *
+ * @return boolean
+ */
+ public function isMigrationStartedAndNotCompleted() {
+ return Setting::getValue('mailpoet_migration_started', false) && !Setting::getValue('mailpoet_migration_complete', false);
+ }
+
+ /**
+ * Test if the migration is needed
+ *
+ * @return boolean
+ */
+ public function isMigrationNeeded() {
+ if(Setting::getValue('mailpoet_migration_complete')) {
+ return false;
+ } else {
+ return $this->tableExists(MP2_CAMPAIGN_TABLE); // Check if the MailPoet 2 tables exist
+ }
+ }
+
+ /**
+ * Store the "Skip import" choice
+ *
+ */
+ public function skipImport() {
+ Setting::setValue('mailpoet_migration_complete', true);
+ }
+
+ /**
+ * Test if a table exists
+ *
+ * @param string $table Table name
+ * @return boolean
+ */
+ private function tableExists($table) {
+ global $wpdb;
+
+ try {
+ $sql = "SHOW TABLES LIKE '{$table}'";
+ $result = $wpdb->query($sql);
+ return !empty($result);
+ } catch (Exception $e) {
+ // Do nothing
+ }
+
+ return false;
+ }
+
+ /**
+ * Initialize the migration page
+ *
+ */
+ public function init() {
+ if(!Setting::getValue('mailpoet_migration_started', false)) {
+ $this->emptyLog();
+ $this->progressbar->setTotalCount(0);
+ }
+ $this->enqueueScripts();
+ }
+
+ /**
+ * Register the JavaScript for the admin area.
+ *
+ */
+ private function enqueueScripts() {
+ wp_enqueue_script('jquery-ui-progressbar');
+ }
+
+ /**
+ * Write a message in the log file
+ *
+ * @param string $message
+ */
+ private function log($message) {
+ file_put_contents($this->log_file, "$message\n", FILE_APPEND);
+ }
+
+ /**
+ * Import the data from MailPoet 2
+ *
+ * @return string Result
+ */
+ public function import() {
+ set_time_limit(self::IMPORT_TIMEOUT_IN_SECONDS);
+ ob_start();
+ $datetime = new \MailPoet\WP\DateTime();
+ $this->log(sprintf('=== ' . __('START IMPORT', 'mailpoet') . ' %s ===', $datetime->formatTime(time(), \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT)));
+ Setting::setValue('import_stopped', false); // Reset the stop import action
+
+ if(!Setting::getValue('mailpoet_migration_started', false)) {
+ $this->eraseMP3Data();
+ Setting::setValue('mailpoet_migration_started', true);
+ $this->displayDataToMigrate();
+ }
+
+ $this->importSegments();
+ $this->importCustomFields();
+ $this->importSubscribers();
+
+ if(!$this->importStopped()) {
+ Setting::setValue('mailpoet_migration_complete', true);
+ $this->log(__('IMPORT COMPLETE', 'mailpoet'));
+ }
+
+ $this->log(sprintf('=== ' . __('END IMPORT', 'mailpoet') . ' %s ===', $datetime->formatTime(time(), \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT)));
+ $result = ob_get_contents();
+ ob_clean();
+ return $result;
+ }
+
+ /**
+ * Empty the log file
+ *
+ */
+ private function emptyLog() {
+ file_put_contents($this->log_file, '');
+ }
+
+ /**
+ * Erase all the MailPoet 3 data
+ *
+ */
+ private function eraseMP3Data() {
+ Activator::deactivate();
+ Activator::activate();
+
+ $this->deleteSegments();
+ $this->resetMigrationCounters();
+ $this->log(__("MailPoet data erased", 'mailpoet'));
+ }
+
+ /**
+ * Reset the migration counters
+ *
+ */
+ private function resetMigrationCounters() {
+ Setting::setValue('last_imported_user_id', 0);
+ Setting::setValue('last_imported_list_id', 0);
+ }
+
+ /**
+ * Delete the existing segments except the wp_users segment
+ *
+ */
+ private function deleteSegments() {
+ global $wpdb;
+
+ $table = MP_SEGMENTS_TABLE;
+ $wpdb->query("DELETE FROM {$table} WHERE type != '" . Segment::TYPE_WP_USERS . "'");
+ }
+
+ /**
+ * Stop the import
+ *
+ */
+ public function stopImport() {
+ Setting::setValue('import_stopped', true);
+ $this->log(__('IMPORT STOPPED BY USER', 'mailpoet'));
+ }
+
+ /**
+ * Test if the import must stop
+ *
+ * @return boolean Import must stop or not
+ */
+ private function importStopped() {
+ return Setting::getValue('import_stopped', false);
+ }
+
+ /**
+ * Display the number of data to migrate
+ *
+ */
+ private function displayDataToMigrate() {
+ $data = $this->getDataToMigrateAndResetProgressBar();
+ $this->log($data);
+ }
+
+ /**
+ * Get the data to migrate
+ *
+ * @return string Data to migrate
+ */
+ private function getDataToMigrateAndResetProgressBar() {
+ $result = '';
+ $total_count = 0;
+
+ $this->progressbar->setTotalCount(0);
+
+ $result .= __('MailPoet 2 data found:', 'mailpoet') . "\n";
+
+ // User Lists
+ $users_lists_count = \ORM::for_table(MP2_LIST_TABLE)->count();
+ $total_count += $users_lists_count;
+ $result .= sprintf(_n('%d subscribers list', '%d subscribers lists', $users_lists_count, 'mailpoet'), $users_lists_count) . "\n";
+
+ // Users
+ $users_count = \ORM::for_table(MP2_USER_TABLE)->count();
+ $total_count += $users_count;
+ $result .= sprintf(_n('%d subscriber', '%d subscribers', $users_count, 'mailpoet'), $users_count) . "\n";
+
+ // TODO to reactivate during the next phases
+ /*
+ // Emails
+ $emails_count = \ORM::for_table(MP2_EMAIL_TABLE)->count();
+ $total_count += $emails_count;
+ $result .= sprintf(_n('%d newsletter', '%d newsletters', $emails_count, 'mailpoet'), $emails_count) . "\n";
+
+ // Forms
+ $forms_count = \ORM::for_table(MP2_FORM_TABLE)->count();
+ $total_count += $forms_count;
+ $result .= sprintf(_n('%d form', '%d forms', $forms_count, 'mailpoet'), $forms_count) . "\n";
+ */
+
+ $this->progressbar->setTotalCount($total_count);
+
+ return $result;
+ }
+
+ /**
+ * Import the subscribers segments
+ *
+ */
+ private function importSegments() {
+ $imported_segments_count = 0;
+ if($this->importStopped()) {
+ $this->segments_mapping = $this->getImportedMapping('segments');
+ return;
+ }
+ $this->log(__("Importing segments...", 'mailpoet'));
+ do {
+ if($this->importStopped()) {
+ break;
+ }
+ $lists = $this->getLists(self::CHUNK_SIZE);
+ $lists_count = count($lists);
+
+ if(is_array($lists)) {
+ foreach($lists as $list) {
+ $segment = $this->importSegment($list);
+ if(!empty($segment)) {
+ $imported_segments_count++;
+ }
+ }
+ }
+ $this->progressbar->incrementCurrentCount($lists_count);
+ } while(($lists != null) && ($lists_count > 0));
+
+ $this->segments_mapping = $this->getImportedMapping('segments');
+
+ $this->log(sprintf(_n("%d segment imported", "%d segments imported", $imported_segments_count, 'mailpoet'), $imported_segments_count));
+ }
+
+ /**
+ * Get the Mailpoet 2 users lists
+ *
+ * @global object $wpdb
+ * @param int $limit Number of users max
+ * @return array Users Lists
+ */
+ private function getLists($limit) {
+ global $wpdb;
+ $lists = array();
+
+ $last_id = Setting::getValue('last_imported_list_id', 0);
+ $table = MP2_LIST_TABLE;
+ $sql = "
+ SELECT l.list_id, l.name, l.description, l.is_enabled, l.created_at
+ FROM `$table` l
+ WHERE l.list_id > '$last_id'
+ ORDER BY l.list_id
+ LIMIT $limit
+ ";
+ $lists = $wpdb->get_results($sql, ARRAY_A);
+
+ return $lists;
+ }
+
+ /**
+ * Import a segment
+ *
+ * @param array $list_data List data
+ * @return Segment
+ */
+ private function importSegment($list_data) {
+ $datetime = new \MailPoet\WP\DateTime();
+ if($list_data['is_enabled']) {
+ $segment = Segment::createOrUpdate(array(
+ 'name' => $list_data['name'],
+ 'type' => 'default',
+ 'description' => !empty($list_data['description']) ? $list_data['description'] : '',
+ 'created_at' => $datetime->formatTime($list_data['created_at'], \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT),
+ ));
+ } else {
+ $segment = Segment::getWPSegment();
+ }
+ if(!empty($segment)) {
+ // Map the segment with its old ID
+ $mapping = new MappingToExternalEntities();
+ $mapping->create(array(
+ 'old_id' => $list_data['list_id'],
+ 'type' => 'segments',
+ 'new_id' => $segment->id,
+ 'created_at' => $datetime->formatTime(time(), \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT),
+ ));
+ }
+ Setting::setValue('last_imported_list_id', $list_data['list_id']);
+ return $segment;
+ }
+
+ /**
+ * Import the custom fields
+ *
+ */
+ private function importCustomFields() {
+ $imported_custom_fields_count = 0;
+ if($this->importStopped()) {
+ return;
+ }
+ $this->log(__("Importing custom fields...", 'mailpoet'));
+ $custom_fields = $this->getCustomFields();
+
+ foreach($custom_fields as $custom_field) {
+ $result = $this->importCustomField($custom_field);
+ if(!empty($result)) {
+ $imported_custom_fields_count++;
+ }
+ }
+
+ $this->log(sprintf(_n("%d custom field imported", "%d custom fields imported", $imported_custom_fields_count, 'mailpoet'), $imported_custom_fields_count));
+ }
+
+ /**
+ * Get the Mailpoet 2 custom fields
+ *
+ * @global object $wpdb
+ * @return array Custom fields
+ */
+ private function getCustomFields() {
+ global $wpdb;
+ $custom_fields = array();
+
+ $table = MP2_CUSTOM_FIELD_TABLE;
+ $sql = "
+ SELECT cf.id, cf.name, cf.type, cf.required, cf.settings
+ FROM `$table` cf
+ ";
+ $custom_fields = $wpdb->get_results($sql, ARRAY_A);
+
+ return $custom_fields;
+ }
+
+ /**
+ * Import a custom field
+ *
+ * @param array $custom_field MP2 custom field
+ * @return CustomField
+ */
+ private function importCustomField($custom_field) {
+ $data = array(
+ 'id' => $custom_field['id'],
+ 'name' => $custom_field['name'],
+ 'type' => $this->mapCustomFieldType($custom_field['type']),
+ 'params' => $this->mapCustomFieldParams($custom_field),
+ );
+ $custom_field = new CustomField();
+ $custom_field->createOrUpdate($data);
+ return $custom_field;
+ }
+
+ /**
+ * Map the MailPoet 2 custom field type with the MailPoet custom field type
+ *
+ * @param string $mp2_type MP2 custom field type
+ * @return string MP3 custom field type
+ */
+ private function mapCustomFieldType($mp2_type) {
+ $type = '';
+ switch($mp2_type) {
+ case 'input':
+ $type = 'text';
+ break;
+ default:
+ $type = $mp2_type;
+ }
+ return $type;
+ }
+
+ /**
+ * Map the MailPoet 2 custom field settings with the MailPoet custom field params
+ *
+ * @param array $custom_field MP2 custom field
+ * @return string serialized MP3 custom field params
+ */
+ private function mapCustomFieldParams($custom_field) {
+ $params = unserialize($custom_field['settings']);
+ $params['label'] = $custom_field['name'];
+ if(isset($params['validate'])) {
+ $params['validate'] = $this->mapCustomFieldValidateValue($params['validate']);
+ }
+ if(isset($params['date_order'])) { // Convert the date_order field
+ $params['date_format'] = strtoupper($params['date_order']);
+ unset($params['date_order']);
+ }
+ return $params;
+ }
+
+ /**
+ * Map the validate value
+ *
+ * @param string $mp2_value MP2 value
+ * @return string MP3 value
+ */
+ private function mapCustomFieldValidateValue($mp2_value) {
+ $value = '';
+ switch($mp2_value) {
+ case 'onlyLetterSp':
+ case 'onlyLetterNumber':
+ $value = 'alphanum';
+ break;
+ case 'onlyNumberSp':
+ $value = 'number';
+ break;
+ case 'phone':
+ $value = 'phone';
+ break;
+ }
+ return $value;
+ }
+
+ /**
+ * Import the subscribers
+ *
+ */
+ private function importSubscribers() {
+ $imported_subscribers_count = 0;
+ if($this->importStopped()) {
+ return;
+ }
+ $this->log(__("Importing subscribers...", 'mailpoet'));
+ $this->wp_users_segment = Segment::getWPSegment();
+ do {
+ if($this->importStopped()) {
+ break;
+ }
+ $users = $this->getUsers(self::CHUNK_SIZE);
+ $users_count = count($users);
+
+ if(is_array($users)) {
+ foreach($users as $user) {
+ $subscriber = $this->importSubscriber($user);
+ if(!empty($subscriber)) {
+ $imported_subscribers_count++;
+ $this->importSubscriberSegments($subscriber, $user['user_id']);
+ $this->importSubscriberCustomFields($subscriber, $user);
+ }
+ }
+ }
+ $this->progressbar->incrementCurrentCount($users_count);
+ } while(($users != null) && ($users_count > 0));
+
+ $this->log(sprintf(_n("%d subscriber imported", "%d subscribers imported", $imported_subscribers_count, 'mailpoet'), $imported_subscribers_count));
+ }
+
+ /**
+ * Get the Mailpoet 2 users
+ *
+ * @global object $wpdb
+ * @param int $limit Number of users max
+ * @return array Users
+ */
+ private function getUsers($limit) {
+ global $wpdb;
+ $users = array();
+
+ $last_id = Setting::getValue('last_imported_user_id', 0);
+ $table = MP2_USER_TABLE;
+ $sql = "
+ SELECT u.*
+ FROM `$table` u
+ WHERE u.user_id > '$last_id'
+ ORDER BY u.user_id
+ LIMIT $limit
+ ";
+ $users = $wpdb->get_results($sql, ARRAY_A);
+
+ return $users;
+ }
+
+ /**
+ * Import a subscriber
+ *
+ * @param array $user_data User data
+ * @return Subscriber
+ */
+ private function importSubscriber($user_data) {
+ $datetime = new \MailPoet\WP\DateTime();
+ $subscriber = Subscriber::createOrUpdate(array(
+ 'wp_user_id' => !empty($user_data['wpuser_id']) ? $user_data['wpuser_id'] : null,
+ 'email' => $user_data['email'],
+ 'first_name' => $user_data['firstname'],
+ 'last_name' => $user_data['lastname'],
+ 'status' => $this->mapUserStatus($user_data['status']),
+ 'created_at' => $datetime->formatTime($user_data['created_at'], \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT),
+ 'subscribed_ip' => !empty($user_data['ip']) ? $user_data['ip'] : null,
+ 'confirmed_ip' => !empty($user_data['confirmed_ip']) ? $user_data['confirmed_ip'] : null,
+ 'confirmed_at' => !empty($user_data['confirmed_at']) ? $datetime->formatTime($user_data['confirmed_at'], \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT) : null,
+ ));
+ Setting::setValue('last_imported_user_id', $user_data['user_id']);
+ if(!empty($subscriber)) {
+ // Map the subscriber with its old ID
+ $mapping = new MappingToExternalEntities();
+ $mapping->create(array(
+ 'old_id' => $user_data['user_id'],
+ 'type' => 'subscribers',
+ 'new_id' => $subscriber->id,
+ 'created_at' => $datetime->formatTime(time(), \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT),
+ ));
+ }
+ return $subscriber;
+ }
+
+ /**
+ * Map the MailPoet 2 user status with MailPoet 3
+ *
+ * @param int $mp2_user_status MP2 user status
+ * @return string MP3 user status
+ */
+ private function mapUserStatus($mp2_user_status) {
+ switch($mp2_user_status) {
+ case 1:
+ $status = 'subscribed';
+ break;
+ case -1:
+ $status = 'unsubscribed';
+ break;
+ case 0:
+ default:
+ $status = 'unconfirmed';
+ }
+ return $status;
+ }
+
+ /**
+ * Import the segments for a subscriber
+ *
+ * @param Subscriber $subscriber MP3 subscriber
+ * @param int $user_id MP2 user ID
+ */
+ private function importSubscriberSegments($subscriber, $user_id) {
+ $user_lists = $this->getUserLists($user_id);
+ foreach($user_lists as $user_list) {
+ $this->importSubscriberSegment($subscriber->id, $user_list);
+ }
+ }
+
+ /**
+ * Get the lists for a user
+ *
+ * @global object $wpdb
+ * @param int $user_id User ID
+ * @return array Users Lists
+ */
+ private function getUserLists($user_id) {
+ global $wpdb;
+ $user_lists = array();
+
+ $table = MP2_USER_LIST_TABLE;
+ $sql = "
+ SELECT ul.list_id, ul.sub_date, ul.unsub_date
+ FROM `$table` ul
+ WHERE ul.user_id = '$user_id'
+ ";
+ $user_lists = $wpdb->get_results($sql, ARRAY_A);
+
+ return $user_lists;
+ }
+
+ /**
+ * Import a subscriber segment
+ *
+ * @param int $subscriber_id
+ * @param array $user_list
+ * @return SubscriberSegment
+ */
+ private function importSubscriberSegment($subscriber_id, $user_list) {
+ $subscriber_segment = null;
+ $datetime = new \MailPoet\WP\DateTime();
+ if(isset($this->segments_mapping[$user_list['list_id']])) {
+ $segment_id = $this->segments_mapping[$user_list['list_id']];
+ $status = (($segment_id == $this->wp_users_segment->id) || empty($user_list['unsub_date'])) ? 'subscribed' : 'unsubscribed'; // the users belonging to the wp_users segment are always subscribed
+ $data = array(
+ 'subscriber_id' => $subscriber_id,
+ 'segment_id' => $segment_id,
+ 'status' => $status,
+ 'created_at' => $datetime->formatTime($user_list['sub_date'], \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT),
+ 'updated_at' => !empty($user_list['unsub_date']) ? $datetime->formatTime($user_list['unsub_date'], \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT) : null,
+ );
+ $subscriber_segment = new SubscriberSegment();
+ $subscriber_segment->createOrUpdate($data);
+ }
+ return $subscriber_segment;
+ }
+
+ /**
+ * Import the custom fields values for a subscriber
+ *
+ * @param Subscriber $subscriber MP3 subscriber
+ * @param array $user MP2 user
+ */
+ private function importSubscriberCustomFields($subscriber, $user) {
+ $imported_custom_fields = $this->getImportedCustomFields();
+ foreach($imported_custom_fields as $custom_field) {
+ $custom_field_column = 'cf_' . $custom_field['id'];
+ if(isset($custom_field_column)) {
+ $this->importSubscriberCustomField($subscriber->id, $custom_field, $user[$custom_field_column]);
+ }
+ }
+ }
+
+ /**
+ * Get the imported custom fields
+ *
+ * @global object $wpdb
+ * @return array Imported custom fields
+ *
+ */
+ private function getImportedCustomFields() {
+ global $wpdb;
+ $table = MP_CUSTOM_FIELDS_TABLE;
+ $sql = "
+ SELECT cf.id, cf.name, cf.type
+ FROM `$table` cf
+ ";
+ $custom_fields = $wpdb->get_results($sql, ARRAY_A);
+ return $custom_fields;
+ }
+
+ /**
+ * Import a subscriber custom field
+ *
+ * @param int $subscriber_id Subscriber ID
+ * @param int $custom_field Custom field
+ * @param string $custom_field_value Custom field value
+ * @return SubscriberCustomField
+ */
+ private function importSubscriberCustomField($subscriber_id, $custom_field, $custom_field_value) {
+ if($custom_field['type'] == 'date') {
+ $datetime = new \MailPoet\WP\DateTime();
+ $value = $datetime->formatTime($custom_field_value, \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT); // Convert the date field
+ } else {
+ $value = $custom_field_value;
+ }
+ $data = array(
+ 'subscriber_id' => $subscriber_id,
+ 'custom_field_id' => $custom_field['id'],
+ 'value' => isset($value) ? $value : '',
+ );
+ $subscriber_custom_field = new SubscriberCustomField();
+ $subscriber_custom_field->createOrUpdate($data);
+ return $subscriber_custom_field;
+ }
+
+ /**
+ * Get the mapping between the MP2 and the imported MP3 IDs
+ *
+ * @param string $model Model (segment,...)
+ * @return array Mapping
+ */
+ public function getImportedMapping($model) {
+ $mappings = array();
+ $mapping_relations = MappingToExternalEntities::where('type', $model)->findArray();
+ foreach($mapping_relations as $relation) {
+ $mappings[$relation['old_id']] = $relation['new_id'];
+ }
+ return $mappings;
+ }
+
+}
diff --git a/lib/Config/Menu.php b/lib/Config/Menu.php
index 42bcdb86ad..9b62cafcf3 100644
--- a/lib/Config/Menu.php
+++ b/lib/Config/Menu.php
@@ -215,6 +215,18 @@ class Menu {
)
);
+ add_submenu_page(
+ true,
+ $this->setPageTitle(__('Migration', 'mailpoet')),
+ '',
+ Env::$required_permission,
+ 'mailpoet-migration',
+ array(
+ $this,
+ 'migration'
+ )
+ );
+
add_submenu_page(
true,
$this->setPageTitle(__('Update', 'mailpoet')),
@@ -279,6 +291,16 @@ class Menu {
$this->displayPage('welcome.html', $data);
}
+ function migration() {
+ $mp2_migrator = new MP2Migrator();
+ $mp2_migrator->init();
+ $data = array(
+ 'log_file_url' => $mp2_migrator->log_file_url,
+ 'progress_url' => $mp2_migrator->progressbar->url,
+ );
+ $this->displayPage('mp2migration.html', $data);
+ }
+
function update() {
global $wp;
$current_url = home_url(add_query_arg($wp->query_string, $wp->request));
diff --git a/lib/Config/Migrator.php b/lib/Config/Migrator.php
index 10e22d0e1d..b39c237c1c 100644
--- a/lib/Config/Migrator.php
+++ b/lib/Config/Migrator.php
@@ -33,7 +33,8 @@ class Migrator {
'statistics_clicks',
'statistics_opens',
'statistics_unsubscribes',
- 'statistics_forms'
+ 'statistics_forms',
+ 'mapping_to_external_entities'
);
}
@@ -79,7 +80,7 @@ class Migrator {
function settings() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
- 'name varchar(20) NOT NULL,',
+ 'name varchar(50) NOT NULL,',
'value longtext,',
'created_at TIMESTAMP NULL,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
@@ -363,6 +364,18 @@ class Migrator {
return $this->sqlify(__FUNCTION__, $attributes);
}
+ function mappingToExternalEntities() {
+ $attributes = array(
+ 'old_id mediumint(9) NOT NULL,',
+ 'type varchar(50) NOT NULL,',
+ 'new_id mediumint(9) NOT NULL,',
+ 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
+ 'PRIMARY KEY (old_id, type),',
+ 'KEY new_id (new_id)'
+ );
+ return $this->sqlify(__FUNCTION__, $attributes);
+ }
+
private function sqlify($model, $attributes) {
$table = $this->prefix . Helpers::camelCaseToUnderscore($model);
diff --git a/lib/Models/MappingToExternalEntities.php b/lib/Models/MappingToExternalEntities.php
new file mode 100644
index 0000000000..1175a5d3bf
--- /dev/null
+++ b/lib/Models/MappingToExternalEntities.php
@@ -0,0 +1,15 @@
+hydrate($data);
+ return $relation->save();
+ }
+
+}
\ No newline at end of file
diff --git a/lib/Util/Helpers.php b/lib/Util/Helpers.php
index fb9922a7d0..89444c2465 100644
--- a/lib/Util/Helpers.php
+++ b/lib/Util/Helpers.php
@@ -136,4 +136,5 @@ class Helpers {
static function splitObject($object = array()) {
return explode(self::DIVIDER, $object);
}
-}
\ No newline at end of file
+
+}
diff --git a/lib/Util/ProgressBar.php b/lib/Util/ProgressBar.php
new file mode 100644
index 0000000000..cd7b2bbfbe
--- /dev/null
+++ b/lib/Util/ProgressBar.php
@@ -0,0 +1,98 @@
+filename = Env::$temp_path . '/' . $filename;
+ $this->url = Env::$temp_url . '/' . $filename;
+ $counters = $this->readProgress();
+ if(isset($counters->total)) {
+ $this->total_count = $counters->total;
+ }
+ if(isset($counters->current)) {
+ $this->current_count = $counters->current;
+ }
+ }
+
+ /**
+ * Get the progress file URL
+ *
+ * @return string Progress file URL
+ */
+ public function getUrl() {
+ return $this->url;
+ }
+
+ /**
+ * Read the progress counters
+ *
+ * @return array|false Array of counters
+ */
+ private function readProgress() {
+ if(file_exists($this->filename)) {
+ $json_content = file_get_contents($this->filename);
+ return json_decode($json_content);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Set the total count
+ *
+ * @param int $count Count
+ */
+ public function setTotalCount($count) {
+ if($count != $this->total_count) {
+ $this->total_count = $count;
+ $this->current_count = 0;
+ $this->saveProgress();
+ }
+ }
+
+ /**
+ * Increment the current count
+ *
+ * @param int $count Count
+ */
+ public function incrementCurrentCount($count) {
+ $this->current_count += $count;
+ $this->saveProgress();
+ }
+
+ /**
+ * Save the progress counters
+ *
+ */
+ private function saveProgress() {
+ file_put_contents($this->filename, json_encode(array(
+ 'total' => $this->total_count,
+ 'current' => $this->current_count,
+ )));
+ }
+
+ }
+
+}
diff --git a/tests/_bootstrap.php b/tests/_bootstrap.php
index d2ff50b4c0..f4ce413ef0 100644
--- a/tests/_bootstrap.php
+++ b/tests/_bootstrap.php
@@ -93,4 +93,22 @@ abstract class MailPoetTest extends \Codeception\TestCase\Test {
protected $runTestInSeparateProcess = false;
protected $preserveGlobalState = false;
protected $inIsolation = false;
+
+ /**
+ * Call protected/private method of a class.
+ *
+ * @param object &$object Instantiated object that we will run method on.
+ * @param string $methodName Method name to call
+ * @param array $parameters Array of parameters to pass into method.
+ *
+ * @return mixed Method return.
+ */
+ public function invokeMethod(&$object, $methodName, array $parameters = array()) {
+ $reflection = new \ReflectionClass(get_class($object));
+ $method = $reflection->getMethod($methodName);
+ $method->setAccessible(true);
+
+ return $method->invokeArgs($object, $parameters);
+ }
+
}
\ No newline at end of file
diff --git a/tests/_data/createMP2Tables.sql b/tests/_data/createMP2Tables.sql
new file mode 100644
index 0000000000..5cb314dec6
--- /dev/null
+++ b/tests/_data/createMP2Tables.sql
@@ -0,0 +1,431 @@
+-- phpMyAdmin SQL Dump
+-- version 4.4.10
+-- http://www.phpmyadmin.net
+--
+-- Client : localhost:3306
+-- Généré le : Mer 26 Avril 2017 à 17:52
+-- Version du serveur : 5.5.42
+-- Version de PHP : 7.0.0
+
+SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
+SET time_zone = "+00:00";
+
+--
+-- Base de données : `mailpoet`
+--
+
+-- --------------------------------------------------------
+
+--
+-- Structure de la table `wp_wysija_campaign`
+--
+
+DROP TABLE IF EXISTS `wp_wysija_campaign`;
+CREATE TABLE `wp_wysija_campaign` (
+ `campaign_id` int(10) unsigned NOT NULL,
+ `name` varchar(250) DEFAULT NULL,
+ `description` text
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Structure de la table `wp_wysija_campaign_list`
+--
+
+DROP TABLE IF EXISTS `wp_wysija_campaign_list`;
+CREATE TABLE `wp_wysija_campaign_list` (
+ `list_id` int(10) unsigned NOT NULL,
+ `campaign_id` int(10) unsigned NOT NULL,
+ `filter` text
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Structure de la table `wp_wysija_custom_field`
+--
+
+DROP TABLE IF EXISTS `wp_wysija_custom_field`;
+CREATE TABLE `wp_wysija_custom_field` (
+ `id` mediumint(9) NOT NULL,
+ `name` tinytext NOT NULL,
+ `type` tinytext NOT NULL,
+ `required` tinyint(1) NOT NULL DEFAULT '0',
+ `settings` text
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Structure de la table `wp_wysija_email`
+--
+
+DROP TABLE IF EXISTS `wp_wysija_email`;
+CREATE TABLE `wp_wysija_email` (
+ `email_id` int(10) unsigned NOT NULL,
+ `campaign_id` int(10) unsigned NOT NULL DEFAULT '0',
+ `subject` varchar(250) NOT NULL DEFAULT '',
+ `body` longtext,
+ `created_at` int(10) unsigned DEFAULT NULL,
+ `modified_at` int(10) unsigned DEFAULT NULL,
+ `sent_at` int(10) unsigned DEFAULT NULL,
+ `from_email` varchar(250) DEFAULT NULL,
+ `from_name` varchar(250) DEFAULT NULL,
+ `replyto_email` varchar(250) DEFAULT NULL,
+ `replyto_name` varchar(250) DEFAULT NULL,
+ `attachments` text,
+ `status` tinyint(4) NOT NULL DEFAULT '0',
+ `type` tinyint(4) NOT NULL DEFAULT '1',
+ `number_sent` int(10) unsigned NOT NULL DEFAULT '0',
+ `number_opened` int(10) unsigned NOT NULL DEFAULT '0',
+ `number_clicked` int(10) unsigned NOT NULL DEFAULT '0',
+ `number_unsub` int(10) unsigned NOT NULL DEFAULT '0',
+ `number_bounce` int(10) unsigned NOT NULL DEFAULT '0',
+ `number_forward` int(10) unsigned NOT NULL DEFAULT '0',
+ `params` text,
+ `wj_data` longtext,
+ `wj_styles` longtext
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Structure de la table `wp_wysija_email_user_stat`
+--
+
+DROP TABLE IF EXISTS `wp_wysija_email_user_stat`;
+CREATE TABLE `wp_wysija_email_user_stat` (
+ `user_id` int(10) unsigned NOT NULL,
+ `email_id` int(10) unsigned NOT NULL,
+ `sent_at` int(10) unsigned NOT NULL,
+ `opened_at` int(10) unsigned DEFAULT NULL,
+ `status` tinyint(4) NOT NULL DEFAULT '0'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Structure de la table `wp_wysija_email_user_url`
+--
+
+DROP TABLE IF EXISTS `wp_wysija_email_user_url`;
+CREATE TABLE `wp_wysija_email_user_url` (
+ `email_id` int(10) unsigned NOT NULL,
+ `user_id` int(10) unsigned NOT NULL,
+ `url_id` int(10) unsigned NOT NULL,
+ `clicked_at` int(10) unsigned DEFAULT NULL,
+ `number_clicked` int(10) unsigned NOT NULL DEFAULT '0'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Structure de la table `wp_wysija_form`
+--
+
+DROP TABLE IF EXISTS `wp_wysija_form`;
+CREATE TABLE `wp_wysija_form` (
+ `form_id` int(10) unsigned NOT NULL,
+ `name` tinytext CHARACTER SET utf8 COLLATE utf8_bin,
+ `data` longtext CHARACTER SET utf8 COLLATE utf8_bin,
+ `styles` longtext CHARACTER SET utf8 COLLATE utf8_bin,
+ `subscribed` int(10) unsigned NOT NULL DEFAULT '0'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Structure de la table `wp_wysija_list`
+--
+
+DROP TABLE IF EXISTS `wp_wysija_list`;
+CREATE TABLE `wp_wysija_list` (
+ `list_id` int(10) unsigned NOT NULL,
+ `name` varchar(250) DEFAULT NULL,
+ `namekey` varchar(255) DEFAULT NULL,
+ `description` text,
+ `unsub_mail_id` int(10) unsigned NOT NULL DEFAULT '0',
+ `welcome_mail_id` int(10) unsigned NOT NULL DEFAULT '0',
+ `is_enabled` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `is_public` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `created_at` int(10) unsigned DEFAULT NULL,
+ `ordering` int(10) unsigned NOT NULL DEFAULT '0'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Structure de la table `wp_wysija_queue`
+--
+
+DROP TABLE IF EXISTS `wp_wysija_queue`;
+CREATE TABLE `wp_wysija_queue` (
+ `user_id` int(10) unsigned NOT NULL,
+ `email_id` int(10) unsigned NOT NULL,
+ `send_at` int(10) unsigned NOT NULL DEFAULT '0',
+ `priority` tinyint(4) NOT NULL DEFAULT '0',
+ `number_try` tinyint(3) unsigned NOT NULL DEFAULT '0'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Structure de la table `wp_wysija_url`
+--
+
+DROP TABLE IF EXISTS `wp_wysija_url`;
+CREATE TABLE `wp_wysija_url` (
+ `url_id` int(10) unsigned NOT NULL,
+ `name` varchar(250) DEFAULT NULL,
+ `url` text
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Structure de la table `wp_wysija_url_mail`
+--
+
+DROP TABLE IF EXISTS `wp_wysija_url_mail`;
+CREATE TABLE `wp_wysija_url_mail` (
+ `email_id` int(11) NOT NULL,
+ `url_id` int(10) unsigned NOT NULL,
+ `unique_clicked` int(10) unsigned NOT NULL DEFAULT '0',
+ `total_clicked` int(10) unsigned NOT NULL DEFAULT '0'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Structure de la table `wp_wysija_user`
+--
+
+DROP TABLE IF EXISTS `wp_wysija_user`;
+CREATE TABLE `wp_wysija_user` (
+ `user_id` int(10) unsigned NOT NULL,
+ `wpuser_id` int(10) unsigned NOT NULL DEFAULT '0',
+ `email` varchar(255) NOT NULL,
+ `firstname` varchar(255) NOT NULL DEFAULT '',
+ `lastname` varchar(255) NOT NULL DEFAULT '',
+ `ip` varchar(100) NOT NULL,
+ `confirmed_ip` varchar(100) NOT NULL DEFAULT '0',
+ `confirmed_at` int(10) unsigned DEFAULT NULL,
+ `last_opened` int(10) unsigned DEFAULT NULL,
+ `last_clicked` int(10) unsigned DEFAULT NULL,
+ `keyuser` varchar(255) NOT NULL DEFAULT '',
+ `created_at` int(10) unsigned DEFAULT NULL,
+ `status` tinyint(4) NOT NULL DEFAULT '0',
+ `domain` varchar(255) DEFAULT '',
+ `cf_1` varchar(100) DEFAULT NULL,
+ `cf_3` varchar(255) DEFAULT NULL,
+ `cf_4` varchar(255) DEFAULT NULL,
+ `cf_5` tinyint(1) DEFAULT NULL,
+ `cf_6` varchar(255) DEFAULT NULL,
+ `cf_7` int(20) DEFAULT NULL,
+ `cf_8` varchar(100) DEFAULT NULL,
+ `cf_9` varchar(100) DEFAULT NULL,
+ `cf_10` varchar(100) DEFAULT NULL,
+ `cf_11` varchar(100) DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Structure de la table `wp_wysija_user_field`
+--
+
+DROP TABLE IF EXISTS `wp_wysija_user_field`;
+CREATE TABLE `wp_wysija_user_field` (
+ `field_id` int(10) unsigned NOT NULL,
+ `name` varchar(250) DEFAULT NULL,
+ `column_name` varchar(250) NOT NULL DEFAULT '',
+ `type` tinyint(3) unsigned DEFAULT '0',
+ `values` text,
+ `default` varchar(250) NOT NULL DEFAULT '',
+ `is_required` tinyint(3) unsigned NOT NULL DEFAULT '0',
+ `error_message` varchar(250) NOT NULL DEFAULT ''
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Structure de la table `wp_wysija_user_history`
+--
+
+DROP TABLE IF EXISTS `wp_wysija_user_history`;
+CREATE TABLE `wp_wysija_user_history` (
+ `history_id` int(10) unsigned NOT NULL,
+ `user_id` int(10) unsigned NOT NULL,
+ `email_id` int(10) unsigned DEFAULT '0',
+ `type` varchar(250) NOT NULL DEFAULT '',
+ `details` text,
+ `executed_at` int(10) unsigned DEFAULT NULL,
+ `executed_by` int(10) unsigned DEFAULT NULL,
+ `source` text
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Structure de la table `wp_wysija_user_list`
+--
+
+DROP TABLE IF EXISTS `wp_wysija_user_list`;
+CREATE TABLE `wp_wysija_user_list` (
+ `list_id` int(10) unsigned NOT NULL,
+ `user_id` int(10) unsigned NOT NULL,
+ `sub_date` int(10) unsigned DEFAULT '0',
+ `unsub_date` int(10) unsigned DEFAULT '0'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Index pour les tables exportées
+--
+
+--
+-- Index pour la table `wp_wysija_campaign`
+--
+ALTER TABLE `wp_wysija_campaign`
+ ADD PRIMARY KEY (`campaign_id`);
+
+--
+-- Index pour la table `wp_wysija_campaign_list`
+--
+ALTER TABLE `wp_wysija_campaign_list`
+ ADD PRIMARY KEY (`list_id`,`campaign_id`);
+
+--
+-- Index pour la table `wp_wysija_custom_field`
+--
+ALTER TABLE `wp_wysija_custom_field`
+ ADD PRIMARY KEY (`id`);
+
+--
+-- Index pour la table `wp_wysija_email`
+--
+ALTER TABLE `wp_wysija_email`
+ ADD PRIMARY KEY (`email_id`);
+
+--
+-- Index pour la table `wp_wysija_email_user_stat`
+--
+ALTER TABLE `wp_wysija_email_user_stat`
+ ADD PRIMARY KEY (`user_id`,`email_id`);
+
+--
+-- Index pour la table `wp_wysija_email_user_url`
+--
+ALTER TABLE `wp_wysija_email_user_url`
+ ADD PRIMARY KEY (`user_id`,`email_id`,`url_id`);
+
+--
+-- Index pour la table `wp_wysija_form`
+--
+ALTER TABLE `wp_wysija_form`
+ ADD PRIMARY KEY (`form_id`);
+
+--
+-- Index pour la table `wp_wysija_list`
+--
+ALTER TABLE `wp_wysija_list`
+ ADD PRIMARY KEY (`list_id`);
+
+--
+-- Index pour la table `wp_wysija_queue`
+--
+ALTER TABLE `wp_wysija_queue`
+ ADD PRIMARY KEY (`user_id`,`email_id`),
+ ADD KEY `SENT_AT_INDEX` (`send_at`);
+
+--
+-- Index pour la table `wp_wysija_url`
+--
+ALTER TABLE `wp_wysija_url`
+ ADD PRIMARY KEY (`url_id`);
+
+--
+-- Index pour la table `wp_wysija_url_mail`
+--
+ALTER TABLE `wp_wysija_url_mail`
+ ADD PRIMARY KEY (`email_id`,`url_id`);
+
+--
+-- Index pour la table `wp_wysija_user`
+--
+ALTER TABLE `wp_wysija_user`
+ ADD PRIMARY KEY (`user_id`),
+ ADD UNIQUE KEY `EMAIL_UNIQUE` (`email`);
+
+--
+-- Index pour la table `wp_wysija_user_field`
+--
+ALTER TABLE `wp_wysija_user_field`
+ ADD PRIMARY KEY (`field_id`);
+
+--
+-- Index pour la table `wp_wysija_user_history`
+--
+ALTER TABLE `wp_wysija_user_history`
+ ADD PRIMARY KEY (`history_id`);
+
+--
+-- Index pour la table `wp_wysija_user_list`
+--
+ALTER TABLE `wp_wysija_user_list`
+ ADD PRIMARY KEY (`list_id`,`user_id`);
+
+--
+-- AUTO_INCREMENT pour les tables exportées
+--
+
+--
+-- AUTO_INCREMENT pour la table `wp_wysija_campaign`
+--
+ALTER TABLE `wp_wysija_campaign`
+ MODIFY `campaign_id` int(10) unsigned NOT NULL AUTO_INCREMENT;
+--
+-- AUTO_INCREMENT pour la table `wp_wysija_custom_field`
+--
+ALTER TABLE `wp_wysija_custom_field`
+ MODIFY `id` mediumint(9) NOT NULL AUTO_INCREMENT;
+--
+-- AUTO_INCREMENT pour la table `wp_wysija_email`
+--
+ALTER TABLE `wp_wysija_email`
+ MODIFY `email_id` int(10) unsigned NOT NULL AUTO_INCREMENT;
+--
+-- AUTO_INCREMENT pour la table `wp_wysija_form`
+--
+ALTER TABLE `wp_wysija_form`
+ MODIFY `form_id` int(10) unsigned NOT NULL AUTO_INCREMENT;
+--
+-- AUTO_INCREMENT pour la table `wp_wysija_list`
+--
+ALTER TABLE `wp_wysija_list`
+ MODIFY `list_id` int(10) unsigned NOT NULL AUTO_INCREMENT;
+--
+-- AUTO_INCREMENT pour la table `wp_wysija_url`
+--
+ALTER TABLE `wp_wysija_url`
+ MODIFY `url_id` int(10) unsigned NOT NULL AUTO_INCREMENT;
+--
+-- AUTO_INCREMENT pour la table `wp_wysija_url_mail`
+--
+ALTER TABLE `wp_wysija_url_mail`
+ MODIFY `email_id` int(11) NOT NULL AUTO_INCREMENT;
+--
+-- AUTO_INCREMENT pour la table `wp_wysija_user`
+--
+ALTER TABLE `wp_wysija_user`
+ MODIFY `user_id` int(10) unsigned NOT NULL AUTO_INCREMENT;
+--
+-- AUTO_INCREMENT pour la table `wp_wysija_user_field`
+--
+ALTER TABLE `wp_wysija_user_field`
+ MODIFY `field_id` int(10) unsigned NOT NULL AUTO_INCREMENT;
+--
+-- AUTO_INCREMENT pour la table `wp_wysija_user_history`
+--
+ALTER TABLE `wp_wysija_user_history`
+ MODIFY `history_id` int(10) unsigned NOT NULL AUTO_INCREMENT;
\ No newline at end of file
diff --git a/tests/_data/dropMP2Tables.sql b/tests/_data/dropMP2Tables.sql
new file mode 100644
index 0000000000..8ca6cb6239
--- /dev/null
+++ b/tests/_data/dropMP2Tables.sql
@@ -0,0 +1,15 @@
+DROP TABLE IF EXISTS `wp_wysija_campaign`;
+DROP TABLE IF EXISTS `wp_wysija_campaign_list`;
+DROP TABLE IF EXISTS `wp_wysija_custom_field`;
+DROP TABLE IF EXISTS `wp_wysija_email`;
+DROP TABLE IF EXISTS `wp_wysija_email_user_stat`;
+DROP TABLE IF EXISTS `wp_wysija_email_user_url`;
+DROP TABLE IF EXISTS `wp_wysija_form`;
+DROP TABLE IF EXISTS `wp_wysija_list`;
+DROP TABLE IF EXISTS `wp_wysija_queue`;
+DROP TABLE IF EXISTS `wp_wysija_url`;
+DROP TABLE IF EXISTS `wp_wysija_url_mail`;
+DROP TABLE IF EXISTS `wp_wysija_user`;
+DROP TABLE IF EXISTS `wp_wysija_user_field`;
+DROP TABLE IF EXISTS `wp_wysija_user_history`;
+DROP TABLE IF EXISTS `wp_wysija_user_list`;
diff --git a/tests/_data/populateMP2Tables.sql b/tests/_data/populateMP2Tables.sql
new file mode 100644
index 0000000000..fbb24e4047
--- /dev/null
+++ b/tests/_data/populateMP2Tables.sql
@@ -0,0 +1,170 @@
+-- phpMyAdmin SQL Dump
+-- version 4.4.10
+-- http://www.phpmyadmin.net
+--
+-- Client : localhost:3306
+-- Généré le : Mer 26 Avril 2017 à 17:44
+-- Version du serveur : 5.5.42
+-- Version de PHP : 7.0.0
+
+SET NAMES utf8;
+SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
+SET time_zone = "+00:00";
+
+--
+-- Base de données : `mailpoet`
+--
+
+--
+-- Vider la table avant d'insérer `wp_wysija_campaign`
+--
+
+TRUNCATE TABLE `wp_wysija_campaign`;
+--
+-- Contenu de la table `wp_wysija_campaign`
+--
+
+INSERT INTO `wp_wysija_campaign` (`campaign_id`, `name`, `description`) VALUES
+(1, 'Guide d''utilisation', '');
+
+--
+-- Vider la table avant d'insérer `wp_wysija_campaign_list`
+--
+
+TRUNCATE TABLE `wp_wysija_campaign_list`;
+--
+-- Vider la table avant d'insérer `wp_wysija_custom_field`
+--
+
+TRUNCATE TABLE `wp_wysija_custom_field`;
+--
+-- Contenu de la table `wp_wysija_custom_field`
+--
+
+INSERT INTO `wp_wysija_custom_field` (`id`, `name`, `type`, `required`, `settings`) VALUES
+(1, 'Pays', 'input', 0, 'a:2:{s:8:"required";s:1:"0";s:8:"validate";s:12:"onlyLetterSp";}'),
+(3, 'Commentaires', 'textarea', 0, 'a:2:{s:8:"required";s:1:"0";s:8:"validate";s:0:"";}'),
+(4, 'Pour ou contre', 'radio', 1, 'a:2:{s:6:"values";a:2:{i:0;a:2:{s:5:"value";s:4:"Pour";s:10:"is_checked";i:0;}i:1;a:2:{s:5:"value";s:6:"Contre";s:10:"is_checked";i:0;}}s:8:"required";s:1:"1";}'),
+(5, 'Réglement', 'checkbox', 1, 'a:2:{s:6:"values";a:1:{i:0;a:2:{s:5:"value";s:23:"J''accepte le réglement";s:10:"is_checked";i:1;}}s:8:"required";s:1:"1";}'),
+(6, 'Langue', 'select', 0, 'a:2:{s:6:"values";a:2:{i:0;a:2:{s:5:"value";s:9:"Français";s:10:"is_checked";i:0;}i:1;a:2:{s:5:"value";s:7:"Anglais";s:10:"is_checked";i:0;}}s:8:"required";s:1:"0";}'),
+(7, 'Date d''activation', 'date', 0, 'a:3:{s:9:"date_type";s:14:"year_month_day";s:16:"is_default_today";s:1:"0";s:10:"date_order";s:10:"mm/dd/yyyy";}'),
+(8, 'Chiffres', 'input', 0, 'a:2:{s:8:"required";s:1:"0";s:8:"validate";s:12:"onlyNumberSp";}'),
+(9, 'Lettres', 'input', 0, 'a:2:{s:8:"required";s:1:"0";s:8:"validate";s:12:"onlyLetterSp";}'),
+(10, 'Alphanumerique', 'input', 0, 'a:2:{s:8:"required";s:1:"0";s:8:"validate";s:16:"onlyLetterNumber";}'),
+(11, 'Téléphone', 'input', 0, 'a:2:{s:8:"required";s:1:"0";s:8:"validate";s:5:"phone";}');
+
+--
+-- Vider la table avant d'insérer `wp_wysija_email`
+--
+
+TRUNCATE TABLE `wp_wysija_email`;
+--
+-- Contenu de la table `wp_wysija_email`
+--
+
+INSERT INTO `wp_wysija_email` (`email_id`, `campaign_id`, `subject`, `body`, `created_at`, `modified_at`, `sent_at`, `from_email`, `from_name`, `replyto_email`, `replyto_name`, `attachments`, `status`, `type`, `number_sent`, `number_opened`, `number_clicked`, `number_unsub`, `number_bounce`, `number_forward`, `params`, `wj_data`, `wj_styles`) VALUES
+(1, 1, 'Guide d''utilisation', '\n\n
\n
| \n
<%= __('This new version is quite an upgrade.') %> <%= __('Since this new version is completely new, we first need to update your database before we begin.') %>
+ +