diff --git a/assets/js/src/import/import.js b/assets/js/src/import/import.js index a428fc3767..6ebe9c32d8 100644 --- a/assets/js/src/import/import.js +++ b/assets/js/src/import/import.js @@ -145,7 +145,7 @@ define( mailChimpListsContainerElement.hide(); jQuery('.mailpoet_mailchimp-key-status').html('').removeClass('mailpoet_mailchimp-ok mailpoet_mailchimp-error'); mailChimpKeyVerifyButtonEelement.prop('disabled', true); - toggleNextStepButton(mailChimpProcessButtonElement, 'on'); + toggleNextStepButton(mailChimpProcessButtonElement, 'off'); } else { mailChimpKeyVerifyButtonEelement.prop('disabled', false); @@ -158,21 +158,21 @@ define( endpoint: 'import', action: 'getMailChimpLists', data: {api_key: mailChimpKeyInputElement.val()} - }).done(function (result) { - if (result.status !== 'success') { + }).done(function (request) { + if (request.result === false) { MailPoet.Notice.hide(); - MailPoet.Notice.error(result.message); + MailPoet.Notice.error(request.message); jQuery('.mailpoet_mailchimp-key-status').removeClass().addClass('mailpoet_mailchimp-key-status mailpoet_mailchimp-error'); mailChimpListsContainerElement.hide(); toggleNextStepButton(mailChimpProcessButtonElement, 'off'); } else { jQuery('.mailpoet_mailchimp-key-status').html('').removeClass().addClass('mailpoet_mailchimp-key-status mailpoet_mailchimp-ok'); - if (!result.data) { + if (!request.data) { jQuery('.mailpoet_mailchimp-key-status').html(MailPoetI18n.noMailChimpLists); mailChimpListsContainerElement.hide(); toggleNextStepButton(mailChimpProcessButtonElement, 'off'); } else { - displayMailChimpLists(result.data); + displayMailChimpLists(request.data); } } MailPoet.Modal.loading(false); @@ -195,17 +195,17 @@ define( api_key: mailChimpKeyInputElement.val(), lists: mailChimpListsContainerElement.find('select').val() } - }).done(function (result) { - if (result.status === 'success') { - importData.step1 = result; + }).done(function (request) { + if (request.result === true) { + importData.step1 = request.data; router.navigate('step2', {trigger: true}); } else { MailPoet.Notice.hide(); - MailPoet.Notice.error(result.message); + MailPoet.Notice.error(request.message); } MailPoet.Modal.loading(false); - }).error(function (error) { + }).error(function () { MailPoet.Modal.loading(false); MailPoet.Notice.error(MailPoetI18n.serverError + result.statusText.toLowerCase() + '.'); }); @@ -376,7 +376,7 @@ define( return; } // define reusable variables - var nextStepButton = jQuery('#step_2_process'), + var nextStepButton = jQuery('#step2_process'), subscribers = jQuery.extend(true, {}, importData.step1), // create a copy of subscribers object for further manipulation subscribersDataTemplate = Handlebars.compile(jQuery('#subscribers_data_template').html()), subscribersDataTemplatePartial = Handlebars.compile(jQuery('#subscribers_data_template_partial').html()), @@ -458,7 +458,7 @@ define( MailPoet.Notice.error(MailPoetI18n.segmentSelectionRequired, { static: true, scroll: true, - addCustomClass: 'segmentSelection', + id: 'segmentSelection', hideClose: true }); } @@ -510,18 +510,18 @@ define( description: segmentDescription } }) - .done(function (result) { - if (result.status === 'success') { + .done(function (request) { + if (request.result === true) { mailpoetLists.push({ - 'id': result.segment.id, - 'name': result.segment.name + 'id': request.segment.id, + 'name': request.segment.name }); var selected_values = segmentSelectElement.val(); if (selected_values === null) { - selected_values = [result.segment.id] + selected_values = [request.segment.id] } else { - selected_values.push(result.segment.id); + selected_values.push(request.segment.id); } enableListSelection(mailpoetLists); @@ -532,7 +532,7 @@ define( } else { MailPoet.Modal.close(); - MailPoet.Notice.error(MailPoetI18n.segmentCreateError + result.message + '.'); + MailPoet.Notice.error(MailPoetI18n.segmentCreateError + request.message + '.'); } }) .error(function (error) { @@ -559,7 +559,7 @@ define( column_id = 'ignore'; // set default column type // if the column is not undefined and has a valid e-mail, set type as email if (column_data % 1 !== 0 && emailRegex.test(column_data)) { - column_id = 'subscriber_email'; + column_id = 's_email'; } else if (subscribers.header) { var header_name = subscribers.header[i], header_name_match = mailpoet_columns.map(function (el) { @@ -571,15 +571,15 @@ define( }// set column type using header name else if (header_name) { if (/first|first name|given name/i.test(header_name)) { - column_id = 'subscriber_firstname'; + column_id = 's_first_name'; } else if (/last|last name/i.test(header_name)) { - column_id = 'subscriber_lastname'; + column_id = 's_last_name'; } else if (/status/i.test(header_name)) { - column_id = 'subscriber_state'; + column_id = 's_status'; } else if (/subscribed|subscription/i.test(header_name)) { - column_id = 'subscriber_confirmed_at'; + column_id = 's_confirmed_at'; } else if (/ip/i.test(header_name)) { - column_id = 'subscriber_confirmed_ip'; + column_id = 's_confirmed_ip'; } } } @@ -681,10 +681,10 @@ define( type: type } }) - .done(function (result) { - if (result.status === 'success') { + .done(function (request) { + if (request.result === true) { var new_column_data = { - 'id': result.customField.id, + 'id': request.customField.id, 'name': name, 'type': type, 'custom': true, @@ -759,7 +759,7 @@ define( // filter subscribers' data to detect dates, emails, etc. function filterSubscribers() { - jQuery('.mailpoet_invalidEmail, .mailpoet_invalidDate').remove(); + jQuery('[data-id="notice_invalidEmail"], [data-id="notice_invalidDate"]').remove(); var subscribersClone = jQuery.extend(true, {}, subscribers), preventNextStep = false, @@ -775,15 +775,15 @@ define( var matchedColumn = jQuery.inArray(column.id, displayedColumnsIds); // EMAIL filter: if the last value in the column doesn't have a valid email, hide the next button - if (column.id === "subscriber_email") { + if (column.id === "s_email") { if (!emailRegex.test(subscribersClone.subscribers[0][matchedColumn])) { preventNextStep = true; - if (!jQuery('.mailpoet_invalidEmail').length) { + if (!jQuery('[data-id="notice_invalidEmail"]').length) { MailPoet.Notice.error(MailPoetI18n.columnContainsInvalidElement, { static: true, scroll: true, hideClose: true, - addCustomClass: 'invalidEmail' + id: 'invalidEmail' }); } } @@ -839,7 +839,7 @@ define( static: true, scroll: true, hideClose: true, - addCustomClass: 'invalidDate' + id: 'invalidDate' }); } } @@ -899,23 +899,62 @@ define( segments: segmentSelectElement.val(), updateSubscribers: (jQuery(':radio[name="subscriber_update_option"]:checked').val() === 'yes') ? true : false }) - }).done(function (result) { - if (result.status !== 'success') { - MailPoet.Notice.error(result.message); + }).done(function (request) { + MailPoet.Modal.loading(false); + if (request.result === false) { + MailPoet.Notice.error(request.error); } else { - alert('Processed ' + result.count) + request.data.lists = []; + importData.step2 = request.data; + router.navigate('step3', {trigger: true}); } }).error(function (error) { MailPoet.Modal.loading(false); MailPoet.Notice.error(MailPoetI18n.serverError + error.statusText.toLowerCase() + '.'); }); - MailPoet.Modal.loading(false); }); filterSubscribers(); enableListSelection(mailpoetLists); }); + + router.on('route:step3', function () { + if (typeof (importData.step2) === 'undefined') { + router.navigate('step2', {trigger: true}); + return; + } + + showCurrentStep(); + + // display statistics + var subscribers_data_import_results_template = Handlebars.compile(jQuery('#subscribers_data_import_results_template').html()), + import_results = { + added: (importData.step2.added) ? MailPoetI18n.subscribersAdded.replace('%1$s', '' + importData.step2.added + '').replace('%2$s', '"' + importData.step2.lists.join('", "') + '"') : false, + updated: (importData.step2.updated) ? MailPoetI18n.subscribersUpdated.replace('%1$s', '' + importData.step2.updated + '').replace('%2$s', '"' + importData.step2.lists.join('", "') + '"') : false, + noaction: (!importData.step2.updated && !importData.step2.added) ? true : false + }, + export_menu_item = jQuery('span.mailpoet_export'); + + jQuery('#subscribers_data_import_results').html(subscribers_data_import_results_template(import_results)).show(); + + jQuery('a.mailpoet_import_again').off().click(function () { + jQuery("#subscribers_data_import_results").hide(); + router.navigate('step1', {trigger: true}); + }); + + jQuery('a.mailpoet_view_subscribers').off().click(function () { + window.location.href = 'admin.php?page=mailpoet-subscribers'; + }); + + // if new subscribers were added and the export menu item is hidden (it's shown only when there are subscribers), display it + if (import_results.added && export_menu_item.not(':visible')) { + export_menu_item.show(); + } + + // reset previous step's data so that coming back to this step is prevented + importData.step2 = undefined; + }); if (!Backbone.History.started) { Backbone.history.start(); diff --git a/assets/js/src/notice.js b/assets/js/src/notice.js index 2eee98f989..a196426088 100644 --- a/assets/js/src/notice.js +++ b/assets/js/src/notice.js @@ -48,7 +48,7 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) { message: '', static: false, hideClose: false, - addCustomClass: false, + id: null, scroll: false, timeout: 2000, onOpen: null, @@ -62,8 +62,8 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) { // clone element this.element = jQuery('#mailpoet_notice_'+this.options.type).clone(); - // add custom identifier class to the element - if (this.options.addCustomClass) this.element.addClass('mailpoet_'+this.options.addCustomClass); + // add data-id to the element + if (this.options.id) this.element.attr('data-id', 'notice_' + this.options.id); // remove id from clone this.element.removeAttr('id'); @@ -168,12 +168,12 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) { if(all !== undefined && all === true) { jQuery('.mailpoet_notice:not([id])').trigger('close'); } else if (all !== undefined && jQuery.isArray(all)) { - for (var noticeClass in all) { - jQuery('.mailpoet_'+all[noticeClass]) + for (var id in all) { + jQuery('[data-id="notice_' + all[id] + '"]') .trigger('close'); } } if (all !== undefined) { - jQuery('.mailpoet_'+noticeClass) + jQuery('[data-id="notice_' + all + '"]') .trigger('close'); } else { jQuery('.mailpoet_notice.updated:not([id]), .mailpoet_notice.error:not([id])') diff --git a/lib/Config/Env.php b/lib/Config/Env.php index b5d968e52b..12c48a3604 100644 --- a/lib/Config/Env.php +++ b/lib/Config/Env.php @@ -17,6 +17,8 @@ class Env { public static $db_prefix; public static $db_source_name; public static $db_host; + public static $db_socket; + public static $db_port; public static $db_name; public static $db_username; public static $db_password; @@ -35,22 +37,33 @@ class Env { self::$lib_path = self::$path . '/lib'; self::$plugin_prefix = 'mailpoet_'; self::$db_prefix = $wpdb->prefix . self::$plugin_prefix; - self::$db_source_name = self::dbSourceName(); self::$db_host = DB_HOST; + self::$db_port = 3306; + self::$db_socket = false; + if (preg_match('/(?=:\d+$)/', DB_HOST)) { + list(self::$db_host, self::$db_port) = explode(':', DB_HOST); + } + else if (preg_match('/:/', DB_HOST)) { + self::$db_socket = true; + } self::$db_name = DB_NAME; self::$db_username = DB_USER; self::$db_password = DB_PASSWORD; self::$db_charset = $wpdb->get_charset_collate(); + self::$db_source_name = self::dbSourceName(self::$db_host, self::$db_socket, self::$db_port); } - private static function dbSourceName() { + private static function dbSourceName($host, $socket,$port) { $source_name = array( - 'mysql:host=', - DB_HOST, + (!$socket) ? 'mysql:host=' : 'mysql:unix_socket=', + $host, + ';', + 'port=', + $port, ';', 'dbname=', DB_NAME ); return implode('', $source_name); } -} +} \ No newline at end of file diff --git a/lib/Config/Migrator.php b/lib/Config/Migrator.php index 5814f304df..81bd028fee 100644 --- a/lib/Config/Migrator.php +++ b/lib/Config/Migrator.php @@ -166,7 +166,8 @@ class Migrator { 'value varchar(255) NOT NULL,', 'created_at TIMESTAMP NOT NULL DEFAULT 0,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', - 'PRIMARY KEY (id)' + 'PRIMARY KEY (id),', + 'UNIQUE KEY subscriber_id_custom_field_id (subscriber_id,custom_field_id)' ); return $this->sqlify(__FUNCTION__, $attributes); } @@ -192,7 +193,8 @@ class Migrator { 'value varchar(255) NOT NULL,', 'created_at TIMESTAMP NOT NULL DEFAULT 0,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', - 'PRIMARY KEY (id)' + 'PRIMARY KEY (id),', + 'UNIQUE KEY newsletter_id_option_field_id (newsletter_id,option_field_id)' ); return $this->sqlify(__FUNCTION__, $attributes); } diff --git a/lib/Import/BootstrapMenu.php b/lib/Import/BootstrapMenu.php index 5356261ed0..71e62d0679 100644 --- a/lib/Import/BootstrapMenu.php +++ b/lib/Import/BootstrapMenu.php @@ -5,43 +5,43 @@ use MailPoet\Models\Segment; use MailPoet\Util\Helpers; class BootstrapMenu { - + function __construct() { $this->subscriberFields = $this->getSubscriberFields(); $this->subscriberCustomFields = $this->getSubscriberCustomFields(); $this->segments = $this->getSegments(); } - + function getSubscriberFields() { return array( - 'subscriber_email' => __("Email"), - 'subscriber_firstname' => __("First name"), - 'subscriber_lastname' => __("Last name"), -/* 'subscriber_confirmed_ip' => __("IP address"), - 'subscriber_confirmed_at' => __("Subscription date"),*/ - 'subscriber_state' => __("Status") + 's_email' => __('Email'), + 's_first_name' => __('First name'), + 's_last_name' => __('Last name'), + /* 's_confirmed_ip' => __('IP address'), + 's_confirmed_at' => __('Subscription date'),*/ + 's_status' => __('Status') ); } - + function getSegments() { return Segment::findArray(); } - + function getSubscriberCustomFields() { return CustomField::findArray(); } - + function formatSubscriberFields() { return array_map(function ($fieldId, $fieldName) { return array( 'id' => $fieldId, 'name' => $fieldName, - 'type' => ($fieldId === 'subscriber_confirmed_at') ? 'date' : null, + 'type' => ($fieldId === 's_confirmed_at') ? 'date' : null, 'custom' => false ); }, array_keys($this->subscriberFields), $this->subscriberFields); } - + function formatSubscriberCustomFields() { return array_map(function ($field) { return array( @@ -53,36 +53,36 @@ class BootstrapMenu { ); }, $this->subscriberCustomFields); } - + function formatSubscriberFieldsSelect2() { $select2Fields = array( array( - 'name' => __("Actions"), + 'name' => __('Actions'), 'children' => array( array( 'id' => 'ignore', - 'name' => __("Ignore column..."), + 'name' => __('Ignore column...'), ), array( 'id' => 'create', - 'name' => __("Create new column...") + 'name' => __('Create new column...') ), ) ), array( - 'name' => __("System columns"), + 'name' => __('System columns'), 'children' => $this->formatSubscriberFields() ) ); if($this->subscriberCustomFields) { array_push($select2Fields, array( - 'name' => __("User columns"), + 'name' => __('User columns'), 'children' => $this->formatSubscriberCustomFields() - )); + )); } return $select2Fields; } - + function bootstrap() { $data['segments'] = array_map(function ($segment) { return array( @@ -90,14 +90,14 @@ class BootstrapMenu { 'name' => $segment['name'], ); }, $this->getSegments()); - + $data['subscriberFields'] = array_merge( $this->formatSubscriberFields(), $this->formatSubscriberCustomFields() ); - + $data['subscriberFieldsSelect2'] = $this->formatSubscriberFieldsSelect2(); - + $data = array_map('json_encode', $data); $data['maxPostSizeBytes'] = Helpers::getMaxPostSize('bytes'); $data['maxPostSize'] = Helpers::getMaxPostSize(); diff --git a/lib/Import/Import.php b/lib/Import/Import.php index 9618699471..bbb9142138 100644 --- a/lib/Import/Import.php +++ b/lib/Import/Import.php @@ -1,5 +1,9 @@ subscribersData = $data['subscribers']; @@ -7,36 +11,136 @@ class Import { $this->updateSubscribers = $data['updateSubscribers']; $this->subscriberFields = $this->getSubscriberFields(); $this->subscriberCustomFields = $this->getCustomSubscriberFields(); - $this->currentTime = time(); + $this->subscribersCount = count(reset($this->subscribersData)); + $this->currentTime = date('Y-m-d H:i:s'); $this->profilerStart = microtime(true); } function process() { - // :) - return array( - 'status' => 'success', - 'count' => count($this->subscribersData['subscriber_email']) + $subscriberFields = $this->subscriberFields; + $subscribersData = $this->subscribersData; + $subscribersData = $this->filterSubscriberState($subscribersData); + list($subscribersData, $subscriberFields) = $this->extendSubscribersAndFields( + $subscribersData, $subscriberFields ); - if(in_array('subscriber_status', $subscriberFields)) { - $this->subscribersData['subscriber_state'] = $this->filterSubscriberState( - $this->subscribersData['subscriber_state'] + list($existingSubscribers, $newSubscribers) = $this->splitSubscribers( + $subscribersData + ); + $addedSubscribers = $updatedSubscribers = array(); + if($newSubscribers) { + $addedSubscribers = $this->addOrUpdateSubscribers( + 'create', + $newSubscribers, + $subscriberFields + ); + + } + if($existingSubscribers && $this->updateSubscribers) { + $updatedSubscribers = $this->addOrUpdateSubscribers( + 'update', + $existingSubscribers, + $subscriberFields + ); + if($addedSubscribers) { + $updatedSubscribers = array_diff_key( + $updatedSubscribers, + $addedSubscribers + ); + } + } + return array( + 'result' => true, + 'data' => array( + 'added' => count($addedSubscribers), + 'updated' => count($updatedSubscribers), + ), + 'profile' => $this->timeExecution() + ); + } + + function splitSubscribers($subscribersData) { + $existingRecords = array_filter( + array_map(function ($subscriberEmails) { + return Subscriber::selectMany(array('email')) + ->whereIn('email', $subscriberEmails) + ->findArray(); + }, array_chunk($subscribersData['s_email'], 200)) + ); + if(!$existingRecords) { + return array( + false, + $subscribersData ); } + $existingRecords = Helpers::flattenArray($existingRecords); + $newRecords = array_keys( + array_diff( + $subscribersData['s_email'], + $existingRecords + ) + ); + if(!$newRecords) { + return array( + $subscribersData, + false + ); + } + $newSubscribers = + array_filter( + array_map(function ($subscriber) use ($newRecords) { + return array_map(function ($index) use ($subscriber) { + return $subscriber[$index]; + }, $newRecords); + }, $subscribersData) + ); + + $existingSubscribers = + array_map(function ($subscriber) use ($newRecords) { + return array_values( // reindex array + array_filter( // remove NULL entries + array_map(function ($index, $data) use ($newRecords) { + if(!in_array($index, $newRecords)) return $data; + }, array_keys($subscriber), $subscriber) + ) + ); + }, $subscribersData); + return array( + $existingSubscribers, + $newSubscribers + ); + } + + function extendSubscribersAndFields($subscribersData, $subscriberFields) { + $subscribersData['created_at'] = $this->filterSubscriberCreatedAtDate(); + $subscriberFields[] = 'created_at'; + return array( + $subscribersData, + $subscriberFields + ); } function getSubscriberFields() { - return array_map(function ($field) { - if(!is_int($field)) return $field; - }, array_keys($this->subscribersData)); + return array_filter( + array_map(function ($field) { + if(!is_int($field)) return $field; + }, array_keys($this->subscribersData)) + ); } function getCustomSubscriberFields() { - return array_map(function ($field) { - if(is_int($field)) return $field; - }, array_keys($this->subscribersData)); + return array_filter( + array_map(function ($field) { + if(is_int($field)) return $field; + }, array_keys($this->subscribersData)) + ); } - function filterSubscriberState($data) { + function filterSubscriberCreatedAtDate() { + return array_fill(0, $this->subscribersCount, $this->currentTime); + } + + function filterSubscriberState($subscribersData) { + if(!in_array('s_status', $this->subscriberFields)) return; $states = array( 'subscribed' => array( 'subscribed', @@ -52,8 +156,7 @@ class Import { 'false' ) ); - - return array_map(function ($state) use ($states) { + $subscribersData['s_status'] = array_map(function ($state) use ($states) { if(in_array(strtolower($state), $states['subscribed'])) { return 1; } @@ -61,7 +164,92 @@ class Import { return -1; } return 1; // make "subscribed" a default state - }, $data); + }, $subscribersData['s_status']); + return $subscribersData; + } + + function addOrUpdateSubscribers($action, $subscribersData, $subscriberFields) { + $subscribersCount = count(reset($subscribersData)) - 1; + $subscribers = array_map(function ($index) use ($subscribersData, $subscriberFields) { + return array_map(function ($field) use ($index, $subscribersData) { + return $subscribersData[$field][$index]; + }, $subscriberFields); + }, range(0, $subscribersCount)); + $subscriberFields = str_replace('s_', '', $subscriberFields); + $currentTime = ($action === 'update') ? date('Y-m-d H:i:s') : $this->currentTime; + foreach (array_chunk($subscribers, 200) as $data) { + try { + if($action == 'create') { + Subscriber::createMultiple( + $subscriberFields, + $data + ); + } + if($action == 'update') { + Subscriber::updateMultiple( + $subscriberFields, + $data, + $currentTime + ); + } + } catch (\PDOException $e) { + throw new \Exception($e->getMessage()); + } + } + $result = Helpers::arrayColumn( // return id=>email array of results + Subscriber::selectMany( + array( + 'id', + 'email' + )) + ->where(($action === 'create') ? 'created_at' : 'updated_at', $currentTime) + ->findArray(), + 'email', 'id' + ); + if($this->subscriberCustomFields) { + $this->addOrUpdateCustomFields( + ($action === 'create') ? 'create' : 'update', + $result, + $subscribersData + ); + } + return $result; + } + + function addOrUpdateCustomFields($action, $dbSubscribers, $subscribersData) { + $subscribers = array_map( + function ($column) use ($dbSubscribers, $subscribersData) { + $count = range(0, count($subscribersData[$column]) - 1); + return array_map( + function ($index, $value) + use ($dbSubscribers, $subscribersData, $column) { + $subscriberId = array_search( + $subscribersData['s_email'][$index], + $dbSubscribers + ); + return array( + $column, + $subscriberId, + $value + ); + }, $count, $subscribersData[$column]); + }, $this->subscriberCustomFields)[0]; + foreach (array_chunk($subscribers, 200) as $data) { + try { + if($action === 'create') { + SubscriberCustomField::createMultiple( + $data + ); + } + if($action === 'update') { + SubscriberCustomField::updateMultiple( + $data + ); + } + } catch (\PDOException $e) { + throw new \Exception($e->getMessage()); + } + } } function timeExecution() { diff --git a/lib/Import/MailChimp.php b/lib/Import/MailChimp.php index e613ac2269..efec244934 100644 --- a/lib/Import/MailChimp.php +++ b/lib/Import/MailChimp.php @@ -47,7 +47,7 @@ class MailChimp { } return array( - 'status' => 'success', + 'result' => true, 'data' => $lists ); } @@ -108,12 +108,14 @@ class MailChimp { } return array( - 'status' => 'success', - 'data' => $subscribers, - 'invalid' => false, - 'duplicate' => false, - 'header' => $header, - 'count' => count($subscribers) + 'result' => true, + 'data' => array( + 'subscribers' => $subscribers, + 'invalid' => false, + 'duplicate' => false, + 'header' => $header, + 'count' => count($subscribers) + ) ); } @@ -128,24 +130,24 @@ class MailChimp { private function processError($error) { switch ($error) { - case 'API': - $message = __('Invalid API key.'); - break; - case 'connection': - $message = __('Could not connect to your MailChimp account.'); - break; - case 'headers': - $message = __('The selected lists do not have matching columns (headers).'); - break; - case 'size': - $message = __('Information received from MailChimp is too large for processing. Please limit the number of lists.'); - break; - case 'subscribers': - $message = __('Did not find any active subscribers.'); - break; - case 'lists': - $message = __('Did not find any valid lists'); - break; + case 'API': + $message = __('Invalid API key.'); + break; + case 'connection': + $message = __('Could not connect to your MailChimp account.'); + break; + case 'headers': + $message = __('The selected lists do not have matching columns (headers).'); + break; + case 'size': + $message = __('Information received from MailChimp is too large for processing. Please limit the number of lists.'); + break; + case 'subscribers': + $message = __('Did not find any active subscribers.'); + break; + case 'lists': + $message = __('Did not find any valid lists'); + break; } return array( 'status' => 'error', diff --git a/lib/Models/CustomField.php b/lib/Models/CustomField.php index d44dc16bcc..e5e7f945ae 100644 --- a/lib/Models/CustomField.php +++ b/lib/Models/CustomField.php @@ -38,12 +38,12 @@ class CustomField extends Model { } function subscribers() { - return $this->has_many_through( + return $this->hasManyThrough( __NAMESPACE__ . '\Subscriber', __NAMESPACE__ . '\SubscriberCustomField', 'custom_field_id', 'subscriber_id' - )->select_expr(MP_SUBSCRIBER_CUSTOM_FIELD_TABLE.'.value'); + )->selectExpr(MP_SUBSCRIBER_CUSTOM_FIELD_TABLE . '.value'); } static function createOrUpdate($data = array()) { diff --git a/lib/Models/Subscriber.php b/lib/Models/Subscriber.php index 3df7c42603..3654a2d69e 100644 --- a/lib/Models/Subscriber.php +++ b/lib/Models/Subscriber.php @@ -1,6 +1,7 @@ whereNull('deleted_at') ->where('status', 'unconfirmed'); } + + static function createMultiple($columns, $values) { + return self::rawExecute( + 'INSERT IGNORE INTO `' . self::$_table . '` ' . + '(' . implode(', ', $columns) . ') ' . + 'VALUES ' . rtrim( + str_repeat( + '(' . rtrim(str_repeat('?,', count($columns)), ',') . ')' . ', ' + , count($values) + ) + , ', '), + Helpers::flattenArray($values) + ); + } + + static function updateMultiple($columns, $subscribers, $currentTime) { + $ignoreColumnsOnUpdate = array( + 'email', + 'created_at' + ); + $emailPosition = array_search('email', $columns); + $sql = function ($type) use ($columns, $subscribers, $emailPosition, $ignoreColumnsOnUpdate) { + return array_filter( + array_map(function ($columnPosition, $columnName) use ($type, $subscribers, $emailPosition, $ignoreColumnsOnUpdate) { + if(in_array($columnName, $ignoreColumnsOnUpdate)) return; + $query = array_map( + function ($subscriber) use ($type, $columnPosition, $emailPosition) { + return ($type === 'values') ? + array( + $subscriber[$emailPosition], + $subscriber[$columnPosition] + ) : + 'WHEN email = ? THEN ?'; + }, $subscribers); + return ($type === 'values') ? + Helpers::flattenArray($query) : + $columnName . '= (CASE ' . implode(' ', $query) . ' END)'; + }, array_keys($columns), $columns) + ); + }; + return self::rawExecute( + 'UPDATE `' . self::$_table . '` ' . + 'SET ' . implode(', ', $sql('statement')) . ', ' . + 'updated_at = "' . $currentTime . '" ' . + 'WHERE email IN (' . rtrim(str_repeat('?,', count($subscribers)), ',') . ')', + array_merge(Helpers::flattenArray($sql('values')), Helpers::arrayColumn($subscribers, $emailPosition)) + ); + } } \ No newline at end of file diff --git a/lib/Models/SubscriberCustomField.php b/lib/Models/SubscriberCustomField.php index e12dddc2bd..2f35395e4a 100644 --- a/lib/Models/SubscriberCustomField.php +++ b/lib/Models/SubscriberCustomField.php @@ -1,6 +1,8 @@ 'error', - 'message' => $segment + 'result' => false, ) : array( - 'status' => 'success', - 'segment' => $segment + 'result' => true, + 'segment' => $segment->asArray() ) ); } @@ -40,17 +39,27 @@ class Import { wp_send_json( (!$result) ? array( - 'status' => 'error' + 'result' => false ) : array( - 'status' => 'success', + 'result' => true, 'customField' => $customField->asArray() ) ); } function process($data) { + $data = file_get_contents(dirname(__FILE__) . '/../../export.txt'); $import = new \MailPoet\Import\Import(json_decode($data, true)); - wp_send_json($import->process()); + try { + wp_send_json($import->process()); + } catch (\Exception $e) { + wp_send_json( + array( + 'result' => false, + 'error' => $e->getMessage() + ) + ); + } } } \ No newline at end of file diff --git a/lib/Util/Helpers.php b/lib/Util/Helpers.php index 130294d34b..19171df716 100644 --- a/lib/Util/Helpers.php +++ b/lib/Util/Helpers.php @@ -75,7 +75,7 @@ class Helpers { static function getMaxPostSize($bytes = false) { $maxPostSize = ini_get('post_max_size'); - if (!$bytes) return $maxPostSize; + if(!$bytes) return $maxPostSize; $maxPostSizeBytes = (int) $maxPostSize; $unit = strtolower($maxPostSize[strlen($maxPostSize) - 1]); switch ($unit) { @@ -90,8 +90,82 @@ class Helpers { } static function flattenArray($array) { - return call_user_func_array( - 'array_merge_recursive', array_map('array_values', $array) - ); + if(!$array) return; + $flattened_array = array(); + array_walk_recursive($array, function ($a) use (&$flattened_array) { $flattened_array[] = $a; }); + return $flattened_array; } -} \ No newline at end of file + + /* + * Using func_get_args() in order to check for proper number ofparameters and trigger errors exactly as the built-in array_column() + * does in PHP 5.5. + * @author Ben Ramsey (http://benramsey.com) + */ + static function arrayColumn($input = null, $columnKey = null, $indexKey = null) { + $argc = func_num_args(); + $params = func_get_args(); + if($argc < 2) { + trigger_error("array_column() expects at least 2 parameters, {$argc} given", E_USER_WARNING); + return null; + } + if(!is_array($params[0])) { + trigger_error( + 'array_column() expects parameter 1 to be array, ' . gettype($params[0]) . ' given', + E_USER_WARNING + ); + return null; + } + if(!is_int($params[1]) + && !is_float($params[1]) + && !is_string($params[1]) + && $params[1] !== null + && !(is_object($params[1]) && method_exists($params[1], '__toString')) + ) { + trigger_error('array_column(): The column key should be either a string or an integer', E_USER_WARNING); + return false; + } + if(isset($params[2]) + && !is_int($params[2]) + && !is_float($params[2]) + && !is_string($params[2]) + && !(is_object($params[2]) && method_exists($params[2], '__toString')) + ) { + trigger_error('array_column(): The index key should be either a string or an integer', E_USER_WARNING); + return false; + } + $paramsInput = $params[0]; + $paramsColumnKey = ($params[1] !== null) ? (string) $params[1] : null; + $paramsIndexKey = null; + if(isset($params[2])) { + if(is_float($params[2]) || is_int($params[2])) { + $paramsIndexKey = (int) $params[2]; + } else { + $paramsIndexKey = (string) $params[2]; + } + } + $resultArray = array(); + foreach ($paramsInput as $row) { + $key = $value = null; + $keySet = $valueSet = false; + if($paramsIndexKey !== null && array_key_exists($paramsIndexKey, $row)) { + $keySet = true; + $key = (string) $row[$paramsIndexKey]; + } + if($paramsColumnKey === null) { + $valueSet = true; + $value = $row; + } elseif(is_array($row) && array_key_exists($paramsColumnKey, $row)) { + $valueSet = true; + $value = $row[$paramsColumnKey]; + } + if($valueSet) { + if($keySet) { + $resultArray[$key] = $value; + } else { + $resultArray[] = $value; + } + } + } + return $resultArray; + } +} \ No newline at end of file diff --git a/views/import.html b/views/import.html index 256ce7a4fa..44da3f0136 100644 --- a/views/import.html +++ b/views/import.html @@ -8,6 +8,7 @@ <% include 'import/step2.html' %> + <% include 'import/step3.html' %> <%= stylesheet('import.css') %> @@ -52,7 +53,9 @@ 'columnContainsInvalidDate': __('One of the columns contains an invalid date. Please fix before continuing.'), 'listCreateError': __('Error adding a new segment:'), 'columnContainsInvalidElement': __('One of the columns contains an invalid email. Please fix before continuing.'), -'customFieldCreateError': __('Custom field could not be created.') +'customFieldCreateError': __('Custom field could not be created.'), +'subscribersAdded': __('%1$s subscribers added to %2$s.'), +'subscribersUpdated': __('%1$s existing subscribers were updated and added to %2$s.') }) %> + \ No newline at end of file