Compare commits

...

45 Commits

Author SHA1 Message Date
5019131b21 Bump up version to 0.0.12 2016-01-22 16:44:09 +02:00
da483fb88f Merge pull request #296 from mailpoet/form_custom_fields
Form custom fields
2016-01-22 14:56:16 +02:00
788bed4622 moved const definition inside render method 2016-01-22 13:45:52 +01:00
3fbe5423d0 added constant for years range + added comment for month quirk 2016-01-22 13:38:43 +01:00
8357295be2 Merge pull request #295 from mailpoet/import_review_fixes
Various fixes based on Rafael's import review comments
2016-01-22 12:28:52 +02:00
8072b162d4 Unit tests for new methods in model subscriber 2016-01-22 11:28:26 +01:00
3f2f0ec1a9 Merge pull request #294 from mailpoet/mailchimp_import_fix
Fixes MailChimp import error
2016-01-22 12:20:44 +02:00
5a5a777b7d Added check for when the custom field doesn't exist
- suppressed cron supervisor error
2016-01-22 11:02:48 +01:00
6cac7f3652 Saving of date custom fields in React & PHP 2016-01-22 10:45:33 +01:00
6a9313107c - Fixes unit test 2016-01-21 12:21:22 -05:00
72c9d301b7 - Fixes issue with file extension warning
- Removes duplicate notices
- Updates Twig's localize function to escape double quotes
2016-01-21 12:08:51 -05:00
ad925de801 Custom fields (in Form & Edit subscriber) 2016-01-21 17:27:34 +01:00
1da28b7299 - Enforces CSV file extension during import file selection
- Updates "no records found" error message
2016-01-20 15:45:56 -05:00
e837ad7014 - Fixes MailChimp import error 2016-01-20 13:54:20 -05:00
daec56191f Save custom fields on subscribe
- added methods to get/set a specific custom field
- added method to get all custom fields (and assign each custom field to the subscriber's instance)
- fixed zIndex of form editor's toolbar (footer was positioned above, preventing click)
2016-01-19 17:02:05 +01:00
7bd25660df Merge pull request #293 from mailpoet/form_editor
Form editor update
2016-01-19 13:22:21 +02:00
3b9821fbe1 Remove matching form block when custom field is deleted 2016-01-18 17:46:42 +01:00
cabe2d61b7 Form editor update
- when a custom field is updated, the matching form field is now also updated
- ability to toggle "is_required" on First name & Last name
- fixed position of validation errors on segment selection, checkbox and radio
- fixed form subscription not working when using custom fields
- fixed sortable in segment selection after list update (add/remove)
- updated position of messages in form subscription
2016-01-18 17:23:10 +01:00
a6d802e2fa Bump up release version information 2016-01-15 18:16:39 +02:00
1732c4f634 Merge pull request #292 from mailpoet/total_subscriber_shortcode
Shortcodes (Archives & Total subscribers) & MailPoet page (create on install)
2016-01-15 17:07:11 +02:00
bb77134224 Unit tests for Settings getValue/setValue
- fixed typo in Shortcodes
- changed for -> foreach
2016-01-15 15:50:23 +01:00
f1cb64b240 Merge pull request #291 from mailpoet/animations
Editor: Animations
2016-01-15 14:15:49 +01:00
3689545589 Default page on install + Setting::setValue() dot notation + cleanup 2016-01-15 13:37:37 +01:00
9b67c56281 Make "Delete" tool animation less janky 2016-01-15 14:06:44 +02:00
dc38b19667 Change transition timings and easings based on feedback 2016-01-15 12:05:43 +02:00
a574733217 archives page 2016-01-14 20:00:15 +01:00
b90aaa629e Merge pull request #290 from mailpoet/subscribe_on_register
Subscribe on register
2016-01-14 20:21:57 +02:00
8de186c0e6 fixed subscribe on registration not using the proper setting 2016-01-14 17:41:04 +01:00
e3719967f9 renaming + casting 2016-01-14 17:13:02 +01:00
138a631ed7 Fix unit tests + enable signup confirmation by default
- minor cleanup
2016-01-14 15:57:46 +01:00
07b7636a72 Update Setting::getValue() to use dot syntax in Hooks
- fixed styling on welcome page (again)
2016-01-14 15:34:13 +01:00
a63ce3cdac Subscribe on registration 2016-01-14 15:30:22 +01:00
f5c7bb87af Merge pull request #288 from mailpoet/settings_round_1
Subscribe in comments
2016-01-14 16:28:52 +02:00
2c8d925971 fixed fatal error 2016-01-14 15:23:22 +01:00
0c5beb2511 Refactor & Improvements
- fixed position of checkbox in comment form
- refactored Subscriber::subscribe() method
- removed Form\Subscribe class in favor of Subscription\Comment (I'll create a similar class for Registration)
- added labels in Settings > Basics for Subscribe in comments & registration
- added method in Setting model to check whether signup confirmation is enabled
2016-01-14 14:11:25 +01:00
9c0316a87d Merge pull request #289 from mailpoet/newsletter_preview
Hook up sending newsletter previews
2016-01-14 12:48:12 +01:00
46c1b682fa Add option to scroll to notices 2016-01-14 13:39:48 +02:00
7954346a3f Fix left padding for .mailpoet_notice on editor pages 2016-01-14 13:31:40 +02:00
d87ff67f50 Remove whitespace after if and catch keywords 2016-01-13 18:50:52 +02:00
6642bb3bfa Verify preview input data, remove "Sender" inputs 2016-01-13 14:28:43 +02:00
2cb32e7a78 Add a method for sending newsletters via new Mailer class 2016-01-13 13:04:21 +02:00
fcea9adbd9 Unit tests + minor bugfix
- added unit tests for new methods in Models\Subscriber
- removed check for "isNew()". It was an unecessary check. Also isNew() always returns false once the new model is saved.
2016-01-13 11:54:23 +01:00
bbdd0dbb6e Subscribe in comments
- added Subscriber::subscribe($user, $segment_ids)
- refactored Router\Subscribers->subscribe() method to account for new method
- added Form\Subscribe class to handle subscription in comments
- updated Basics settings page (changed "list" to "segment")
2016-01-12 18:46:31 +01:00
1b2cf7bd16 Merge pull request #287 from mailpoet/newsletter_save
JSON encode newsletter body when sending it to server
2016-01-12 12:34:38 +01:00
b7cfa549d5 Change newsletter and template saving to JSON encode body separately 2016-01-11 18:27:30 +02:00
64 changed files with 1792 additions and 753 deletions

View File

@ -19,6 +19,8 @@ a:focus
// select 2
.select2-container
width: 25em !important
// textareas
textarea.regular-text
width: 25em !important

View File

@ -125,6 +125,7 @@ handle_icon = '../img/handle.png'
float: none
#mailpoet_form_toolbar
z-index: 999
position: absolute
width: 400px

View File

@ -90,7 +90,7 @@ body.mailpoet_modal_opened
padding: 0
margin: 0
width: 100%
transition: margin 250ms ease-out
transition: margin 350ms ease-out
.mailpoet_panel_wrapper
background-color: #f1f1f1

View File

@ -7,7 +7,6 @@ $tool-active-secondary-color = #ffffff
$tool-width = 20px
$master-column-tool-width = 24px
$layer-selector-width = 30px
.mailpoet_tools
position: absolute
@ -33,10 +32,35 @@ $layer-selector-width = 30px
width: $master-column-tool-width
height: $master-column-tool-width
.mailpoet_delete_block_activate
max-width: 100%
max-height: $master-column-tool-width
opacity: 1
display: block
.mailpoet_delete_block_confirm,
.mailpoet_delete_block_cancel
max-width: 100%
max-height: 0
opacity: 0
overflow: hidden
display: block
.mailpoet_delete_block_activated
width: auto
height: auto
.mailpoet_delete_block_activate
overflow: hidden
max-height: 0
opacity: 0
.mailpoet_delete_block_confirm,
.mailpoet_delete_block_cancel
max-height: $master-column-tool-width*2
opacity: 1
.mailpoet_tool
display: inline-block
width: $tool-width
@ -82,9 +106,10 @@ $layer-selector-width = 30px
padding: 0
.mailpoet_delete_block_activate
max-width: 100%
max-width: $tool-width
display: inline-block
opacity: 1
animation-fade-in-and-scale-horizontally()
.mailpoet_delete_block_confirm,
.mailpoet_delete_block_cancel
@ -95,12 +120,12 @@ $layer-selector-width = 30px
animation-fade-in-and-scale-horizontally()
.mailpoet_delete_block_activated
height: auto
width: auto
border-radius(3px)
background-color: $warning-background-color
padding: 3px 5px
line-height: 1.2em
height: auto
.mailpoet_delete_block_activate
overflow: hidden

View File

@ -129,3 +129,7 @@ body
#mailpoet_modal_close
display: none
.wrap > .mailpoet_notice,
.update-nag
margin-left: 2px + 15px !important

View File

@ -13,8 +13,9 @@ animation-background-color()
animation-fade-in()
animation-name: fadeIn
animation-duration: 250ms
animation-duration: 300ms
animation-fill-mode: forwards
animation-timing-function: ease-in
animation-fade-in-and-scale-horizontally()
transition: all 250ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */

View File

@ -1,31 +1,31 @@
define([
'react',
'react-checkbox-group'
'react'
],
function(
React,
CheckboxGroup
React
) {
var FormFieldCheckbox = React.createClass({
const FormFieldCheckbox = React.createClass({
onValueChange: function(e) {
e.target.value = this.refs.checkbox.checked ? '1' : '';
return this.props.onValueChange(e);
},
render: function() {
var selected_values = this.props.item[this.props.field.name] || '';
if(
selected_values !== undefined
&& selected_values.constructor !== Array
) {
selected_values = selected_values.split(';').map(function(value) {
return value.trim();
});
}
var count = Object.keys(this.props.field.values).length;
const isChecked = !!(this.props.item[this.props.field.name]);
var options = Object.keys(this.props.field.values).map(
const options = Object.keys(this.props.field.values).map(
function(value, index) {
return (
<p key={ 'checkbox-' + index }>
<label>
<input type="checkbox" value={ value } />
&nbsp;{ this.props.field.values[value] }
<input
ref="checkbox"
type="checkbox"
value="1"
checked={ isChecked }
onChange={ this.onValueChange }
name={ this.props.field.name }
/>
{ this.props.field.values[value] }
</label>
</p>
);
@ -33,30 +33,10 @@ function(
);
return (
<CheckboxGroup
name={ this.props.field.name }
value={ selected_values }
ref={ this.props.field.name }
onChange={ this.handleValueChange }>
<div>
{ options }
</CheckboxGroup>
</div>
);
},
handleValueChange: function() {
var field = this.props.field.name;
var group = this.refs[field];
var selected_values = [];
if(group !== undefined) {
selected_values = group.getCheckedValues();
}
return this.props.onValueChange({
target: {
name: field,
value: selected_values.join(';')
}
});
}
});

View File

@ -0,0 +1,196 @@
define([
'react',
'moment',
], function(
React,
Moment
) {
class FormFieldDateYear extends React.Component {
render() {
const yearsRange = 100;
const years = [];
const currentYear = Moment().year();
for (let i = currentYear; i >= currentYear - yearsRange; i--) {
years.push((
<option
key={ i }
value={ i }
>{ i }</option>
));
}
return (
<select
name={ this.props.name + '[year]' }
value={ this.props.year }
onChange={ this.props.onValueChange }
>
{ years }
</select>
);
}
}
class FormFieldDateMonth extends React.Component {
render() {
const months = [];
for (let i = 1; i <= 12; i++) {
months.push((
<option
key={ i }
value={ i }
>{ this.props.monthNames[i - 1] }</option>
));
}
return (
<select
name={ this.props.name + '[month]' }
value={ this.props.month }
onChange={ this.props.onValueChange }
>
{ months }
</select>
);
}
}
class FormFieldDateDay extends React.Component {
render() {
const days = [];
for (let i = 1; i <= 31; i++) {
days.push((
<option
key={ i }
value={ i }
>{ i }</option>
));
}
return (
<select
name={ this.props.name + '[day]' }
value={ this.props.day }
onChange={ this.props.onValueChange }
>
{ days }
</select>
);
}
}
class FormFieldDate extends React.Component {
constructor(props) {
super(props);
this.state = {
year: Moment().year(),
month: 1,
day: 1
}
}
componentDidMount() {
}
componentDidUpdate(prevProps, prevState) {
if (
(this.props.item !== undefined && prevProps.item !== undefined)
&& (this.props.item.id !== prevProps.item.id)
) {
this.extractTimeStamp();
}
}
extractTimeStamp() {
const timeStamp = parseInt(this.props.item[this.props.field.name], 10);
this.setState({
year: Moment.unix(timeStamp).year(),
// Moment returns the month as [0..11]
// We increment it to match PHP's mktime() which expects [1..12]
month: Moment.unix(timeStamp).month() + 1,
day: Moment.unix(timeStamp).date()
});
}
updateTimeStamp(field) {
let newTimeStamp = Moment(
`${this.state.month}/${this.state.day}/${this.state.year}`,
'M/D/YYYY'
).valueOf();
if (!isNaN(newTimeStamp) && parseInt(newTimeStamp, 10) > 0) {
// convert milliseconds to seconds
newTimeStamp /= 1000;
return this.props.onValueChange({
target: {
name: field,
value: newTimeStamp
}
});
}
}
onValueChange(e) {
// extract property from name
const matches = e.target.name.match(/(.*?)\[(.*?)\]/);
let field = null;
let property = null;
if (matches !== null && matches.length === 3) {
field = matches[1];
property = matches[2];
let value = parseInt(e.target.value, 10);
this.setState({
[`${property}`]: value
}, () => {
this.updateTimeStamp(field);
});
}
}
render() {
const monthNames = window.mailpoet_month_names || [];
const dateType = this.props.field.params.date_type;
const dateSelects = dateType.split('_');
const fields = dateSelects.map(type => {
switch(type) {
case 'year':
return (<FormFieldDateYear
onValueChange={ this.onValueChange.bind(this) }
ref={ 'year' }
key={ 'year' }
name={ this.props.field.name }
year={ this.state.year }
/>);
break;
case 'month':
return (<FormFieldDateMonth
onValueChange={ this.onValueChange.bind(this) }
ref={ 'month' }
key={ 'month' }
name={ this.props.field.name }
month={ this.state.month }
monthNames={ monthNames }
/>);
break;
case 'day':
return (<FormFieldDateDay
onValueChange={ this.onValueChange.bind(this) }
ref={ 'day' }
key={ 'day' }
name={ this.props.field.name }
day={ this.state.day }
/>);
break;
}
});
return (
<div>
{fields}
</div>
);
}
};
return FormFieldDate;
});

View File

@ -5,7 +5,8 @@ define([
'form/fields/select.jsx',
'form/fields/radio.jsx',
'form/fields/checkbox.jsx',
'form/fields/selection.jsx'
'form/fields/selection.jsx',
'form/fields/date.jsx',
],
function(
React,
@ -14,7 +15,8 @@ function(
FormFieldSelect,
FormFieldRadio,
FormFieldCheckbox,
FormFieldSelection
FormFieldSelection,
FormFieldDate
) {
var FormField = React.createClass({
renderField: function(data, inline = false) {
@ -55,6 +57,10 @@ function(
case 'selection':
field = (<FormFieldSelection {...data} />);
break;
case 'date':
field = (<FormFieldDate {...data} />);
break;
}
if(inline === true) {
@ -66,10 +72,10 @@ function(
);
} else {
return (
<p key={ 'field-' + (data.index || 0) }>
<div key={ 'field-' + (data.index || 0) }>
{ field }
{ description }
</p>
</div>
);
}
},

View File

@ -7,7 +7,6 @@ function(
var FormFieldRadio = React.createClass({
render: function() {
var selected_value = this.props.item[this.props.field.name];
var count = Object.keys(this.props.field.values).length;
var options = Object.keys(this.props.field.values).map(
function(value, index) {
@ -20,7 +19,7 @@ function(
value={ value }
onChange={ this.props.onValueChange }
name={ this.props.field.name } />
&nbsp;{ this.props.field.values[value] }
{ this.props.field.values[value] }
</label>
</p>
);

View File

@ -617,6 +617,28 @@ var WysijaForm = {
// this is a url, so do not encode the protocol
return encodeURI(str).replace(/[!'()*]/g, escape);
}
},
updateBlock: function(field) {
var hasUpdated = false;
WysijaForm.getBlocks().each(function(b) {
if(b.block.getData().id === field.id) {
hasUpdated = true;
b.block.redraw(field);
}
});
return hasUpdated;
},
removeBlock: function(field, callback) {
var hasRemoved = false;
WysijaForm.getBlocks().each(function(b) {
if(b.block.getData().id === field.id) {
hasRemoved = true;
b.block.removeBlock(callback);
}
});
return hasRemoved;
}
};
@ -825,10 +847,6 @@ WysijaForm.Block = Class.create({
Effect.Fade(this.element.identify(), {
duration: 0.2,
afterFinish: function(effect) {
if(effect.element.next('.mailpoet_form_block') !== undefined && callback !== false) {
// show controls of next block to allow mass delete
WysijaForm.get(effect.element.next('.mailpoet_form_block')).block.showControls();
}
// remove placeholder
if(effect.element.previous('.block_placeholder') !== undefined) {
effect.element.previous('.block_placeholder').remove();

View File

@ -133,10 +133,10 @@ define([
}.bind(this));
},
transitionIn: function() {
return this._transition('slideDown', 'fadeIn', 'easeIn');
return this._transition('slideDown', 'fadeIn', 'easeOut');
},
transitionOut: function() {
return this._transition('slideUp', 'fadeOut', 'easeOut');
return this._transition('slideUp', 'fadeOut', 'easeIn');
},
_transition: function(slideDirection, fadeDirection, easing) {
var promise = jQuery.Deferred();

View File

@ -63,16 +63,17 @@ define([
};
Module.saveNewsletter = function(options) {
return Module._query({
return MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'save',
options: options,
data: options || {},
});
};
Module.previewNewsletter = function(options) {
return MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'preview',
action: 'sendPreview',
data: options || {},
});
};

View File

@ -1,5 +1,6 @@
define([
'newsletter_editor/App',
'newsletter_editor/components/communication',
'mailpoet',
'notice',
'backbone',
@ -8,7 +9,18 @@ define([
'blob',
'filesaver',
'html2canvas'
], function(App, MailPoet, Notice, Backbone, Marionette, jQuery, Blob, FileSaver, html2canvas) {
], function(
App,
CommunicationComponent,
MailPoet,
Notice,
Backbone,
Marionette,
jQuery,
Blob,
FileSaver,
html2canvas
) {
"use strict";
@ -17,26 +29,33 @@ define([
// Save editor contents to server
Module.save = function() {
App.getChannel().trigger('beforeEditorSave');
var json = App.toJSON();
// Stringify to enable transmission of primitive non-string value types
if (!_.isUndefined(json.body)) {
json.body = JSON.stringify(json.body);
}
App.getChannel().trigger('beforeEditorSave', json);
// save newsletter
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'save',
data: json,
}).done(function(response) {
CommunicationComponent.saveNewsletter(json).done(function(response) {
if(response.success !== undefined && response.success === true) {
// TODO: Handle translations
//MailPoet.Notice.success("<?php _e('Newsletter has been saved.'); ?>");
} else if(response.error !== undefined) {
if(response.error.length === 0) {
// TODO: Handle translations
MailPoet.Notice.error("<?php _e('An unknown error occurred, please check your settings.'); ?>");
MailPoet.Notice.error(
"An unknown error occurred, please check your settings.",
{
scroll: true,
}
);
} else {
$(response.error).each(function(i, error) {
MailPoet.Notice.error(error);
MailPoet.Notice.error(error, { scroll: true });
});
}
}
@ -58,7 +77,7 @@ define([
promise.then(function(thumbnail) {
var data = _.extend(options || {}, {
thumbnail: thumbnail.toDataURL('image/jpeg'),
body: App.getBody(),
body: JSON.stringify(App.getBody()),
});
return MailPoet.Ajax.post({
@ -154,6 +173,7 @@ define([
App.getConfig().get('translations.templateNameMissing'),
{
positionAfter: that.$el,
scroll: true,
}
);
} else if (templateDescription === '') {
@ -161,6 +181,7 @@ define([
App.getConfig().get('translations.templateDescriptionMissing'),
{
positionAfter: that.$el,
scroll: true,
}
);
} else {
@ -174,6 +195,7 @@ define([
App.getConfig().get('translations.templateSaved'),
{
positionAfter: that.$el,
scroll: true,
}
);
}).fail(function() {
@ -182,6 +204,7 @@ define([
App.getConfig().get('translations.templateSaveFailed'),
{
positionAfter: that.$el,
scroll: true,
}
);
});
@ -206,6 +229,7 @@ define([
App.getConfig().get('translations.templateNameMissing'),
{
positionAfter: that.$el,
scroll: true,
}
);
} else if (templateDescription === '') {
@ -213,6 +237,7 @@ define([
App.getConfig().get('translations.templateDescriptionMissing'),
{
positionAfter: that.$el,
scroll: true,
}
);
} else {

View File

@ -242,26 +242,29 @@ define([
console.log('trying to send a preview');
// get form data
var data = {
from_name: this.$('#mailpoet_preview_from_name').val(),
from_email: this.$('#mailpoet_preview_from_email').val(),
to_email: this.$('#mailpoet_preview_to_email').val(),
newsletter: App.newsletterId,
subscriber: this.$('#mailpoet_preview_to_email').val(),
id: App.getNewsletter().get('id'),
};
// send test email
MailPoet.Modal.loading(true);
// TODO: Migrate logic to new AJAX format
CommunicationComponent.previewNewsletter(data).done(function(response) {
if(response.success !== undefined && response.success === true) {
MailPoet.Notice.success(App.getConfig().get('translations.testEmailSent'));
} else if(response.error !== undefined) {
if(response.error.length === 0) {
MailPoet.Notice.error(App.getConfig().get('translations.unknownErrorOccurred'));
} else {
$(response.error).each(function(i, error) {
MailPoet.Notice.error(error);
if(response.result !== undefined && response.result === true) {
MailPoet.Notice.success(App.getConfig().get('translations.newsletterPreviewSent'), { scroll: true });
} else {
if (_.isArray(response.errors)) {
response.errors.map(function(error) {
MailPoet.Notice.error(error, { scroll: true });
});
} else {
MailPoet.Notice.error(
App.getConfig().get('translations.newsletterPreviewFailedToSend'),
{
scroll: true,
static: true,
}
);
}
}
MailPoet.Modal.loading(false);

View File

@ -18,6 +18,12 @@ define(
var ImportTemplate = React.createClass({
saveTemplate: function(template) {
// Stringify to enable transmission of primitive non-string value types
if (!_.isUndefined(template.body)) {
template.body = JSON.stringify(template.body);
}
MailPoet.Ajax.post({
endpoint: 'newsletterTemplates',
action: 'save',
@ -111,12 +117,19 @@ define(
}.bind(this));
},
handleSelectTemplate: function(template) {
var body = template.body;
// Stringify to enable transmission of primitive non-string value types
if (!_.isUndefined(body)) {
body = JSON.stringify(body);
}
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'save',
data: {
id: this.props.params.id,
body: template.body
body: body
}
}).done(function(response) {
if(response.result === true) {

View File

@ -20,10 +20,7 @@ function(
$('form.mailpoet_form').each(function() {
var form = $(this);
form.parsley({
errorsWrapper: '<p></p>',
errorTemplate: '<span></span>'
}).on('form:submit', function(parsley) {
form.parsley().on('form:submit', function(parsley) {
var data = form.serializeObject() || {};

View File

@ -51,6 +51,28 @@ define(
}
];
var custom_fields = window.mailpoet_custom_fields || [];
custom_fields.map(custom_field => {
if(custom_field.type === 'input') {
custom_field.type = 'text';
}
let field = {
name: 'cf_' + custom_field.id,
label: custom_field.name,
type: custom_field.type
};
if(custom_field.params) {
field.params = custom_field.params;
}
if(custom_field.params.values) {
field.values = custom_field.params.values;
}
fields.push(field);
});
var messages = {
onUpdate: function() {
MailPoet.Notice.success('Subscriber successfully updated!');

View File

@ -8,12 +8,14 @@ define(
'papaparse',
'select2'
],
function (Backbone,
_,
jQuery,
MailPoet,
Handlebars,
Papa) {
function (
Backbone,
_,
jQuery,
MailPoet,
Handlebars,
Papa
) {
if (!jQuery('#mailpoet_subscribers_import').length) {
return;
}
@ -138,6 +140,14 @@ define(
*/
uploadElement.change(function () {
MailPoet.Notice.hide();
var ext = this.value.match(/\.(.+)$/);
if (ext === null || ext[1].toLowerCase() !== 'csv') {
this.value = '';
MailPoet.Notice.error(MailPoetI18n.wrongFileFormat, {
timeout: 3000,
});
}
toggleNextStepButton(
uploadProcessButtonElement,
(this.value.trim() !== '') ? 'on' : 'off'
@ -416,7 +426,10 @@ define(
}
else {
MailPoet.Modal.loading(false);
MailPoet.Notice.error(MailPoetI18n.noValidRecords, {
var errorNotice = MailPoetI18n.noValidRecords;
errorNotice = errorNotice.replace('[link]', MailPoetI18n.csvKBLink);
errorNotice = errorNotice.replace('[/link]', '</a>');
MailPoet.Notice.error(errorNotice, {
timeout: 3000,
});
}
@ -721,7 +734,7 @@ define(
}
});
// reduce subscribers object if the total length is geater than the
// reduce subscribers object if the total length is greater than the
// maximum number of defined rows
if (subscribers.subscribersCount > (maxRowsToShow + 1)) {
subscribers.subscribers.splice(

608
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,61 @@
<?php
namespace MailPoet\Config;
use \MailPoet\Models\Setting;
class Hooks {
function __construct() {
}
function init() {
// Subscribe in comments
if((bool)Setting::getValue('subscribe.on_comment.enabled')) {
add_action(
'comment_form_after_fields',
'\MailPoet\Subscription\Comment::extendForm'
);
add_action(
'comment_post',
'\MailPoet\Subscription\Comment::onSubmit',
60,
2
);
add_action(
'wp_set_comment_status',
'\MailPoet\Subscription\Comment::onStatusUpdate',
60,
2
);
}
// Subscribe in registration form
if((bool)Setting::getValue('subscribe.on_register.enabled')) {
if(is_multisite()) {
add_action(
'signup_extra_fields',
'\MailPoet\Subscription\Registration::extendForm'
);
add_action(
'wpmu_validate_user_signup',
'\MailPoet\Subscription\Registration::onMultiSiteRegister',
60,
1
);
} else {
add_action(
'register_form',
'\MailPoet\Subscription\Registration::extendForm'
);
add_action(
'register_post',
'\MailPoet\Subscription\Registration::onRegister',
60,
3
);
}
}
// WP Users synchronization
add_action(
'user_register',

View File

@ -5,6 +5,7 @@ use MailPoet\Models;
use MailPoet\Cron\Supervisor;
use MailPoet\Router;
use MailPoet\Models\Setting;
use MailPoet\Settings\Pages;
if(!defined('ABSPATH')) exit;
@ -31,6 +32,7 @@ class Initializer {
$this->runQueueSupervisor();
$this->setupShortcodes();
$this->setupHooks();
$this->setupPages();
$this->setupImages();
}
@ -127,6 +129,11 @@ class Initializer {
$changelog->init();
}
function setupPages() {
$pages = new Pages();
$pages->init();
}
function setupShortcodes() {
$shortcodes = new Shortcodes();
$shortcodes->init();

View File

@ -190,6 +190,8 @@ class Menu {
}
function welcome() {
if((bool)(defined('DOING_AJAX') && DOING_AJAX)) return;
global $wp;
$current_url = home_url(add_query_arg($wp->query_string, $wp->request));
$redirect_url =
@ -240,6 +242,7 @@ class Menu {
function settings() {
$settings = Setting::getAll();
$flags = $this->_getFlags();
// dkim: check if public/private keys have been generated
if(
@ -258,10 +261,9 @@ class Menu {
$data = array(
'settings' => $settings,
'segments' => Segment::getPublished()
->findArray(),
'segments' => Segment::getPublic()->findArray(),
'pages' => Pages::getAll(),
'flags' => $this->_getFlags(),
'flags' => $flags,
'charsets' => Charsets::getAll(),
'current_user' => wp_get_current_user(),
'permissions' => Permissions::getAll(),
@ -294,7 +296,7 @@ class Menu {
} else {
// check if users can register
$flags['registration_enabled'] =
(bool) get_option('users_can_register', false);
(bool)get_option('users_can_register', false);
}
return $flags;
@ -305,6 +307,23 @@ class Menu {
$data['segments'] = Segment::findArray();
$data['custom_fields'] = array_map(function($field) {
$field['params'] = unserialize($field['params']);
if(!empty($field['params']['values'])) {
$values = array();
foreach($field['params']['values'] as $value) {
$values[$value['value']] = $value['value'];
}
$field['params']['values'] = $values;
}
return $field;
}, CustomField::findArray());
$data['date_formats'] = Block\Date::getDateFormats();
$data['month_names'] = Block\Date::getMonthNames();
echo $this->renderer->render('subscribers/subscribers.html', $data);
}

View File

@ -8,6 +8,7 @@ use MailPoet\Config\PopulatorData\Templates\PostNotificationsBlankTemplate;
use \MailPoet\Models\Segment;
use \MailPoet\Segments\WP;
use \MailPoet\Models\Setting;
use \MailPoet\Settings\Pages;
if (!defined('ABSPATH')) exit;
@ -48,6 +49,29 @@ class Populator {
$this->createDefaultSegments();
$this->createDefaultSettings();
$this->createMailPoetPage();
}
private function createMailPoetPage() {
$pages = get_posts(array(
'posts_per_page' => 1,
'orderby' => 'date',
'order' => 'DESC',
'post_type' => 'mailpoet_page'
));
$page = null;
if(!empty($pages)) {
$page = array_shift($pages);
if(strpos($page->post_content, '[mailpoet_page]') === false) {
$page = null;
}
}
if($page === null) {
$mailpoet_page_id = Pages::createMailPoetPage();
Setting::setValue('subscription.page', $mailpoet_page_id);
}
}
private function createDefaultSettings() {
@ -72,6 +96,9 @@ class Populator {
'name' => $user_name,
'address' => $current_user->user_email
));
// enable signup confirmation by default
Setting::setValue('signup_confirmation.enabled', true);
}
private function createDefaultSegments() {

View File

@ -1,5 +1,8 @@
<?php
namespace MailPoet\Config;
use \MailPoet\Models\Newsletter;
use \MailPoet\Models\Subscriber;
use \MailPoet\Models\SubscriberSegment;
class Shortcodes {
function __construct() {
@ -9,6 +12,26 @@ class Shortcodes {
// form widget shortcode
add_shortcode('mailpoet_form', array($this, 'formWidget'));
add_shortcode('wysija_form', array($this, 'formWidget'));
// subscribers count shortcode
add_shortcode('mailpoet_subscribers_count', array(
$this, 'getSubscribersCount'
));
add_shortcode('wysija_subscribers_count', array(
$this, 'getSubscribersCount'
));
// archives page
add_shortcode('mailpoet_archive', array(
$this, 'getArchive'
));
add_filter('mailpoet_archive_date', array(
$this, 'renderArchiveDate'
), 2);
add_filter('mailpoet_archive_subject', array(
$this, 'renderArchiveSubject'
), 2);
}
function formWidget($params = array()) {
@ -23,4 +46,76 @@ class Shortcodes {
));
}
}
function getSubscribersCount($params) {
if(!empty($params['segments'])) {
$segment_ids = array_map(function($segment_id) {
return (int)trim($segment_id);
}, explode(',', $params['segments']));
}
if(empty($segment_ids)) {
return Subscriber::filter('subscribed')->count();
} else {
return SubscriberSegment::whereIn('segment_id', $segment_ids)
->select('subscriber_id')->distinct()
->filter('subscribed')
->findResultSet()->count();
}
}
function getArchive($params) {
if(!empty($params['segments'])) {
$segment_ids = array_map(function($segment_id) {
return (int)trim($segment_id);
}, explode(',', $params['segments']));
}
$newsletters = array();
$html = '';
// TODO: needs more advanced newsletters in order to finish
$newsletters = Newsletter::limit(10)->orderByDesc('created_at')->findMany();
if(empty($newsletters)) {
return apply_filters(
'mailpoet_archive_no_newsletters',
__('Oops! There are no newsletters to display.')
);
} else {
$title = apply_filters('mailpoet_archive_title', '');
if(!empty($title)) {
$html .= '<h3 class="mailpoet_archive_title">'.$title.'</h3>';
}
$html .= '<ul class="mailpoet_archive">';
foreach($newsletters as $newsletter) {
$html .= '<li>'.
'<span class="mailpoet_archive_date">'.
apply_filters('mailpoet_archive_date', $newsletter).
'</span>
<span class="mailpoet_archive_subject">'.
apply_filters('mailpoet_archive_subject', $newsletter).
'</span>
</li>';
}
$html .= '</ul>';
}
return $html;
}
function renderArchiveDate($newsletter) {
return date_i18n(
get_option('date_format'),
strtotime($newsletter->created_at)
);
}
function renderArchiveSubject($newsletter) {
return '<a href="TODO" target="_blank" title="'
.esc_attr(__('Preview in new tab')).'">'
.esc_attr($newsletter->subject).
'</a>';
}
}

View File

@ -1,6 +1,5 @@
<?php
namespace MailPoet\Config;
use \MailPoet\Models\Subscriber;
use \MailPoet\Util\Security;
if(!defined('ABSPATH')) exit;
@ -22,18 +21,6 @@ class Widget {
function registerWidget() {
register_widget('\MailPoet\Form\Widget');
// subscribers count shortcode
add_shortcode('mailpoet_subscribers_count', array(
$this, 'getSubscribersCount'
));
add_shortcode('wysija_subscribers_count', array(
$this, 'getSubscribersCount'
));
}
function getSubscribersCount($params) {
return Subscriber::filter('subscribed')->count();
}
function setupDependencies() {

View File

@ -22,8 +22,9 @@ class Supervisor {
return $this->startDaemon();
}
if(
!$this->force_start &&
in_array($this->daemon['status'], array('stopped', 'stopping'))
!$this->force_start
&& isset($this->daemon['status'])
&& in_array($this->daemon['status'], array('stopped', 'stopping'))
) {
return $this->daemon['status'];
}

View File

@ -13,7 +13,9 @@ abstract class Base {
if($block['id'] === 'segments') {
$rules['required'] = true;
$rules['mincheck'] = 1;
$rules['error-message'] = __('You need to select a list');
$rules['group'] = $block['id'];
$rules['errors-container'] = '.mailpoet_error_'.$block['id'];
$rules['required-message'] = __('You need to select a list');
}
if(!empty($block['params']['required'])) {
@ -29,7 +31,7 @@ abstract class Base {
}
}
if($block['type'] === 'radio') {
if(in_array($block['type'], array('radio', 'checkbox'))) {
$rules['group'] = 'custom_field_'.$block['id'];
$rules['errors-container'] = '.mailpoet_error_'.$block['id'];
$rules['required-message'] = __('You need to select at least one option.');

View File

@ -9,15 +9,16 @@ class Checkbox extends Base {
$field_name = static::getFieldName($block);
$field_validation = static::getInputValidation($block);
// TODO: check if it still makes sense
// create hidden default value
// $html .= '<input type="hidden"name="'.$field_name.'" value="0" '.static::getInputValidation($block).'/>';
$html .= '<p class="mailpoet_paragraph">';
$html .= static::renderLabel($block);
foreach($block['params']['values'] as $option) {
$options = (!empty($block['params']['values'])
? $block['params']['values']
: array()
);
foreach($options as $option) {
$html .= '<label class="mailpoet_checkbox_label">';
$html .= '<input type="checkbox" class="mailpoet_checkbox" ';
@ -35,6 +36,8 @@ class Checkbox extends Base {
$html .= '</label>';
}
$html .= '<span class="mailpoet_error_'.$block['id'].'"></span>';
$html .= '</p>';
return $html;

View File

@ -13,9 +13,12 @@ class Radio extends Base {
$html .= static::renderLabel($block);
$html .= '<span class="mailpoet_error_'.$block['id'].'"></span>';
$options = (!empty($block['params']['values'])
? $block['params']['values']
: array()
);
foreach($block['params']['values'] as $option) {
foreach($options as $option) {
$html .= '<label class="mailpoet_radio_label">';
$html .= '<input type="radio" class="mailpoet_radio" ';
@ -33,6 +36,8 @@ class Radio extends Base {
$html .= '</label>';
}
$html .= '<span class="mailpoet_error_'.$block['id'].'"></span>';
$html .= '</p>';
return $html;

View File

@ -13,23 +13,27 @@ class Segment extends Base {
$html .= static::renderLabel($block);
if(!empty($block['params']['values'])) {
// display values
foreach($block['params']['values'] as $segment) {
if(!isset($segment['id']) || !isset($segment['name'])) continue;
$options = (!empty($block['params']['values'])
? $block['params']['values']
: array()
);
$is_checked = (isset($segment['is_checked']) && $segment['is_checked']) ? 'checked="checked"' : '';
foreach($options as $option) {
if(!isset($option['id']) || !isset($option['name'])) continue;
$html .= '<label class="mailpoet_checkbox_label">';
$html .= '<input type="checkbox" class="mailpoet_checkbox" ';
$html .= 'name="'.$field_name.'[]" ';
$html .= 'value="'.$segment['id'].'" '.$is_checked.' ';
$html .= $field_validation;
$html .= ' />'.$segment['name'];
$html .= '</label>';
}
$is_checked = (isset($option['is_checked']) && $option['is_checked']) ? 'checked="checked"' : '';
$html .= '<label class="mailpoet_checkbox_label">';
$html .= '<input type="checkbox" class="mailpoet_checkbox" ';
$html .= 'name="'.$field_name.'[]" ';
$html .= 'value="'.$option['id'].'" '.$is_checked.' ';
$html .= $field_validation;
$html .= ' />'.$option['name'];
$html .= '</label>';
}
$html .= '<span class="mailpoet_error_'.$block['id'].'"></span>';
$html .= '</p>';
return $html;

View File

@ -6,11 +6,11 @@ class Submit extends Base {
static function render($block) {
$html = '';
$html .= '<input class="mailpoet_submit" type="submit" ';
$html .= '<p class="mailpoet_submit"><input type="submit" ';
$html .= 'value="'.static::getFieldLabel($block).'" ';
$html .= '/>';
$html .= '/></p>';
return $html;
}

View File

@ -194,96 +194,4 @@ class Widget extends \WP_Widget {
}
}
}
}
// set the content filter to replace the shortcode
if(isset($_GET['mailpoet_page']) && strlen(trim($_GET['mailpoet_page'])) > 0) {
switch($_GET['mailpoet_page']) {
case 'mailpoet_form_iframe':
$id = (isset($_GET['mailpoet_form']) && (int)$_GET['mailpoet_form'] > 0) ? (int)$_GET['mailpoet_form'] : null;
$form = Form::findOne($id);
if($form !== false) {
// render form
$output = Util\Export::get('html', $form->asArray());
// $output = do_shortcode($output);
print $output;
exit;
}
break;
default:
// add_filter('wp_title', 'mailpoet_meta_page_title'));
add_filter('the_title', 'mailpoet_page_title', 10, 2);
add_filter('the_content', 'mailpoet_page_content', 98, 1);
break;
}
}
function mailpoet_page_title($title = '', $id = null) {
// get signup confirmation page id
$signup_confirmation = Setting::getValue('signup_confirmation');
$page_id = $signup_confirmation['page'];
// check if we're on the signup confirmation page
if((int)$page_id === (int)$id) {
global $post;
// disable comments
$post->comment_status = 'close';
// disable password
$post->post_password = '';
$subscriber = null;
// get subscriber key from url
$subscriber_digest = (isset($_GET['mailpoet_key']) && strlen(trim($_GET['mailpoet_key'])) === 32) ? trim($_GET['mailpoet_key']) : null;
if($subscriber_digest !== null) {
// get subscriber
// TODO: change select() to selectOne() once it's implemented
$subscribers = $mailpoet->subscribers()->select(array(
'filter' => array(
'subscriber_digest' => $subscriber_digest
),
'limit' => 1
));
if(!empty($subscribers)) {
$subscriber = array_shift($subscribers);
}
}
// check if we have a subscriber record
if($subscriber === null) {
return __('Your confirmation link expired, please subscribe again.');
} else {
// we have a subscriber, let's check its state
switch($subscriber['subscriber_state']) {
case MailPoetSubscribers::STATE_UNCONFIRMED:
case MailPoetSubscribers::STATE_UNSUBSCRIBED:
// set subscriber state as confirmed
$mailpoet->subscribers()->update(array(
'subscriber' => $subscriber['subscriber'],
'subscriber_state' => MailPoetSubscribers::STATE_SUBSCRIBED,
'subscriber_confirmed_at' => time()
));
return __("You've subscribed");
break;
case MailPoetSubscribers::STATE_SUBSCRIBED:
return __("You've already subscribed");
break;
}
}
} else {
return $title;
}
}
function mailpoet_page_content($content = '') {
if(strpos($content, '[mailpoet_page]') !== FALSE) {
$content = str_replace('[mailpoet_page]', '', $content);
}
return $content;
}

View File

@ -16,27 +16,73 @@ class Setting extends Model {
}
public static function getValue($key, $default = null) {
$setting = Setting::where('name', $key)->findOne();
if($setting === false) {
return $default;
} else {
if(is_serialized($setting->value)) {
return unserialize($setting->value);
$keys = explode('.', $key);
if(count($keys) === 1) {
$setting = Setting::where('name', $key)->findOne();
if($setting === false) {
return $default;
} else {
return $setting->value;
if(is_serialized($setting->value)) {
return unserialize($setting->value);
} else {
return $setting->value;
}
}
} else {
$main_key = array_shift($keys);
$setting = static::getValue($main_key, $default);
if($setting !== $default) {
for($i = 0, $count = count($keys); $i < $count; $i++) {
if(array_key_exists($keys[$i], $setting)) {
$setting = $setting[$keys[$i]];
} else {
return $default;
}
}
}
return $setting;
}
}
public static function setValue($key, $value) {
if(is_array($value)) {
$value = serialize($value);
}
$keys = explode('.', $key);
return Setting::createOrUpdate(array(
'name' => $key,
'value' => $value
));
if(count($keys) === 1) {
if(is_array($value)) {
$value = serialize($value);
}
return Setting::createOrUpdate(array(
'name' => $key,
'value' => $value
));
} else {
$main_key = array_shift($keys);
$setting_value = static::getValue($main_key, array());
$current_value = &$setting_value;
$last_key = array_pop($keys);
foreach($keys as $key) {
if(!is_array($current_value)) {
$current_value = array();
}
if(!array_key_exists($key, $current_value)) {
$current_value = array($key => array());
}
$current_value =& $current_value[$key];
}
if(is_scalar($current_value)) {
$current_value = array();
}
$current_value[$last_key] = $value;
return static::setValue($main_key, $setting_value);
}
}
public static function getAll() {

View File

@ -32,6 +32,51 @@ class Subscriber extends Model {
return parent::delete();
}
function addToSegments(array $segment_ids = array()) {
$segments = Segment::whereIn('id', $segment_ids)->findMany();
foreach($segments as $segment) {
$association = SubscriberSegment::create();
$association->subscriber_id = $this->id;
$association->segment_id = $segment->id;
$association->save();
}
}
function sendConfirmationEmail() {
$this->set('status', 'unconfirmed');
// TODO
}
static function subscribe($subscriber_data = array(), $segment_ids = array()) {
if(empty($subscriber_data) or empty($segment_ids)) {
return false;
}
$subscriber = static::createOrUpdate($subscriber_data);
if($subscriber !== false && $subscriber->id() > 0) {
// restore deleted subscriber
if($subscriber->deleted_at !== NULL) {
$subscriber->setExpr('deleted_at', 'NULL');
}
if((bool)Setting::getValue('signup_confirmation.enabled')) {
if($subscriber->status !== 'subscribed') {
$subscriber->sendConfirmationEmail();
}
} else {
$subscriber->set('status', 'subscribed');
}
if($subscriber->save()) {
$subscriber->addToSegments($segment_ids);
}
}
return $subscriber;
}
static function search($orm, $search = '') {
if(strlen(trim($search) === 0)) {
return $orm;
@ -181,21 +226,82 @@ class Subscriber extends Model {
$subscriber = false;
if(isset($data['id']) && (int)$data['id'] > 0) {
$subscriber = self::findOne((int)$data['id']);
$subscriber = static::findOne((int)$data['id']);
unset($data['id']);
}
if($subscriber === false && !empty($data['email'])) {
$subscriber = static::where('email', $data['email'])->findOne();
if($subscriber !== false) {
unset($data['email']);
}
}
// custom fields
$custom_fields = array();
foreach($data as $key => $value) {
if(strpos($key, 'cf_') === 0) {
$custom_fields[(int)substr($key, 3)] = $value;
unset($data[$key]);
}
}
if($subscriber === false) {
$subscriber = self::create();
$subscriber = static::create();
$subscriber->hydrate($data);
} else {
$subscriber->set($data);
}
$subscriber->save();
if($subscriber->save()) {
if(!empty($custom_fields)) {
foreach($custom_fields as $custom_field_id => $value) {
$subscriber->setCustomField($custom_field_id, $value);
}
}
}
return $subscriber;
}
function withCustomFields() {
$custom_fields = CustomField::select('id')->findArray();
if(empty($custom_fields)) return $this;
$custom_field_ids = Helpers::arrayColumn($custom_fields, 'id');
$relations = SubscriberCustomField::select('custom_field_id')
->select('value')
->whereIn('custom_field_id', $custom_field_ids)
->where('subscriber_id', $this->id())
->findMany();
foreach($relations as $relation) {
$this->{'cf_'.$relation->custom_field_id} = $relation->value;
}
return $this;
}
function getCustomField($custom_field_id, $default = null) {
$custom_field = SubscriberCustomField::select('value')
->where('custom_field_id', $custom_field_id)
->where('subscriber_id', $this->id())
->findOne();
if($custom_field === false) {
return $default;
} else {
return $custom_field->value;
}
}
function setCustomField($custom_field_id, $value) {
return SubscriberCustomField::createOrUpdate(array(
'subscriber_id' => $this->id(),
'custom_field_id' => $custom_field_id,
'value' => $value
));
}
static function bulkMoveToList($orm, $data = array()) {
$segment_id = (isset($data['segment_id']) ? (int)$data['segment_id'] : 0);
$segment = Segment::findOne($segment_id);
@ -273,8 +379,7 @@ class Subscriber extends Model {
if(!empty($subscribers)) {
foreach($subscribers as $subscriber) {
// TODO: send confirmation email
// $subscriber->sendConfirmationEmail()
$subscriber->sendConfirmationEmail();
}
return $subscribers->count();

View File

@ -12,6 +12,49 @@ class SubscriberCustomField extends Model {
parent::__construct();
}
static function createOrUpdate($data = array()) {
$custom_field = CustomField::findOne($data['custom_field_id']);
if($custom_field === false) {
return false;
} else {
$custom_field = $custom_field->asArray();
}
if($custom_field['type'] === 'date') {
if(is_array($data['value'])) {
$day = (
isset($data['value']['day'])
? (int)$data['value']['day']
: 1
);
$month = (
isset($data['value']['month'])
? (int)$data['value']['month']
: 1
);
$year = (
isset($data['value']['year'])
? (int)$data['value']['year']
: 1970
);
$data['value'] = mktime(0, 0, 0, $month, $day, $year);
}
}
$relation = self::where('custom_field_id', $data['custom_field_id'])
->where('subscriber_id', $data['subscriber_id'])
->findOne();
if($relation === false) {
$relation = self::create();
$relation->hydrate($data);
} else {
$relation->set($data);
}
return $relation->save();
}
static function createMultiple($values) {
$values = array_map('array_values', $values);
return self::rawExecute(

View File

@ -33,6 +33,10 @@ class SubscriberSegment extends Model {
return $orm;
}
static function subscribed($orm) {
return $orm->where('status', 'subscribed');
}
static function createMultiple($segmnets, $subscribers) {
$values = Helpers::flattenArray(
array_map(function ($segment) use ($subscribers) {

View File

@ -18,15 +18,19 @@ class CustomFields {
}
function delete($id) {
$result = false;
$custom_field = CustomField::findOne($id);
if($custom_field !== false) {
if($custom_field === false or !$custom_field->id()) {
wp_send_json(array(
'result' => false
));
} else {
$custom_field->delete();
$result = true;
}
wp_send_json($result);
wp_send_json(array(
'result' => true,
'field' => $custom_field->asArray()
));
}
}
function save($data = array()) {

View File

@ -30,10 +30,6 @@ class NewsletterTemplates {
}
function save($data = array()) {
if (isset($data['body'])) {
$data['body'] = json_encode($data['body']);
}
$result = NewsletterTemplate::createOrUpdate($data);
if($result !== true) {
wp_send_json($result);

View File

@ -59,10 +59,6 @@ class Newsletters {
unset($data['options']);
}
if (isset($data['body'])) {
$data['body'] = json_encode($data['body']);
}
$errors = array();
$result = false;
@ -221,6 +217,45 @@ class Newsletters {
wp_send_json(array('rendered_body' => $renderer->render()));
}
function sendPreview($data = array()) {
$id = (isset($data['id'])) ? (int) $data['id'] : 0;
$newsletter = Newsletter::findOne($id);
if($newsletter === false) {
wp_send_json(array(
'result' => false
));
}
if(empty($data['subscriber'])) {
wp_send_json(array(
'result' => false,
'errors' => array(__('Please specify receiver information')),
));
}
$newsletter = $newsletter->asArray();
$renderer = new Renderer($newsletter);
$rendered_body = $renderer->render();
$newsletter['body'] = array(
'html' => $rendered_body,
'text' => '',
);
try {
$mailer = new \MailPoet\Mailer\Mailer(false, false, false);
wp_send_json(array(
'result' => $mailer->send($newsletter, $data['subscriber'])
));
} catch(\Exception $e) {
wp_send_json(array(
'result' => false,
'errors' => array($e->getMessage()),
));
}
}
function listing($data = array()) {
$listing = new Listing\Handler(
'\MailPoet\Models\Newsletter',

View File

@ -24,7 +24,7 @@ class Subscribers {
} else {
$segments = $subscriber->segments()->findArray();
$subscriber = $subscriber->asArray();
$subscriber = $subscriber->withCustomFields()->asArray();
$subscriber['segments'] = array_map(function($segment) {
return $segment['id'];
}, $segments);
@ -68,10 +68,10 @@ class Subscribers {
function save($data = array()) {
$errors = array();
$result = false;
$segments = false;
$segment_ids = array();
if(array_key_exists('segments', $data)) {
$segments = $data['segments'];
$segment_ids = (array)$data['segments'];
unset($data['segments']);
}
@ -82,18 +82,8 @@ class Subscribers {
} else {
$result = true;
if($segments !== false) {
SubscriberSegment::where('subscriber_id', $subscriber->id)
->deleteMany();
if(!empty($segments)) {
foreach($segments as $segment_id) {
$relation = SubscriberSegment::create();
$relation->segment_id = $segment_id;
$relation->subscriber_id = $subscriber->id;
$relation->save();
}
}
if(!empty($segment_ids)) {
$subscriber->addToSegments($segment_ids);
}
}
wp_send_json(array(
@ -112,109 +102,37 @@ class Subscribers {
$errors[] = __('This form does not exist.');
}
if(empty($data['segments'])) {
$errors[] = __('You need to select a list');
} else {
$segments = Segment::whereIn('id', (array)$data['segments'])->findMany();
if(empty($segments)) {
$errors[] = __('You need to select a list');
}
}
$segment_ids = (!empty($data['segments'])
? (array)$data['segments']
: array()
);
unset($data['segments']);
$subscriber = false;
if(empty($segment_ids)) {
$errors[] = __('You need to select a list');
}
if(!empty($errors)) {
wp_send_json(array('errors' => $errors));
} else {
if(!empty($data['email'])) {
$subscriber = Subscriber::where('email', $data['email'])->findOne();
}
}
$signup_confirmation = Setting::getValue('signup_confirmation', array());
if($subscriber === false) {
// create new subscriber
$data['status'] = (
(!empty($signup_confirmation['enabled']))
? 'unconfirmed' : 'subscribed'
);
// custom fields
$custom_fields = array();
foreach($data as $key => $value) {
if(strpos($key, 'cf_') === 0) {
$custom_fields[substr($key, 3)] = $value;
unset($data[$key]);
}
}
// insert new subscriber
$subscriber = Subscriber::createOrUpdate($data);
if($subscriber === false || !$subscriber->id()) {
$errors = array_merge($errors, $subscriber->getValidationErrors());
} else {
// add custom fields
if(!empty($custom_fields)) {
foreach($custom_fields as $custom_field_id => $value) {
if(is_array($value)) {
// date
$value = mktime(0, 0, 0, $value['month'], $value['day'], $value['year']);
}
$subscriber_custom_field = SubscriberCustomField::create();
$subscriber_custom_field->hydrate(array(
'subscriber_id' => $subscriber->id(),
'custom_field_id' => $custom_field_id,
'value' => $value
));
$subscriber_custom_field->save();
}
}
}
} else {
$subscriber->set('status', (
!empty($signup_confirmation['enabled'])
? 'unconfirmed' : 'subscribed'
wp_send_json(array(
'result' => false,
'errors' => $errors
));
// restore deleted subscriber
if($subscriber->deleted_at !== NULL) {
$subscriber->setExpr('deleted_at', 'NULL');
}
if(!$subscriber->save()) {
$errors[] = __('An error occurred. Please try again later.');
}
}
// get segments
// IDEA: $subscriptions->addToSegments($data['segments']);
$segments_subscribed = array();
foreach($segments as $segment) {
if($segment->addSubscriber($subscriber->id())) {
$segments_subscribed[] = $segment->id;
}
$subscriber = Subscriber::subscribe($data, $segment_ids);
$result = false;
if($subscriber === false || !$subscriber->id()) {
$errors = array_merge($errors, $subscriber->getValidationErrors());
} else {
$result = true;
}
// if signup confirmation is enabled and the subscriber is unconfirmed
if(!empty($signup_confirmation['enabled'])
&& !empty($segments_subscribed)
&& $subscriber->status !== 'subscribed'
) {
// TODO: send confirmation email
// resend confirmation email
$is_sent = true;
/*$is_sent = static::sendSignupConfirmation(
$subscriber->asArray(),
$segments->asArray()
);*/
// error message if the email could not be sent
if($is_sent === false) {
$errors[] = __('The signup confirmation email could not be sent. Please check your settings.');
}
if(!empty($errors)) {
wp_send_json(array(
'result' => false,
'errors' => $errors
));
}
// get success message to display after subscription
@ -223,15 +141,6 @@ class Subscribers {
? unserialize($form->settings) : null
);
if(!empty($errors)) {
wp_send_json(array(
'result' => false,
'errors' => $errors
));
} else {
$result = true;
}
if($form_settings !== null) {
$message = $form_settings['success_message'];
@ -274,7 +183,7 @@ class Subscribers {
// response depending on context
if($doing_ajax === true) {
wp_send_json(array(
'result' => $result,
'result' => true,
'message' => $message
));
} else {

View File

@ -2,22 +2,71 @@
namespace MailPoet\Settings;
class Pages {
function __construct() {
}
static function getAll() {
$mailpoet_pages = \get_posts(array(
function init() {
register_post_type('mailpoet_page', array(
'labels' => array(
'name' => __('MailPoet Page'),
'singular_name' => __('MailPoet Page')
),
'public' => true,
'has_archive' => false,
'show_ui' => true,
'show_in_menu' => false,
'rewrite' => false,
'show_in_nav_menus'=>false,
'can_export'=>false,
'publicly_queryable'=>true,
'exclude_from_search'=>true
));
}
static function createMailPoetPage() {
remove_all_actions('pre_post_update');
remove_all_actions('save_post');
remove_all_actions('wp_insert_post');
$id = wp_insert_post(array(
'post_status' => 'publish',
'post_type' => 'mailpoet_page',
'post_author' => 1,
'post_content' => '[mailpoet_page]',
'post_title' => __('MailPoet Page'),
'post_name' => 'subscriptions'
));
flush_rewrite_rules();
return ((int)$id > 0) ? (int)$id : false;
}
static function getMailPoetPages() {
return get_posts(array(
'post_type' => 'mailpoet_page'
));
}
static function getAll() {
$all_pages = array_merge(
static::getMailPoetPages(),
get_pages()
);
$pages = array();
foreach(array_merge($mailpoet_pages, \get_pages()) as $page) {
$pages[] = array(
'id' => $page->ID,
'title' => $page->post_title,
'preview_url' => \get_permalink($page->ID),
'edit_url' => \get_edit_post_link($page->ID)
);
foreach($all_pages as $page) {
$pages[] = static::getPageData($page);
}
return $pages;
}
static function getPageData($page) {
return array(
'id' => $page->ID,
'title' => $page->post_title,
'preview_url' => get_permalink($page->ID),
'edit_url' => get_edit_post_link($page->ID)
);
}
}

View File

@ -113,7 +113,7 @@ class MailChimp {
'invalid' => false,
'duplicate' => false,
'header' => $header,
'count' => count($subscribers)
'subscribersCount' => count($subscribers)
)
);
}

View File

@ -0,0 +1,85 @@
<?php
namespace MailPoet\Subscription;
use \MailPoet\Models\Setting;
use \MailPoet\Models\Subscriber;
class Comment {
const SPAM = 'spam';
const APPROVED = 1;
const PENDING_APPROVAL = 0;
static function extendForm() {
$label = Setting::getValue(
'subscribe.on_comment.label',
__('Yes, add me to your mailing list.')
);
print '<p class="comment-form-mailpoet">
<label for="mailpoet_subscribe_on_comment">
<input
type="checkbox"
id="mailpoet_subscribe_on_comment"
value="1"
name="mailpoet[subscribe_on_comment]"
/>&nbsp;'.esc_attr($label).'
</label>
</p>';
}
static function onSubmit($comment_id, $comment_status) {
if($comment_status === Comment::SPAM) return;
if(
isset($_POST['mailpoet']['subscribe_on_comment'])
&& (bool)$_POST['mailpoet']['subscribe_on_comment'] === true
) {
if($comment_status === Comment::PENDING_APPROVAL) {
// add a comment meta to remember to subscribe the user
// once the comment gets approved
add_comment_meta(
$comment_id,
'mailpoet',
'subscribe_on_comment',
true
);
} else if($comment_status === Comment::APPROVED) {
static::subscribeAuthorOfComment($comment_id);
}
}
}
static function onStatusUpdate($comment_id, $action) {
if($action === 'approve') {
// check if the comment's author wants to subscribe
$do_subscribe = (
get_comment_meta(
$comment_id,
'mailpoet',
true
) === 'subscribe_on_comment'
);
if($do_subscribe === true) {
static::subscribeAuthorOfComment($comment_id);
delete_comment_meta($comment_id, 'mailpoet');
}
}
}
private static function subscribeAuthorOfComment($comment_id) {
$segment_ids = Setting::getValue('subscribe.on_comment.segments', array());
if(!empty($segment_ids)) {
$comment = get_comment($comment_id);
$result = Subscriber::subscribe(
array(
'email' => $comment->comment_author_email,
'first_name' => $comment->comment_author
),
$segment_ids
);
}
}
}

View File

@ -0,0 +1,74 @@
<?php
namespace MailPoet\Subscription;
use \MailPoet\Models\Setting;
use \MailPoet\Models\Subscriber;
class Registration {
static function extendForm() {
$label = Setting::getValue(
'subscribe.on_register.label',
__('Yes, add me to your mailing list.')
);
print '<p class="registration-form-mailpoet">
<label for="mailpoet_subscribe_on_register">
<input
type="checkbox"
id="mailpoet_subscribe_on_register"
value="1"
name="mailpoet[subscribe_on_register]"
/>&nbsp;'.esc_attr($label).'
</label>
</p>';
}
static function onMultiSiteRegister($result) {
if(empty($result['errors']->errors)) {
if(
isset($_POST['mailpoet']['subscribe_on_register'])
&& (bool)$_POST['mailpoet']['subscribe_on_register'] === true
) {
static::subscribeNewUser(
$result['user_name'],
$result['user_email']
);
}
}
return $result;
}
static function onRegister(
$user_login,
$user_email = null,
$errors = null
) {
if(
empty($errors->errors)
&& isset($_POST['mailpoet']['subscribe_on_register'])
&& (bool)$_POST['mailpoet']['subscribe_on_register'] === true
) {
static::subscribeNewUser(
$user_login,
$user_email
);
}
}
private static function subscribeNewUser($name, $email) {
$segment_ids = Setting::getValue(
'subscribe.on_register.segments',
array()
);
if(!empty($segment_ids)) {
Subscriber::subscribe(
array(
'email' => $email,
'first_name' => $name
),
$segment_ids
);
}
}
}

View File

@ -38,7 +38,8 @@ class i18n extends \Twig_Extension {
$output[] = '<script type="text/javascript">';
$output[] = ' var MailPoetI18n = MailPoetI18n || {}';
foreach($translations as $key => $translation) {
$output[] = 'MailPoetI18n[\''.$key.'\'] = "'.$translation.'";';
$output[] =
'MailPoetI18n["'.$key.'"] = "'. str_replace('"', '\"', $translation) . '";';
}
$output[] = '</script>';
return join("\n", $output);

View File

@ -5,7 +5,7 @@ if (!defined('ABSPATH')) exit;
/*
* Plugin Name: MailPoet
* Version: 0.0.10
* Version: 0.0.12
* Plugin URI: http://www.mailpoet.com
* Description: MailPoet Newsletters.
* Author: MailPoet
@ -23,7 +23,7 @@ if (!defined('ABSPATH')) exit;
require 'vendor/autoload.php';
define('MAILPOET_VERSION', '0.0.10');
define('MAILPOET_VERSION', '0.0.12');
$initializer = new Initializer(array(
'file' => __FILE__,

View File

@ -25,7 +25,6 @@
"papaparse": "4.1.1",
"parsleyjs": "^2.1.2",
"react": "0.14.3",
"react-checkbox-group": "0.2.2",
"react-dom": "0.14.3",
"react-infinity": "1.2.2",
"react-prefixr": "0.1.0",

View File

@ -1,21 +1,18 @@
define([
'newsletter_editor/App',
'newsletter_editor/components/save',
'amd-inject-loader!newsletter_editor/components/save'
], function(EditorApplication, SaveComponent, SaveInjector) {
'amd-inject-loader!newsletter_editor/components/save',
'jquery'
], function(EditorApplication, SaveComponent, SaveInjector, jQuery) {
describe('Save', function() {
describe('save method', function() {
var module;
before(function() {
module = SaveInjector({
'mailpoet': {
Ajax: {
post: function() {
var deferred = jQuery.Deferred();
deferred.resolve({});
return deferred;
}
'newsletter_editor/components/communication': {
saveNewsletter: function() {
return jQuery.Deferred();
}
}
});
@ -26,29 +23,41 @@ define([
global.stubChannel(EditorApplication, {
trigger: spy,
});
EditorApplication.toJSON = sinon.stub();
EditorApplication.toJSON = sinon.stub().returns({
body: {
type: 'container',
},
});
module.save();
expect(spy.withArgs('beforeEditorSave').calledOnce).to.be.true;
});
it('triggers afterEditorSave event', function() {
var stub = sinon.stub().callsArgWith(2, { success: true }),
spy = sinon.spy();
var spy = sinon.spy(),
promise = jQuery.Deferred();
global.stubChannel(EditorApplication, {
trigger: spy,
});
EditorApplication.toJSON = sinon.stub();
EditorApplication.toJSON = sinon.stub().returns({
body: {
type: 'container',
},
});
var module = SaveInjector({
'newsletter_editor/components/communication': {
saveNewsletter: sinon.stub().returns(promise),
}
});
promise.resolve({ success: true });
module.save();
expect(spy.withArgs('afterEditorSave').calledOnce).to.be.true;
});
it('sends newsletter json to server for saving', function() {
var mock = sinon.mock({ saveNewsletter: function() {} }).expects('saveNewsletter').once().returns(jQuery.Deferred());
var mock = sinon.mock().once().returns(jQuery.Deferred());
var module = SaveInjector({
'mailpoet': {
Ajax: {
post: mock,
}
'newsletter_editor/components/communication': {
saveNewsletter: mock,
}
});
global.stubChannel(EditorApplication);
@ -58,6 +67,29 @@ define([
mock.verify();
});
it('encodes newsletter body in JSON format', function() {
var body = {type: 'testType'},
mock = sinon.mock()
.once()
.withArgs({
body: JSON.stringify(body),
})
.returns(jQuery.Deferred());
global.stubChannel(EditorApplication);
EditorApplication.toJSON = sinon.stub().returns({
body: body,
});
var module = SaveInjector({
'newsletter_editor/components/communication': {
saveNewsletter: mock,
}
});
module.save();
mock.verify();
});
});
describe('view', function() {

View File

@ -79,6 +79,31 @@ class SettingCest {
expect($record->value)->equals('new data');
}
function itCanGetAndSetValue() {
expect(Setting::setValue('test', '123'))->true();
expect(Setting::getValue('test'))->equals('123');
}
function itCanGetAndSetNestedValue() {
expect(Setting::setValue('test.key', '123'))->true();
expect(Setting::getValue('test.key'))->equals('123');
expect(Setting::setValue('test.key.subkey', '123'))->true();
expect(Setting::setValue('test.key.subkey2', '456'))->true();
expect(Setting::getValue('test.key'))->notEmpty();
expect(Setting::getValue('test.key.subkey'))->equals('123');
expect(Setting::getValue('test.key.subkey2'))->equals('456');
}
function itCanSetValueToNull() {
expect(Setting::setValue('test.key', true))->true();
expect(Setting::getValue('test.key'))->equals(true);
expect(Setting::setValue('test.key', null))->true();
expect(Setting::getValue('test.key'))->null();
}
function _after() {
ORM::forTable(Setting::$_table)
->deleteMany();

View File

@ -317,6 +317,102 @@ class SubscriberCest {
expect($subscribers[0]['first_name'])->equals($values[0]['first_name']);
}
function itCanSubscribe() {
$segment = Segment::create();
$segment->hydrate(array('name' => 'List #1'));
$segment->save();
$segment2 = Segment::create();
$segment2->hydrate(array('name' => 'List #2'));
$segment2->save();
$subscriber = Subscriber::subscribe(
$this->data,
array($segment->id(), $segment2->id())
);
expect($subscriber->id() > 0)->equals(true);
expect($subscriber->segments()->count())->equals(2);
expect($subscriber->email)->equals($this->data['email']);
expect($subscriber->first_name)->equals($this->data['first_name']);
expect($subscriber->last_name)->equals($this->data['last_name']);
expect($subscriber->status)->equals('subscribed');
expect($subscriber->deleted_at)->equals(null);
}
function itCanBeAddedToSegments() {
$segment = Segment::create();
$segment->hydrate(array('name' => 'List #1'));
$segment->save();
$segment2 = Segment::create();
$segment2->hydrate(array('name' => 'List #2'));
$segment2->save();
$this->subscriber->addToSegments(array($segment->id(), $segment2->id()));
$subscriber_segments = $this->subscriber->segments()->findArray();
expect($this->subscriber->segments()->count())->equals(2);
expect($subscriber_segments[0]['name'])->equals('List #1');
expect($subscriber_segments[1]['name'])->equals('List #2');
}
function itCanBeUpdatedByEmail() {
$subscriber_updated = Subscriber::createOrUpdate(array(
'email' => $this->data['email'],
'first_name' => 'JoJo',
'last_name' => 'DoDo'
));
expect($this->subscriber->id())->equals($subscriber_updated->id());
$subscriber = Subscriber::findOne($this->subscriber->id());
expect($subscriber->email)->equals($this->data['email']);
expect($subscriber->first_name)->equals('JoJo');
expect($subscriber->last_name)->equals('DoDo');
}
function itCanSetCustomField() {
$custom_field = CustomField::createOrUpdate(array(
'name' => 'Date of Birth',
'type' => 'date'
));
expect($custom_field->id() > 0)->true();
$value = array(
'year' => 1984,
'month' => 3,
'day' => 9
);
$subscriber = Subscriber::findOne($this->subscriber->id());
$subscriber->setCustomField($custom_field->id(), $value);
$subscriber = $subscriber->withCustomFields()->asArray();
expect($subscriber['cf_'.$custom_field->id()])->equals(
mktime(0, 0, 0, $value['month'], $value['day'], $value['year'])
);
}
function itCanGetCustomField() {
$subscriber = Subscriber::findOne($this->subscriber->id());
expect($subscriber->getCustomField(9999, 'default_value'))
->equals('default_value');
$custom_field = CustomField::createOrUpdate(array(
'name' => 'Custom field: text input',
'type' => 'input'
));
$subscriber->setCustomField($custom_field->id(), 'non_default_value');
expect($subscriber->getCustomField($custom_field->id(), 'default_value'))
->equals('non_default_value');
}
function _after() {
ORM::forTable(Subscriber::$_table)
->deleteMany();

View File

@ -58,7 +58,7 @@ class MailChimpCest {
expect(isset($subscribers['data']['duplicate']))->true();
expect(isset($subscribers['data']['header']))->true();
expect(count($subscribers['data']['subscribers']))->equals(1);
expect($subscribers['data']['count'])->equals(1);
expect($subscribers['data']['subscribersCount'])->equals(1);
}
function itFailsWhenListHeadersDontMatch() {

View File

@ -66,7 +66,7 @@
type="radio"
name="on_success"
value="message"
<% if(form.data.settings.on_success is empty or form.data.settings.on_success == 'message') %>
<% if(form.settings.on_success is empty or form.settings.on_success == 'message') %>
checked="checked"
<% endif %>
/><%= __('Show message') %>
@ -77,15 +77,15 @@
type="radio"
name="on_success"
value="page"
<% if(form.data.settings.on_success == 'page') %>
<% if(form.settings.on_success == 'page') %>
checked="checked"
<% endif %>
/><%= __('Go to page') %>
</label>
</p>
<!-- default success message -->
<% if form.data.settings.success_message %>
<% set success_message = form.data.settings.success_message %>
<% if form.settings.success_message %>
<% set success_message = form.settings.success_message %>
<% else %>
<% set success_message = __('Check your inbox now to confirm your subscription.') %>
<% endif %>
@ -103,7 +103,7 @@
<select name="success_page">
<% for page in pages %>
<option value="<%= page.id %>"
<%- if form.data.settings.success_page != page.id %>
<%- if form.settings.success_page == page.id %>
<%=- ' selected="selected"' %>
<%- endif %>><%= page.title %></option>
<% endfor %>
@ -445,28 +445,31 @@
return false;
}
var message = null;
if(callback !== false) {
var message = null;
if(response.is_widget === true) {
message = "<%= __('Saved! The changes are already active in your widget.') %>";
} else {
message = "<%= __('Saved! Add this form to %1$sa widget%2$s.') | format("<a href='widgets.php' target='_blank'>", '</a>') | raw %>";
}
if(response.is_widget === true) {
message = "<%= __('Saved! The changes are already active in your widget.') %>";
} else {
message = "<%= __('Saved! Add this form to %1$sa widget%2$s.') | format("<a href='widgets.php' target='_blank'>", '</a>') | raw %>";
}
if(message !== null) {
MailPoet.Notice.hide();
MailPoet.Notice.success(message, {
scroll: true,
static: (response.is_widget === false)
});
}
if(message !== null) {
MailPoet.Notice.hide();
MailPoet.Notice.success(message, {
scroll: true,
static: (response.is_widget === false)
});
}
// if there is a callback, call it!
if(callback !== undefined) {
callback();
// if there is a callback, call it!
if(callback !== undefined) {
callback();
}
}
});
}
window.mailpoet_form_save = mailpoet_form_save;
// toolbar: on success toggle
$(document).on('change', 'input[name="on_success"]', toggleOnSuccessOptions);
@ -506,7 +509,7 @@
// open popup
MailPoet.Modal.popup({
title: "<%= __('Add new field') %>",
template: $('#form_template_field_new').html()
template: $('#form_template_field_form').html()
});
return false;
@ -524,7 +527,7 @@
if(response.result !== false) {
MailPoet.Modal.popup({
title: "<%= __('Edit field') %>",
template: $('#form_template_field_new').html(),
template: $('#form_template_field_form').html(),
data: response
});
}
@ -544,9 +547,16 @@
endpoint: 'customFields',
action: 'delete',
data: id
}).done(function(result) {
if(result === true) {
}).done(function(response) {
if(response.result === true) {
item.remove();
if(response.field !== undefined) {
WysijaForm.removeBlock(response.field, function() {
mailpoet_form_save(false);
});
}
mailpoet_form_fields();
MailPoet.Notice.success(
"<%= __('Removed custom field “"+name+"“') %>"
@ -685,8 +695,8 @@
) %>
<!-- custom field: new -->
<%= partial('form_template_field_new',
'form/templates/settings/field_new.hbs'
<%= partial('form_template_field_form',
'form/templates/settings/field_form.hbs'
) %>
<!-- form preview -->

View File

@ -6,7 +6,7 @@
{{#ifCond type '==' 'input'}}
{{> _settings_label }}
{{> _settings_label_within }}
{{#ifCond field 'in' 'first_name,last_name' }}
{{#ifCond id 'in' 'first_name,last_name' }}
{{> _settings_required }}
{{/ifCond}}
{{/ifCond}}

View File

@ -78,7 +78,16 @@
data: data
}).done(function(response) {
if(response.result === true) {
// close popup
MailPoet.Modal.close();
if(WysijaForm.updateBlock(response.field) === true) {
// trigger save, if a block has been updated
mailpoet_form_save(false);
}
mailpoet_form_fields();
if(data.id) {
MailPoet.Notice.success(
"<%= __('Updated custom field “"+data.name+"“') %>"
@ -88,9 +97,6 @@
"<%= __('Added custom field “"+data.name+"“') %>"
);
}
// close popup
MailPoet.Modal.success();
} else {
if(response.errors.length > 0) {
$(response.errors).each(function(i, error) {

View File

@ -20,7 +20,6 @@
mailpoet_segment_selection_render();
setInputNames();
setupSortableSegments();
// add segment
$('.mailpoet_segment_add').on('click', function() {
@ -125,6 +124,7 @@
$(item).find('.mailpoet_segment_id').attr('name', 'params[values]['+index+'][id]');
$(item).find('.mailpoet_segment_name').attr('name', 'params[values]['+index+'][name]');
});
setupSortableSegments();
}
});
<{{!}}/script>

View File

@ -16,8 +16,6 @@
class="mailpoet_form mailpoet_form_<%= form_type %>"
novalidate
>
<div class="mailpoet_message"></div>
<input type="hidden" name="form_id" value="<%= form.id %>" />
<% if not(form.settings.segments_selected_by == 'user') %>
@ -26,6 +24,8 @@
<% endfor %>
<% endif %>
<%= html | raw %>
<div class="mailpoet_message"></div>
</form>
</div>
<%= after_widget | raw %>

View File

@ -1239,10 +1239,10 @@
'<%= __('Select a shortcode') %>',
unsubscribeLinkMissing:
'<%= __('Please include an unsubscribe link to continue.') %>',
testEmailSent:
'<%= __('Test email successfully sent!') %>',
unknownErrorOccurred:
'<%= __('An unknown error occurred, please check your settings.') %>',
newsletterPreviewSent:
'<%= __('Newsletter preview email has been successfully sent!') %>',
newsletterPreviewFailedToSend:
'<%= __('Attempt to send a newsletter preview email failed. Please verify that your sending method is configured correctly try again.') %>',
templateNameMissing:
'<%= __('Please add a template name') %>',
templateDescriptionMissing:

View File

@ -1,20 +1,6 @@
<div class="handlediv" title="Click to toggle"><br></div>
<h3><%= __('Preview') %></h3>
<div class="mailpoet_region_content">
<div class="mailpoet_form_field">
<label>
<%= __('From name') %><br />
<input id="mailpoet_preview_from_name" class="mailpoet_input mailpoet_input_full" type="text" name="from_name" value="{{ from_name }}" />
</label>
</div>
<div class="mailpoet_form_field">
<label>
<%= __('From email') %><br />
<input id="mailpoet_preview_from_email" class="mailpoet_input mailpoet_input_full" type="text" name="from_email" value="{{ from_email }}" />
</label>
</div>
<div class="mailpoet_form_field">
<label>
<%= __('Send preview to') %><br />

View File

@ -102,17 +102,20 @@
<% endif %>
/>
</p>
<p>
<label><%= __('Users will be subscribed to these lists:') %></label>
</p>
<p>
<select
id="mailpoet_subscribe_on_comment_lists"
name="subscribe[on_comment][lists][]"
id="mailpoet_subscribe_on_comment_segments"
name="subscribe[on_comment][segments][]"
placeholder="<%= __('Choose a list') %>"
multiple
>
<% for segment in segments %>
<option
value="<%= segment.id %>"
<% if(segment.id in settings.subscribe.on_comment.lists) %>
<% if(segment.id in settings.subscribe.on_comment.segments) %>
selected="selected"
<% endif %>
><%= segment.name %></option>
@ -163,17 +166,20 @@
<% endif %>
/>
</p>
<p>
<label><%= __('Users will be subscribed to these lists:') %></label>
</p>
<p>
<select
id="mailpoet_subscribe_on_register_lists"
name="subscribe[on_register][lists][]"
id="mailpoet_subscribe_on_register_segments"
name="subscribe[on_register][segments][]"
placeholder="<%= __('Choose a list') %>"
multiple
>
<% for segment in segments %>
<option
value="<%= segment.id %>"
<% if(segment.id in settings.subscribe.on_register.lists) %>
<% if(segment.id in settings.subscribe.on_register.segments) %>
selected="selected"
<% endif %>
><%= segment.name %></option>
@ -227,19 +233,19 @@
><%= __('Edit') %></a>
</p>
<p>
<label><%= __('Subscribers can choose from these lists :') %></label>
<label><%= __('Subscribers can choose from these lists:') %></label>
</p>
<p>
<select
id="mailpoet_subscription_edit_lists"
name="subscription[lists][]"
id="mailpoet_subscription_edit_segments"
name="subscription[segments][]"
placeholder="<%= __('Leave empty to show all lists') %>"
multiple
>
<% for segment in segments %>
<option
value="<%= segment.id %>"
<% if(segment.id in settings.subscription.lists) %>
<% if(segment.id in settings.subscription.segments) %>
selected="selected"
<% endif %>
><%= segment.name %></option>
@ -307,7 +313,7 @@
</p>
<p>
<select
id="mailpoet_shortcode_subscribers_list"
id="mailpoet_shortcode_subscribers_count"
data-shortcode="mailpoet_subscribers_count"
data-output="mailpoet_shortcode_subscribers"
placeholder="<%= __('Leave empty to show all lists') %>"
@ -328,25 +334,26 @@
// on dom loaded
$(function() {
// select2 instances
$('#mailpoet_subscribe_on_comment_lists').select2();
$('#mailpoet_subscribe_on_register_lists').select2();
$('#mailpoet_subscription_edit_lists').select2();
$('#mailpoet_subscribe_on_comment_segments').select2();
$('#mailpoet_subscribe_on_register_segments').select2();
$('#mailpoet_subscription_edit_segments').select2();
$('#mailpoet_shortcode_archives_list').select2();
$('#mailpoet_shortcode_subscribers_list').select2();
$('#mailpoet_shortcode_subscribers_count').select2();
// shortcodes
$('#mailpoet_shortcode_archives_list, #mailpoet_shortcode_subscribers_list')
$('#mailpoet_shortcode_archives_list, #mailpoet_shortcode_subscribers_count')
.on('change', function() {
var shortcode = $(this).data('shortcode'),
values = $(this).val() || [];
if(values.length > 0) {
shortcode += ' list_id="';
if (values.length > 0) {
shortcode += ' segments="';
shortcode += values.join(',');
shortcode += '"';
}
$('#' + $(this).data('output')).val('[' + shortcode + ']');
$('#' + $(this).data('output'))
.val('[' + shortcode + ']');
});
});
});

View File

@ -1,5 +1,8 @@
<% extends 'layout.html' %>
<% block content %>
<% set csvDescription = __('This needs to be in CSV style. See [link]examples in our support site[/link].') %>
<% set csvKBLink = '<a target="_blank" href="http://support.mailpoet.com/knowledgebase/importing-subscribers-with-a-csv-file?utm_source=wpadmin&utm_campaign=import">' %>
<div id="mailpoet_subscribers_import" class="wrap">
<h2 class="title">
<%= __('Import') %>
@ -19,9 +22,11 @@
'noMailChimpLists': __('No active lists found.'),
'serverError': __('Server error:'),
'select': __('Select'),
'csvKBLink': csvKBLink,
'wrongFileFormat': __('Only comma-separated (CSV) file format is supported.'),
'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.'),
'dataProcessingError': __("Your data could not be processed. Please make sure it is in the proper format."),
'noValidRecords': __('No valid records were found. This needs to be in CSV style. See [link]examples in our support site[/link]'),
'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.'),

View File

@ -30,14 +30,7 @@
<th scope="row">
<label for="paste_input"> <%= __('Copy and paste your subscribers from Excel/Sheets') %>
<p class="description">
<%=
__('This needs to be in CSV style. See [link]examples in our support site[/link].')
|replace({
'[link]': '<a target="_blank" href="http://support.mailpoet.com/knowledgebase/importing-subscribers-with-a-csv-file/main.html?utm_source=wpadmin&utm_campaign=import">',
'[/link]': '</a>'
})
|raw
%>
<%= csvDescription|replace({'[link]': csvKBLink, '[/link]': '</a>'})|raw %>
</p>
</label>
</th>
@ -60,23 +53,15 @@
<th scope="row">
<label for="file_local">
<%= __('Upload a file') %>
<p class="description">
<%=
__('This needs to be in CSV style. See [link]examples in our support site[/link].')
|replace({
'[link]': '<a target="_blank" href="http://support.mailpoet.com/knowledgebase/importing-subscribers-with-a-csv-file/main.html?utm_source=wpadmin&utm_campaign=import">',
'[/link]': '</a>'
})
|raw
%>
<%= csvDescription|replace({'[link]': csvKBLink, '[/link]': '</a>'})|raw %>
</p>
</label>
</th>
<td>
<input type="file" id="file_local">
<input type="file" id="file_local" accept=".csv" />
&nbsp;
<%= __( 'total max upload file size : %s' )|replace({'%s': maxPostSize}) %>
<%= __('total max upload file size : %s')|format(maxPostSize) %>
</td>
</tr>
</tbody>

View File

@ -16,5 +16,7 @@
<script type="text/javascript">
var mailpoet_segments = <%= json_encode(segments) %>;
var mailpoet_custom_fields = <%= json_encode(custom_fields) %>;
var mailpoet_month_names = <%= json_encode(month_names) %>;
</script>
<% endblock %>

View File

@ -63,6 +63,7 @@
border-color: #df4a02;
text-align: right;
height:auto;
text-shadow: none;
}
#mailpoet_welcome .welcome_sub .button_thanks:hover,
.welcome_sub .button_thanks:active {