- Updates Migrator with new column for Segments

- Updates Segmnets tests
- Updates MailPoet's Notice.js with additional options
- Updates Import's router, WP menu bootstrap logic, client- and
  server-side logic
This commit is contained in:
MrCasual
2015-11-01 20:00:23 -05:00
parent ff5353c894
commit ad0adb48e1
12 changed files with 1464 additions and 258 deletions

File diff suppressed because it is too large Load Diff

View File

@ -47,6 +47,8 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
type: 'success',
message: '',
static: false,
hideClose: false,
addCustomClass: false,
scroll: false,
timeout: 2000,
onOpen: null,
@ -60,6 +62,9 @@ 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);
// remove id from clone
this.element.removeAttr('id');
@ -73,7 +78,6 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
}
// listen to remove event
var element = this.element;
jQuery(this.element).on('close', function() {
jQuery(this).fadeOut(200, function() {
// on close callback
@ -148,7 +152,7 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
// if the notice is not static, it has to disappear after a timeout
if(this.options.static === false) {
this.element.delay(this.options.timeout).trigger('close');
} else {
} else if (this.options.hideClose === false) {
this.element.append('<a href="javascript:;" class="mailpoet_notice_close"><span class="dashicons dashicons-dismiss"></span></a>');
this.element.find('.mailpoet_notice_close').on('click', function() {
jQuery(this).trigger('close');
@ -163,6 +167,14 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
hide: function(all) {
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])
.trigger('close');
}
} if (all !== undefined) {
jQuery('.mailpoet_'+noticeClass)
.trigger('close');
} else {
jQuery('.mailpoet_notice.updated:not([id]), .mailpoet_notice.error:not([id])')
.trigger('close');

View File

@ -1,6 +1,6 @@
<?php
namespace MailPoet\Config;
use MailPoet\Import\Import;
use \MailPoet\Import\BootstrapMenu;
use \MailPoet\Models\Segment;
use \MailPoet\Models\Setting;
use \MailPoet\Models\Form;
@ -214,13 +214,14 @@ class Menu {
}
function import() {
$import = new Import();
$data = $import->bootstrapImportMenu();
$import = new BootstrapMenu();
$data = $import->bootstrap();
echo $this->renderer->render('import.html', $data);
}
function formEditor() {
$id = (isset($_GET['id']) ? (int)$_GET['id'] : 0);
$form = Form::findOne($id);

View File

@ -0,0 +1,106 @@
<?php namespace MailPoet\Import;
use MailPoet\Models\CustomField;
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")
);
}
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,
'custom' => false
);
}, array_keys($this->subscriberFields), $this->subscriberFields);
}
function formatSubscriberCustomFields() {
return array_map(function ($field) {
return array(
'id' => $field['id'],
'name' => $field['name'],
'label' => $field['name'],
'type' => $field['type'],
'custom' => true
);
}, $this->subscriberCustomFields);
}
function formatSubscriberFieldsSelect2() {
$select2Fields = array(
array(
'name' => __("Actions"),
'children' => array(
array(
'id' => 'ignore',
'name' => __("Ignore column..."),
),
array(
'id' => 'create',
'name' => __("Create new column...")
),
)
),
array(
'name' => __("System columns"),
'children' => $this->formatSubscriberFields()
)
);
if($this->subscriberCustomFields) {
array_push($select2Fields, array(
'name' => __("User columns"),
'children' => $this->formatSubscriberCustomFields()
));
}
return $select2Fields;
}
function bootstrap() {
$data['segments'] = array_map(function ($segment) {
return array(
'id' => $segment['id'],
'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();
return $data;
}
}

View File

@ -1,106 +1,71 @@
<?php namespace MailPoet\Import;
use MailPoet\Models\CustomField;
use MailPoet\Models\Segment;
use MailPoet\Util\Helpers;
class Import {
function getSegments() {
return Segment::findArray();
public function __construct($data) {
$this->subscribersData = $data['subscribers'];
$this->segments = $data['segments'];
$this->updateSubscribers = $data['updateSubscribers'];
$this->subscriberFields = $this->getSubscriberFields();
$this->subscriberCustomFields = $this->getCustomSubscriberFields();
$this->currentTime = time();
$this->profilerStart = microtime(true);
}
function getSubscriberCustomFields() {
return CustomField::findArray();
function process() {
// :)
return array(
'status' => 'success',
'count' => count($this->subscribersData['subscriber_email'])
);
if(in_array('subscriber_status', $subscriberFields)) {
$this->subscribersData['subscriber_state'] = $this->filterSubscriberState(
$this->subscribersData['subscriber_state']
);
}
}
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")
);
}
function formatSubscriberFields($subscriberFields) {
return array_map(function ($fieldId, $fieldName) {
return array(
'id' => $fieldId,
'name' => $fieldName,
'type' => ($fieldId === 'subscriber_confirmed_at') ? 'date' : null,
'custom' => false
);
}, array_keys($subscriberFields), $subscriberFields);
}
function formatSubscriberCustomFields($subscriberCustomFields) {
return array_map(function ($field) {
return array(
'id' => $field['id'],
'name' => $field['name'],
'label' => $field['name'],
'type' => $field['type'],
'custom' => true
);
}, $subscriberCustomFields);
if(!is_int($field)) return $field;
}, array_keys($this->subscribersData));
}
function formatSelect2Fields($subscriberFields, $subscriberCustomFields) {
$data = array(
array(
'name' => __("Actions"),
'children' => array(
array(
'id' => 'ignore',
'name' => __("Ignore column..."),
),
array(
'id' => 'create',
'name' => __("Create new column...")
),
)
function getCustomSubscriberFields() {
return array_map(function ($field) {
if(is_int($field)) return $field;
}, array_keys($this->subscribersData));
}
function filterSubscriberState($data) {
$states = array(
'subscribed' => array(
'subscribed',
'confirmed',
1,
'1',
'true'
),
array(
'name' => __("System columns"),
'children' => $subscriberFields
'unsubscribed' => array(
'unsubscribed',
-1,
'-1',
'false'
)
);
if($subscriberCustomFields) {
array_push($data, array(
'name' => __("User columns"),
'children' => $subscriberCustomFields
));
}
return $data;
return array_map(function ($state) use ($states) {
if(in_array(strtolower($state), $states['subscribed'])) {
return 1;
}
if(in_array(strtolower($state), $states['unsubscribed'])) {
return -1;
}
return 1; // make "subscribed" a default state
}, $data);
}
function bootstrapImportMenu() {
$data['segments'] = array_map(function ($segment) {
return array(
'id' => $segment['id'],
'name' => $segment['name'],
'text' => $segment['name']
);
}, $this->getSegments());
$data['subscriberFields'] = $this->formatSubscriberFields(
$this->getSubscriberFields()
);
$data['subscriberCustomFields'] = $this->formatSubscriberCustomFields(
$this->getSubscriberCustomFields()
);
$data['select2Fields'] = $this->formatSelect2Fields(
$data['subscriberFields'],
$data['subscriberCustomFields']
);
$data['maximumParseSize'] = Helpers::get_maximum_post_size();
return array_map('json_encode', $data);
function timeExecution() {
$profilerEnd = microtime(true);
return ($profilerEnd - $this->profilerStart) / 60;
}
}

155
lib/Import/MailChimp.php Normal file
View File

@ -0,0 +1,155 @@
<?php
namespace MailPoet\Import;
use MailPoet\Util\Helpers;
class MailChimp {
public function __construct($APIKey, $lists = false) {
$this->APIKey = $this->getAPIKey($APIKey);
$this->maxPostSize = Helpers::getMaxPostSize('bytes');
$this->dataCenter = $this->getDataCenter();
$this->lists = $lists;
$this->listsURL = 'https://%s.api.mailchimp.com/2.0/lists/list?apikey=%s';
$this->exportURL = 'https://%s.api.mailchimp.com/export/1.0/list/?apikey=%s&id=%s';
}
public function getLists() {
if(!$this->APIKey || !$this->dataCenter) {
return $this->processError('API');
}
$connection = @fopen(sprintf($this->listsURL, $this->dataCenter, $this->APIKey), 'r');
if(!$connection) {
return $this->processError('connection');
} else {
$response = '';
while (!feof($connection)) {
$buffer = fgets($connection, 4096);
if(trim($buffer) !== '') {
$response .= $buffer;
}
}
fclose($connection);
}
$response = json_decode($response);
if(!$response) {
return $this->processError('API');
}
foreach ($response->data as $list) {
$lists[] = array(
'id' => $list->id,
'name' => $list->name
);
}
return array(
'status' => 'success',
'data' => $lists
);
}
public function getSubscribers() {
if(!$this->APIKey || !$this->dataCenter) {
return $this->processError('API');
}
if(!is_array($this->lists)) {
return $this->processError('lists');
}
$bytesFetched = 0;
foreach ($this->lists as $list) {
$url = sprintf($this->exportURL, $this->dataCenter, $this->APIKey, $list);
$connection = @fopen($url, 'r');
if(!$connection) {
return $this->processError('connection');
} else {
$i = 0;
$header = array();
while (!feof($connection)) {
$buffer = fgets($connection, 4096);
if(trim($buffer) !== '') {
$obj = json_decode($buffer);
if($i === 0) {
$header = $obj;
if(is_object($header) && isset($header->error)) {
return $this->processError('API');
}
if(!isset($headerHash)) {
$headerHash = md5(implode(',', $header));
} else {
if(md5(implode(',', $header) !== $headerHash)) {
return $this->processError('headers');
}
}
} else {
$subscribers[] = $obj;
}
$i++;
}
$bytesFetched += strlen($buffer);
if($bytesFetched > $this->maxPostSize) {
return $this->processError('size');
}
}
fclose($connection);
}
}
if(!count($subscribers)) {
return $this->processError('subscribers');
}
return array(
'status' => 'success',
'data' => $subscribers,
'invalid' => false,
'duplicate' => false,
'header' => $header,
'count' => count($subscribers)
);
}
private function getDataCenter() {
// double parantheses: http://phpsadness.com/sad/51
return ($this->APIKey) ? end((explode('-', $this->APIKey))) : false;
}
private function getAPIKey($APIKey) {
return (preg_match('/[a-zA-Z0-9]{32}-[a-zA-Z0-9]{3,}/', $APIKey)) ? $APIKey : false;
}
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;
}
return array(
'status' => 'error',
'message' => $message
);
}
}

View File

@ -2,6 +2,8 @@
namespace MailPoet\Router;
use MailPoet\Import\MailChimp;
use MailPoet\Models\CustomField;
use MailPoet\Models\Segment;
if(!defined('ABSPATH')) exit;
@ -15,4 +17,40 @@ class Import {
$mailChimp = new MailChimp($data['api_key'], $data['lists']);
wp_send_json($mailChimp->getSubscribers());
}
function addSegment($data) {
$segment = Segment::createOrUpdate($data, $returnObject = true);
wp_send_json(
(!is_array($segment)) ?
array(
'status' => 'error',
'message' => $segment
) :
array(
'status' => 'success',
'segment' => $segment
)
);
}
function addCustomField($data) {
$customField = CustomField::create();
$customField->hydrate($data);
$result = $customField->save();
wp_send_json(
(!$result) ?
array(
'status' => 'error'
) :
array(
'status' => 'success',
'customField' => $customField->asArray()
)
);
}
function process($data) {
$import = new \MailPoet\Import\Import(json_decode($data, true));
wp_send_json($import->process());
}
}

View File

@ -73,29 +73,23 @@ class Helpers {
return $jqueryui_format;
}
/*
* Determine maximum post size in bytes
*/
static function get_maximum_post_size() {
$maximum_post_size = ini_get('post_max_size');
$maximum_post_size_bytes = (int) $maximum_post_size;
$unit = strtolower($maximum_post_size[strlen($maximum_post_size) - 1]);
static function getMaxPostSize($bytes = false) {
$maxPostSize = ini_get('post_max_size');
if (!$bytes) return $maxPostSize;
$maxPostSizeBytes = (int) $maxPostSize;
$unit = strtolower($maxPostSize[strlen($maxPostSize) - 1]);
switch ($unit) {
case 'g':
$maximum_post_size_bytes *= 1024;
case 'm':
$maximum_post_size_bytes *= 1024;
case 'k':
$maximum_post_size_bytes *= 1024;
case 'g':
$maxPostSizeBytes *= 1024;
case 'm':
$maxPostSizeBytes *= 1024;
case 'k':
$maxPostSizeBytes *= 1024;
}
return $maximum_post_size_bytes;
return $maxPostSizeBytes;
}
/*
* Flatten multidimensional array
*/
static function flatten_array($array) {
static function flattenArray($array) {
return call_user_func_array(
'array_merge_recursive', array_map('array_values', $array)
);

View File

@ -11,6 +11,7 @@ class SegmentCest {
$this->before_time = time();
$this->data = array(
'name' => 'some name',
'description' => 'some description'
);
$this->segment = Segment::create();
@ -22,6 +23,20 @@ class SegmentCest {
expect($this->saved)->equals(true);
}
function itCanHaveName() {
expect($this->segment->name)->equals($this->data['name']);
}
function nameMustBeUnique() {
$segment = Segment::create();
$segment->hydrate($this->data);
expect($segment->save())->contains('Duplicate');
}
function itCanHaveDescription() {
expect($this->segment->description)->equals($this->data['description']);
}
function itHasToBeValid() {
expect($this->saved)->equals(true);
$empty_model = Segment::create();

View File

@ -5,8 +5,8 @@
<h2 class="title"><%= __('Import') %></h2>
<!-- STEP 1: method selection -->
<% include 'import/step1.html' %>
<!-- STEP 2: subscriber manipulation -->
<!-- STEP 2: subscriber data manipulation -->
<% include 'import/step2.html' %>
<!-- STEP 3: results -->
</div>
@ -15,17 +15,53 @@
<%= localize({
'noMailChimpLists': __('No active lists found.'),
'serverError': __('Server error:'),
'select': __('Select')
'select': __('Select'),
'maxPostSizeNotice': __('Your CSV is over %s, and too big to process. Please split the file in two, or more.')|replace({'%s': maxPostSize}),
'dataProcessingError': __("Your data couldn't be processed. Please make sure it is in the proper format."),
'noValidRecords': __('No valid records were found.'),
'importNoticeSkipped': __('%1$s records were skipped due to problems.'),
'importNoticeInvalid': __('%1$s emails are not valid : %2$s.'),
'importNoticeDuplicate': __('%1$s emails appear more than once in your file : %2$s.'),
'hideDetails': __('Hide details.'),
'showDetails': __('Show more details.'),
'listSelectionRequired': __('You need to select at least one list.'),
'addNewList': __('Add new list'),
'addNewColumuserColumnsn': __('Add new list'),
'userColumns': __('User columns'),
'selectedValueAlreadyMatched': __('The selected value is already matched to another column.'),
'confirmCorrespondingColumn': __('Can you confirm that this column is corresponding to that field?'),
'columnContainInvalidElement': __('One of the columns contains an invalid email. Please fix before continuing.'),
'january': __('January'),
'february': __('February'),
'march': __('March'),
'april': __('April'),
'may': __('May'),
'june': __('June'),
'july': __('July'),
'august': __('August'),
'september': __('September'),
'october': __('October'),
'november': __('November'),
'december': __('December'),
'noDateFieldMatch': __("Do not match as a 'date field' if most of the rows for that column return the same error."),
'emptyDate': __('Date cannot be empty'),
'verifyDateMatch': __('Verify that the date in blue matches the original one'),
'pm': __('pm'),
'am': __('am'),
'dateMatchError': __('Error matching date.'),
'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.')
}) %>
<script type="text/javascript">
var
maximum_parse_size = <%= maximumParseSize %>,
maximum_parse_notice = "<%= __('Your CSV is over %s, and too big to process. Please split the file in two, or more.')|replace({'%s': maximumParseSize}) %>",
data_container = {},
mailpoet_columns_select2 = <%= select2Fields|raw %>,
maxPostSize = '<%= maxPostSize %>',
importData = {},
mailpoet_columns_select2 = <%= subscriberFieldsSelect2|raw %>,
mailpoet_columns = <%= subscriberFields|raw %>,
mailpoet_lists = <%= segments|raw %>,
email_regex = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-_]{0,61}[a-zA-Z0-9])+.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
mailpoetLists = <%= segments|raw %>,
emailRegex = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-_]{0,61}[a-zA-Z0-9])+.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
</script>
<% endblock %>

View File

@ -1,4 +1,4 @@
<div id="step_1" class="mailpoet_hidden">
<div id="step1" class="mailpoet_hidden">
<div class="inside">
<!-- Method selection -->
<table class="mailpoet_subscribers form-table">
@ -76,7 +76,7 @@
<td>
<input type="file" id="file_local">
&nbsp;
<%= __( 'total max upload file size : %s' )|replace({'%s': maximumParseSize}) %>
<%= __( 'total max upload file size : %s' )|replace({'%s': maxPostSize}) %>
</td>
</tr>
</tbody>
@ -109,7 +109,7 @@
</label>
</th>
<td>
<select class ="mailchimp_lists_select" multiple="multiple"></select>
<select class="mailchimp_lists_select" data-placeholder="<%= __('Select') %>" multiple="multiple"></select>
</td>
</tr>
</tbody>
@ -143,12 +143,11 @@
<tr>
<th scope="row">
<a href="javascript:;"
class="button-primary wysija mailpoet_process"><%= ('Next step') %> </a>
class="button-primary disabled wysija mailpoet_process"><%= ('Next step') %> </a>
</th>
</tr>
</tbody>
</table>
</script>
</div>
</div>

216
views/import/step2.html Normal file
View File

@ -0,0 +1,216 @@
<div id="step2" class="mailpoet_hidden">
<div id="subscribers_data_parse_results">
<!-- Template data -->
</div>
<script id="subscribers_data_parse_results_template" type="text/x-handlebars-template">
<div class="notice">
<ul>
<li>{{{notice}}}</li>
<li><a class="mailpoet_subscribers_data_parse_results_details_show"
href="javascript:;"><%= __('Show more details.') %></a></li>
</ul>
<div class="mailpoet_subscribers_data_parse_results_details mailpoet_hidden">
<hr>
<ul>
{{#if duplicate}}
<li>{{{duplicate}}}</li>
{{/if}}
{{#if invalid}}
<li>{{{invalid}}}</li>
{{/if}}
</ul>
</div>
</div>
</script>
<div class="inside">
<br>
<!-- Subscribers Data -->
<div id="subscribers_data">
<table class="mailpoet_subscribers widefat fixed">
<!-- Template data -->
</table>
</div>
<table class="mailpoet_subscribers form-table">
<tbody>
<!-- MP3 Segments -->
<tr class="mailpoet_segments mailpoet_hidden">
<th scope="row">
<label>
<%= __('Pick one or many segments') %>
<p class="description"><%= __('Pick the segments you want to import those subscribers to.') %>
</label>
</th>
<td>
<select id="mailpoet_segments_select" data-placeholder="<%= __('Select') %>" multiple="multiple"></select>
<a href="javascript:;" class="mailpoet_create_segment"><%= __('Create new list') %></a>
</td>
</tr>
<tr class="mailpoet_no_segments mailpoet_hidden">
<th scope="row">
<%=
__('To add subscribers to a mailing segment, [link]create a list[/link].')
|replace({
'[link]': '<a href="javascript:;" class="mailpoet_create_segment">',
'[/link]': '</a>'
})
|raw
%>
</th>
</tr>
<tr>
<th scope="row">
<label>
<%= __("Update existing subscribers' information") %>
</label>
</th>
<td>
<label>
<input type="radio" name="subscriber_update_option" value="yes"
checked><span><%= __('Yes') %></span>
</label>
<label>
<input type="radio" name="subscriber_update_option"
value="no"><span><%= __('No') %></span>
</label>
</td>
</tr>
<tr>
<th>
<a href="javascript:;" id="step_2_process"
class="button-primary wysija disabled"><%= __('Next step') %> </a>
</th>
</tr>
</tbody>
</table>
<!-- subscribers data template -->
<script id="subscribers_data_template" type="text/x-handlebars-template">
<thead>
<th>
<%= __('Match data') %>
</th>
{{#show_and_match_columns .}}
{{#.}}
<th>
<select class="mailpoet_subscribers_column_data_match" data-column-id="{{column_id}}" id="column_{{@index}}">
</th>
{{/.}}
{{/show_and_match_columns}}
</thead>
<tbody>
{{> subscribers_data_template_partial}}
</tbody>
</script>
<script id="subscribers_data_template_partial" type="text/x-handlebars-template">
{{#if header}}
<tr class="mailpoet_header">
<td></td>
{{#header}}
<td>
{{this}}
</td>
{{/header}}
</tr>
{{/if}}
{{#subscribers}}
<tr>
<td>
{{show_real_index @index}}
</td>
{{#.}}
<td>
{{{this}}}
</td>
{{/.}}
</tr>
{{/subscribers}}
</script>
<!-- New segment template -->
<script id="new_segment_template" type="text/x-handlebars-template">
<p>
<label><%= __('Name') %>:</label>
<input id="new_segment_name" type="text" name="name"/>
</p>
<p class="mailpoet_validation_error" data-error="segment_name_required">
<%= __('You need to specify a name') %>
</p>
<p class="mailpoet_validation_error" data-error="segment_name_not_unique">
<%= __('This name is already taken') %>
</p>
<p>
<label><%= __('Description') %>:</label>
<br/>
<textarea id="new_segment_description" cols="40" rows="3" name="description"/>
</p>
<hr/>
<p class="mailpoet_align_right">
<input type="submit" value="<%= __('Done') %>" id="new_segment_process"
class="button-primary "/>
<input type="submit" value="<%= __('Cancel') %>" id="new_segment_cancel"
class="button-primary"/>
</p>
</form>
</script>
<!-- New column template -->
<script id="new_column_template" type="text/x-handlebars-template">
<p>
<label><%= __('Field type') %>:</label>
<select id="new_column_type" name="type">
<option value="">--</option>
<option value="input">
<%= __('Text Input') %>
</option>
<option value="textarea">
<%= __('Text Area') %>
</option>
<option value="radio">
<%= __('Radio buttons') %>
</option>
<option value="checkbox">
<%= __('Checkbox') %>
</option>
<option value="select">
<%= __('Select') %>
</option>
<option value="date">
<%= __('Date') %>
</option>
</select>
</p>
<p class="mailpoet_validation_error" data-error="type_required">
<%= __('You need to select a type') %>
</p>
<p>
<label><%= __('Field name') %>:</label>
<input id="new_column_name" type="text" name="name" value="{{ name }}"/>
</p>
<p class="mailpoet_validation_error" data-error="name_required">
<%= __('You need to specify a name') %>
</p>
<p class="mailpoet_validation_error" data-error="name_not_unique">
<%= __('This name is already taken') %>
</p>
<hr/>
<p class="mailpoet_align_right">
<input type="submit" value="<%= __('Done') %>" id="new_column_process"
class="button-primary "/>
<input type="submit" value="<%= __('Cancel') %>" id="new_column_cancel"
class="button-primary"/>
</p>
</form>
</script>
</div>
</div>