Compare commits

...

97 Commits

Author SHA1 Message Date
8dbfe82922 Bump version up to 0.0.14 2016-02-05 17:34:53 +02:00
7322f2151c Merge pull request #334 from mailpoet/unit_tests_update
Enables conditional bypassing of unit tests
2016-02-05 16:38:31 +02:00
c43d2f240d - Updates MailChimp test and .env.sample 2016-02-05 09:35:32 -05:00
bbcd267b6f - Updates .env.sample with available options for unit tests 2016-02-05 09:00:28 -05:00
bbc4acb2a4 Merge pull request #335 from mailpoet/mailer_class_Fix
Fixes detection of reply_to address
2016-02-05 12:52:39 +02:00
c89cc5a919 Merge pull request #333 from mailpoet/test_email_fix
Test email fix
2016-02-05 12:19:40 +02:00
33075940de - Fixes detection of reply_to address 2016-02-04 19:22:11 -05:00
51c09b8360 Merge pull request #325 from mailpoet/router_unit_tests
Unit tests (Router\NewsletterTemplates & Router\Segments)
2016-02-04 18:59:09 -05:00
2fbf85f371 - Catches exception returned by mailer class when sender is not configured 2016-02-04 18:57:07 -05:00
24cb614adb - Enables conditional bypassing of unit tests 2016-02-04 18:41:18 -05:00
55f851208b Major update of unit tests / updated routers + models + react 2016-02-04 19:04:52 +01:00
990dac7727 Merge pull request #332 from mailpoet/editor_fixes
Editor bug fixes
2016-02-04 09:02:21 -05:00
233020ca20 fix unit tests 2016-02-04 14:38:25 +01:00
0c419cde16 Unit tests (Router\NewsletterTemplates & Router\Segments
- moved json_decode(body) inside NewsletterTemplate->asArray (override)
- updated NewsletterTemplate router accordingly
- finished Segments unit test
2016-02-04 14:36:08 +01:00
35f9530d8e Merge pull request #326 from mailpoet/router_unit_tests_2
More Unit Tests + Initializer fix
2016-02-04 08:24:15 -05:00
992fe2a6e9 Fix preventing dragging by settings/delete block tools 2016-02-04 13:47:46 +02:00
c2cb88f995 Unit tests fixed + models & routers update 2016-02-04 11:41:05 +01:00
ba69d659ab Changed editor to not send 'last_modified', let server pick one 2016-02-03 18:21:30 +02:00
397d988eb1 Allow block dragging only with "Move" tool, but not with others 2016-02-03 18:21:30 +02:00
d85f2341ec Change Posts/ALC to use MailPoet specific image size 2016-02-03 18:21:30 +02:00
2b3c288b5f Merge pull request #330 from mailpoet/template_footer_fix
Removes footer text/link color overrides for coffee shop template
2016-02-03 11:19:43 -05:00
8ec28a23a7 Removes footer text/link color overrides for coffee shop template 2016-02-03 15:04:18 +02:00
12c9623e2f Better Error handling for models
- added (array)getErrors() to models, returns false if no errors
- converted Forms::saveEditor method to use getErrors
- added error handling on the form editor view
2016-02-03 12:23:42 +01:00
8ba9fdccbc fix for initializer - widget is now registered 2016-02-03 10:42:56 +01:00
a2ef62302f More Unit Tests + Initializer fix
- added unit test for Router\Forms
- updated unit test for Model\Segment to reflect changes
2016-02-02 17:22:11 +01:00
24ecc879d3 Merge pull request #319 from mailpoet/php53-fix
Updates code to work with PHP 5.3
2016-02-02 13:25:23 +01:00
a1104a7f90 Merge pull request #320 from mailpoet/sending_queue_worker_fix
Sending queue worker fix
2016-02-01 16:32:42 +02:00
aa959810e9 Merge pull request #322 from mailpoet/code_coverage
Code coverage report
2016-02-01 16:31:53 +02:00
45b7a79277 Merge pull request #321 from mailpoet/router_upgrade_3
Router upgrade 3
2016-02-01 15:41:13 +02:00
41c8c0dae5 Code coverage report 2016-02-01 14:07:54 +01:00
2aeab7aaff Router Upgrade #3 & bugfix on Cron/Supervisor
- ALC
- Cron
- ImportExport
- Mailer
- Newsletters (only get method for consistency with other router get methods)
- Permissions
2016-02-01 13:00:11 +01:00
4fd0c4b484 Router updates + unit tests + React
- added -f flag to run unit test command in order to fail fast
- pass only id to "$endpoint->get($id)" in React forms instead of array
- updated routers according to the ->get($id) change
- refactored a bit the way form creation works
- added unit tests for Segments router
2016-02-01 11:56:21 +01:00
d4623cf763 Router update for Settings and Setup + unit tests 2016-02-01 11:56:21 +01:00
181c4fed08 - Fixes an issues with the sending queue worker throwing and error when
newsletter is not found
2016-01-31 21:35:34 -05:00
7884dd8389 - Updates code to work with PHP 5.3. Closes #307 2016-01-31 14:02:57 -05:00
b577d33414 Merge pull request #318 from mailpoet/minor_cron_update
Cron update
2016-01-29 22:03:55 +02:00
70de0a01bf - Moves cron timeout/execution limit to the central cron helper class 2016-01-29 15:01:10 -05:00
3b7f77d9af Bump up version to 0.0.13 2016-01-29 21:47:56 +02:00
21847ca875 Merge pull request #315 from mailpoet/sending_queue_worker_fix
Sending worker newsletter processing fix
2016-01-29 21:07:17 +02:00
6153316047 Merge pull request #314 from mailpoet/cron_loop_fix
Cron refactoring
2016-01-29 21:06:35 +02:00
32f8f07602 - Refactors and fixes issues identified during code review 2016-01-29 13:30:13 -05:00
70a04d9bf6 Merge pull request #317 from mailpoet/router_upgrade_2
CustomFields (Router update + Form block renaming + Unit tests)
2016-01-29 18:45:29 +02:00
bb1cc997cc CustomFields
- renamed form block type "input" to "text" for consistency with React
- updated CustomFields router to comply with main router
- unit tests for CF router
2016-01-29 17:16:24 +01:00
24f96d9d7d Merge pull request #313 from mailpoet/router_upgrade
Main Router update + Subscribers router update + Unit test
2016-01-29 17:01:21 +02:00
46c7332da2 Merge pull request #316 from mailpoet/sendgrid_mailer_fix
SendGrid mailer update
2016-01-29 12:53:44 +02:00
2f42f643ab - Fixes message body construction 2016-01-28 22:29:47 -05:00
63c87f3746 - Fixes issue with the sending worker failing to process newsletters 2016-01-28 21:40:30 -05:00
d4d575cda4 - Refactors cron supervisor/daemon/router 2016-01-28 21:38:23 -05:00
2cf03ec0a3 - Fixes cron HTTP request loop issue 2016-01-28 12:50:12 -05:00
72ad98a77f unit test for router Subscribers 2016-01-28 18:00:55 +01:00
b5094f568c Merge pull request #312 from mailpoet/mailpoet_bridge_update
MailPoet mailer update
2016-01-28 16:50:47 +02:00
7d224274fc - Rebases master & fixes some code 2016-01-28 09:40:57 -05:00
f3b9f7be92 - Updates MailPoet mailer to support batch sending
- Fixes message encoding issue
2016-01-28 09:40:57 -05:00
6e74f82ace Merge pull request #311 from mailpoet/mailer_tests_update
Disables send method for all mailer tests
2016-01-28 14:20:56 +02:00
831eb6af44 Merge pull request #310 from mailpoet/fix_install
Fix init/hooks so that we properly initialize db/plugin
2016-01-28 13:51:22 +02:00
1e8e5aecee Remove space after if 2016-01-28 13:50:58 +02:00
c0f98c9ba6 - Moves credentials from mailer tests to ENV file
- Disables send method by default unless enabled in ENV file
2016-01-27 18:36:38 -05:00
746c19d6ed - Resolves an issue with cron not starting 2016-01-27 14:15:00 -05:00
894a9e8c90 Router\Router is now taking care of outputting JSON
- converted Subscribers Router to return array instead of wp_send_json()
- added unit test for Subscribers Router
2016-01-27 17:23:30 +01:00
8fea917337 Merge pull request #308 from mailpoet/mailer_updates
Update to mailer methods
2016-01-27 14:13:37 +02:00
c60425afb2 Fix init/hooks so that we properly initialize db/plugin
- Added activation hook in main file (mailpoet.php)
- auto deactivate plugin in case of fatal errors during init
2016-01-27 12:52:40 +01:00
0776e9ad73 - Adds "reply to" option to all mailers
- Replaces WPMail with Swift using local transport (PHP mail)
- Fixes AmazonSES region naming convention
- Updates tests
2016-01-26 19:08:02 -05:00
91981cc324 Merge pull request #306 from mailpoet/mailer_cleanup
Removes Mandrill API mailer method
2016-01-26 18:52:10 +02:00
1906fafacb Merge pull request #301 from mailpoet/shortcodes_implementation
Shortcodes implementation
2016-01-26 18:32:15 +02:00
c11d95b402 - Refactors code 2016-01-26 11:29:56 -05:00
b4c8fe6f45 - Updates newsletter router and sending queue worker to work with the
shortcodes implementation
2016-01-26 10:44:18 -05:00
d0e770e0fc - Removes Mandrill API mailer method 2016-01-26 09:18:48 -05:00
3d6d1a4282 - Disables shortcodes unit test 2016-01-26 09:14:12 -05:00
dc3b47db00 - Implements shortcodes
- Updates newsletter router/sending queue worker to use shortcodes
  replacement class
2016-01-26 09:13:29 -05:00
900d6694e2 Merge pull request #302 from mailpoet/parsley_issue
removed parsley validation on step 3
2016-01-26 15:50:31 +02:00
e8074a61a5 Merge pull request #298 from mailpoet/text_block_rendering_fix
Text block formatting fix
2016-01-26 12:51:07 +02:00
64501a914a removed parsley validation on step 3
- fixed placeholder for select2 instances in settings
- fixed issue in sending queue worker when newsletter does not exist
2016-01-26 11:36:20 +01:00
de70e855ad - Replaces regex with DOM queries 2016-01-25 21:09:16 -05:00
03e3b5a94b - Fixes regex that formats heading tags 2016-01-25 21:08:16 -05:00
3331bed31c Merge pull request #300 from mailpoet/text_rendering
Text rendering
2016-01-25 18:16:16 +02:00
8c5a33a0fe Merge pull request #297 from mailpoet/alc_rendering
Automated latest content rendering
2016-01-25 17:35:51 +02:00
01eb6c7a98 - Refactors ALC 2016-01-25 09:27:51 -05:00
f502e0b677 - Implements text rendering
- Updates tests
- Updates newsletter router and sending queue worker to reflect changes to
  the renderer
2016-01-22 22:07:02 -05:00
a6b64a1c5d - Implements automated latest content rendering 2016-01-22 19:59:50 -05:00
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
144 changed files with 3977 additions and 2140 deletions

View File

@ -1,2 +1,16 @@
WP_TEST_PATH="/var/www/wordpress"
WP_TEST_ENABLE_NETWORK_TESTS="true"
WP_TEST_IMPORT_MAILCHIMP_API=""
WP_TEST_IMPORT_MAILCHIMP_LISTS="" // (separated with comma)
WP_TEST_MAILER_ENABLE_SENDING="true"
WP_TEST_MAILER_AMAZON_ACCESS=""
WP_TEST_MAILER_AMAZON_SECRET=""
WP_TEST_MAILER_AMAZON_REGION=""
WP_TEST_MAILER_ELASTICEMAIL_API=""
WP_TEST_MAILER_MAILGUN_API=""
WP_TEST_MAILER_MAILGUN_DOMAIN=""
WP_TEST_MAILER_MAILPOET_API=""
WP_TEST_MAILER_SENDGRID_API=""
WP_TEST_MAILER_SMTP_HOST=""
WP_TEST_MAILER_SMTP_LOGIN=""
WP_TEST_MAILER_SMTP_PASSWORD=""

View File

@ -105,7 +105,17 @@ class RoboFile extends \Robo\Tasks {
function testUnit($file = null) {
$this->loadEnv();
$this->_exec('vendor/bin/codecept build');
$this->_exec('vendor/bin/codecept run unit '.(($file) ? $file : ''));
$this->_exec('vendor/bin/codecept run unit -f '.(($file) ? $file : ''));
}
function testCoverage() {
$this->loadEnv();
$this->_exec('vendor/bin/codecept build');
$this->_exec(join(' ', array(
'vendor/bin/codecept run',
'--coverage',
'--coverage-html'
)));
}
function testJavascript() {

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

@ -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

@ -120,7 +120,7 @@ function(
<select
id={ this.props.field.id || this.props.field.name }
ref="select"
placeholder={ this.props.field.placeholder }
data-placeholder={ this.props.field.placeholder }
multiple={ this.props.field.multiple }
defaultValue={ default_value }
{...this.props.field.validation}

View File

@ -48,7 +48,7 @@ define(
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'get',
data: { id: id }
data: id
}).done(function(response) {
if(response === false) {
this.setState({

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

@ -125,8 +125,8 @@ const FormList = React.createClass({
endpoint: 'forms',
action: 'create'
}).done(function(response) {
if(response !== false) {
window.location = response;
if(response.result && response.form_id) {
window.location = mailpoet_form_edit_url + response.form_id;
}
});
},

View File

@ -9,7 +9,7 @@ define([
Module._query = function(args) {
return MailPoet.Ajax.post({
endpoint: 'wordpress',
endpoint: 'automatedLatestContent',
action: args.action,
data: args.options || {},
});

View File

@ -11,7 +11,7 @@ define([
// Does not hold newsletter content nor newsletter styles, those are
// handled by other components.
Module.NewsletterModel = SuperModel.extend({
stale: ['body'],
stale: ['body', 'created_at', 'deleted_at', 'updated_at'],
initialize: function(options) {
this.on('change', function() {
App.getChannel().trigger('autoSave');

View File

@ -145,14 +145,6 @@ define(
}.bind(this));
}
},
componentDidMount: function() {
if(this.isMounted()) {
jQuery('#mailpoet_newsletter').parsley();
}
},
isValid: function() {
return (jQuery('#mailpoet_newsletter').parsley().validate());
},
render: function() {
return (
<div>
@ -166,8 +158,7 @@ define(
fields={ fields }
params={ this.props.params }
messages={ messages }
isValid={ this.isValid }>
>
<p className="submit">
<input
className="button button-primary"

View File

@ -29,7 +29,7 @@ define(
action: 'save',
data: template
}).done(function(response) {
if(response === true) {
if(response.result === true) {
this.props.onImport(template);
} else {
response.map(function(error) {

View File

@ -29,12 +29,14 @@ define(
subject: 'Draft newsletter',
}
}).done(function(response) {
if(response.id !== undefined) {
this.history.pushState(null, `/template/${response.id}`);
if(response.result && response.newsletter.id) {
this.history.pushState(null, `/template/${response.newsletter.id}`);
} else {
response.map(function(error) {
MailPoet.Notice.error(error);
});
if(response.errors.length > 0) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
}
}.bind(this));
},

View File

@ -138,12 +138,14 @@ define(
options: this.state,
},
}).done(function(response) {
if(response.id !== undefined) {
this.showTemplateSelection(response.id);
if(response.result && response.newsletter.id) {
this.showTemplateSelection(response.newsletter.id);
} else {
response.map(function(error) {
MailPoet.Notice.error(error);
});
if(response.errors.length > 0) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
}
}.bind(this));
},

View File

@ -32,12 +32,15 @@ define(
type: 'standard',
}
}).done(function(response) {
if(response.id !== undefined) {
this.showTemplateSelection(response.id);
console.log(response);
if(response.result && response.newsletter.id) {
this.showTemplateSelection(response.newsletter.id);
} else {
response.map(function(error) {
MailPoet.Notice.error(error);
});
if(response.errors.length > 0) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
}
}.bind(this));
},

View File

@ -111,12 +111,14 @@ define(
options: this.state,
},
}).done(function(response) {
if(response.id !== undefined) {
this.showTemplateSelection(response.id);
if(response.result && response.newsletter.id) {
this.showTemplateSelection(response.newsletter.id);
} else {
response.map(function(error) {
MailPoet.Notice.error(error);
});
if(response.errors.length > 0) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
}
}.bind(this));
},

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,24 @@ define(
}
];
var custom_fields = window.mailpoet_custom_fields || [];
custom_fields.map(custom_field => {
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(

View File

@ -19,3 +19,12 @@ modules:
user: ''
password: ''
dump: tests/_data/dump.sql
coverage:
enabled: true
whitelist:
include:
- lib/*
exclude:
blacklist:
include:
exclude:

View File

@ -10,7 +10,8 @@
"swiftmailer/swiftmailer": "^5.4",
"phpseclib/phpseclib": "*",
"mtdowling/cron-expression": "^1.0",
"nesbot/carbon": "^1.21"
"nesbot/carbon": "^1.21",
"soundasleep/html2text": "^0.3.0"
},
"require-dev": {
"codeception/codeception": "*",

672
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -9,13 +9,6 @@ class Activator {
function __construct() {
}
function init() {
register_activation_hook(
Env::$file,
array($this, 'activate')
);
}
function activate() {
$migrator = new Migrator();
$migrator->up();

View File

@ -36,8 +36,9 @@ class Env {
self::$views_path = self::$path . '/views';
self::$assets_path = self::$path . '/assets';
self::$assets_url = plugins_url('/assets', $file);
self::$temp_path = wp_upload_dir()['path'];
self::$temp_URL = wp_upload_dir()['url'];
$wp_upload_dir = wp_upload_dir();
self::$temp_path = $wp_upload_dir['path'];
self::$temp_URL = $wp_upload_dir['url'];
self::$languages_path = self::$path . '/lang';
self::$lib_path = self::$path . '/lib';
self::$plugin_prefix = 'mailpoet_';
@ -72,19 +73,4 @@ class Env {
);
return implode('', $source_name);
}
static function isPluginActivated() {
$activatesPlugins = get_option('active_plugins');
$isActivated = (
in_array(
sprintf('%s/%s.php', basename(self::$path), self::$plugin_name),
$activatesPlugins
) ||
in_array(
sprintf('%s/%s.php', explode('/', plugin_basename(__FILE__))[0], self::$plugin_name),
$activatesPlugins
)
);
return ($isActivated) ? true : false;
}
}

View File

@ -9,6 +9,8 @@ use MailPoet\Settings\Pages;
if(!defined('ABSPATH')) exit;
require_once(ABSPATH . 'wp-admin/includes/plugin.php');
class Initializer {
function __construct($params = array(
'file' => '',
@ -19,21 +21,34 @@ class Initializer {
function init() {
$this->setupDB();
$this->setupActivator();
$this->setupRenderer();
$this->setupLocalizer();
$this->setupMenu();
$this->setupRouter();
$this->setupWidget();
$this->setupAnalytics();
$this->setupPermissions();
$this->setupChangelog();
$this->setupPublicAPI();
$this->runQueueSupervisor();
$this->setupShortcodes();
$this->setupHooks();
$this->setupPages();
$this->setupImages();
register_activation_hook(Env::$file, array($this, 'runMigrator'));
register_activation_hook(Env::$file, array($this, 'runPopulator'));
add_action('init', array($this, 'setup'));
add_action('widgets_init', array($this, 'setupWidget'));
}
function setup() {
try {
$this->setupRenderer();
$this->setupLocalizer();
$this->setupMenu();
$this->setupRouter();
$this->setupPermissions();
$this->setupPublicAPI();
$this->setupAnalytics();
$this->setupChangelog();
$this->runQueueSupervisor();
$this->setupShortcodes();
$this->setupHooks();
$this->setupPages();
$this->setupImages();
} catch(\Exception $e) {
// if anything goes wrong during init
// automatically deactivate the plugin
deactivate_plugins(Env::$file);
}
}
function setupDB() {
@ -81,9 +96,14 @@ class Initializer {
define('MP_NEWSLETTER_STATISTICS_TABLE', $newsletter_statistics);
}
function setupActivator() {
$activator = new Activator();
$activator->init();
function runMigrator() {
$migrator = new Migrator();
$migrator->up();
}
function runPopulator() {
$populator = new Populator();
$populator->up();
}
function setupRenderer() {
@ -97,10 +117,7 @@ class Initializer {
}
function setupMenu() {
$menu = new Menu(
$this->renderer,
Env::$assets_url
);
$menu = new Menu($this->renderer, Env::$assets_url);
$menu->init();
}
@ -138,6 +155,7 @@ class Initializer {
$shortcodes = new Shortcodes();
$shortcodes->init();
}
function setupHooks() {
$hooks = new Hooks();
$hooks->init();
@ -153,7 +171,7 @@ class Initializer {
try {
$supervisor = new Supervisor();
$supervisor->checkDaemon();
} catch (\Exception $e) {
} catch(\Exception $e) {
}
}

View File

@ -296,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;
@ -307,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

@ -1,7 +1,7 @@
<?php
namespace MailPoet\Config;
if (!defined('ABSPATH')) exit;
if(!defined('ABSPATH')) exit;
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');

View File

@ -279,19 +279,19 @@ class FranksRoastHouseTemplate {
"blocks" => array(
array(
"type" => "footer",
"text" => __("<p><span style=\"color: #000000;\"><a href=\"[unsubscribeUrl]\" style=\"color: #000000;\">Unsubscribe</a> | <a href=\"[manageSubscriptionUrl]\" style=\"color: #000000;\">Manage subscription</a></span><br /><span style=\"color: #000000;\">12345 MailPoet Drive, EmailVille, 76543</span></p>"),
"text" => __("<p><a href=\"[unsubscribeUrl]\">Unsubscribe</a> | <a href=\"[manageSubscriptionUrl]\">Manage subscription</a><br />12345 MailPoet Drive, EmailVille, 76543</p>"),
"styles" => array(
"block" => array(
"backgroundColor" => "#a9a7a7"
),
"text" => array(
"fontColor" => "#ffffff",
"fontColor" => "#000000",
"fontFamily" => "Arial",
"fontSize" => "12px",
"textAlign" => "center"
),
"link" => array(
"fontColor" => "#ffffff",
"fontColor" => "#000000",
"textDecoration" => "underline"
)
)

View File

@ -20,7 +20,7 @@ class PublicAPI {
Helpers::underscoreToCamelCase($_GET['action']) :
false;
$this->request_payload = isset($_GET['request_payload']) ?
json_decode(urldecode($_GET['request_payload']), true) :
unserialize(base64_decode($_GET['request_payload'])) :
false;
}

View File

@ -9,13 +9,12 @@ class Widget {
}
function init() {
add_action('widgets_init', array($this, 'registerWidget'));
$this->registerWidget();
if(!is_admin()) {
//$this->setupActions();
add_action('widgets_init', array($this, 'setupDependencies'));
$this->setupDependencies();
} else {
add_action('widgets_init', array($this, 'setupAdminDependencies'));
$this->setupAdminDependencies();
}
}
@ -69,6 +68,9 @@ class Widget {
}
}
// TODO: extract this method into an Initializer
// - the "ajax" part might probably be useless
// - the "post" (non-ajax) part needs to be redone properly
function setupActions() {
// ajax requests
add_action(

70
lib/Cron/CronHelper.php Normal file
View File

@ -0,0 +1,70 @@
<?php
namespace MailPoet\Cron;
use MailPoet\Models\Setting;
use MailPoet\Util\Security;
if(!defined('ABSPATH')) exit;
class CronHelper {
static $daemon_execution_limit = 30;
static $daemon_timeout_limit = 30;
static function createDaemon($token) {
$daemon = array(
'status' => 'starting',
'counter' => 0,
'token' => $token
);
self::saveDaemon($daemon);
return $daemon;
}
static function getDaemon() {
return Setting::getValue('cron_daemon');
}
static function saveDaemon($daemon) {
$daemon['updated_at'] = time();
return Setting::setValue(
'cron_daemon',
$daemon
);
}
static function createToken() {
return Security::generateRandomString();
}
static function accessDaemon($token) {
$payload = serialize(array('token' => $token));
$url = '/?mailpoet-api&section=queue&action=run&request_payload=' .
base64_encode($payload);
$args = array(
'timeout' => 1,
'user-agent' => 'MailPoet (www.mailpoet.com) Cron'
);
$result = wp_remote_get(
self::getSiteUrl() . $url,
$args
);
return wp_remote_retrieve_body($result);
}
private static function getSiteUrl() {
// additional check for some sites running on a virtual machine or behind
// proxy where there could be different ports (e.g., host:8080 => guest:80)
// if the site URL does not contain a port, return the URL
if(!preg_match('!^https?://.*?:\d+!', site_url())) return site_url();
preg_match('!://(?P<host>.*?):(?P<port>\d+)!', site_url(), $server);
// connect to the URL with port
$fp = @fsockopen($server['host'], $server['port'], $errno, $errstr, 1);
if($fp) return site_url();
// connect to the URL without port
$fp = @fsockopen($server['host'], $server['port'], $errno, $errstr, 1);
if($fp) return preg_replace('!(?=:\d+):\d+!', '$1', site_url());
// throw an error if all connection attempts failed
throw new \Exception(__('Site URL is unreachable.'));
}
}

View File

@ -2,8 +2,6 @@
namespace MailPoet\Cron;
use MailPoet\Cron\Workers\SendingQueue;
use MailPoet\Models\Setting;
use MailPoet\Util\Security;
require_once(ABSPATH . 'wp-includes/pluggable.php');
@ -13,132 +11,68 @@ class Daemon {
public $daemon;
public $request_payload;
public $refreshed_token;
public $timer;
private $timer;
function __construct($request_payload = array()) {
set_time_limit(0);
ignore_user_abort();
$this->daemon = $this->getDaemon();
$this->refreshed_token = $this->refreshToken();
$this->daemon = CronHelper::getDaemon();
$this->token = CronHelper::createToken();
$this->request_payload = $request_payload;
$this->timer = microtime(true);
}
function start() {
if(!isset($this->request_payload['session'])) {
$this->abortWithError(__('Missing session ID.'));
}
$this->manageSession('start');
function run() {
$daemon = $this->daemon;
if(!$daemon) {
$this->saveDaemon(
array(
'status' => 'starting',
'counter' => 0
)
);
}
if($daemon['status'] === 'started') {
$_SESSION['cron_daemon'] = array(
'result' => false,
'errors' => array(__('Daemon already running.'))
);
}
if($daemon['status'] === 'starting') {
$_SESSION['cron_daemon'] = 'started';
$_SESSION['cron_daemon'] = array('result' => true);
$this->manageSession('end');
$daemon['status'] = 'started';
$daemon['token'] = $this->refreshed_token;
$this->saveDaemon($daemon);
$this->callSelf();
}
$this->manageSession('end');
}
function run() {
$allowed_statuses = array(
'stopping',
'starting',
'started'
);
if(!$this->daemon || !in_array($this->daemon['status'], $allowed_statuses)) {
$this->abortWithError(__('Invalid daemon status.'));
$this->abortWithError(__('Daemon does not exist.'));
}
if(!isset($this->request_payload['token']) ||
$this->request_payload['token'] !== $this->daemon['token']
$this->request_payload['token'] !== $daemon['token']
) {
$this->abortWithError('Invalid token.');
$this->abortWithError(__('Invalid or missing token.'));
}
$this->abortIfStopped($daemon);
try {
$sending_queue = new SendingQueue($this->timer);
$sending_queue->process();
} catch(Exception $e) {
}
$elapsed_time = microtime(true) - $this->timer;
if($elapsed_time < 30) {
sleep(30 - $elapsed_time);
if($elapsed_time < CronHelper::$daemon_execution_limit) {
sleep(CronHelper::$daemon_execution_limit - $elapsed_time);
}
// after each execution, re-read daemon data in case it was deleted or
// its status has changed
$daemon = CronHelper::getDaemon();
if(!$daemon || $daemon['token'] !== $this->request_payload['token']) {
exit;
}
// after each execution, read daemon in case its status was modified
$daemon = $this->getDaemon();
if($daemon['status'] === 'stopping') $daemon['status'] = 'stopped';
if($daemon['status'] === 'starting') $daemon['status'] = 'started';
$daemon['token'] = $this->refreshed_token;
$daemon['counter']++;
$this->saveDaemon($daemon);
if($daemon['status'] === 'started') $this->callSelf();
}
function getDaemon() {
return Setting::getValue('cron_daemon');
}
function saveDaemon($daemon_data) {
$daemon_data['updated_at'] = time();
return Setting::setValue(
'cron_daemon',
$daemon_data
);
}
function refreshToken() {
return Security::generateRandomString();
}
function manageSession($action) {
switch($action) {
case 'start':
if(session_id()) {
session_write_close();
}
session_id($this->request_payload['session']);
session_start();
break;
case 'end':
session_write_close();
break;
$this->abortIfStopped($daemon);
if($daemon['status'] === 'starting') {
$daemon['status'] = 'started';
}
$daemon['token'] = $this->token;
CronHelper::saveDaemon($daemon);
$this->callSelf();
}
function abortIfStopped($daemon) {
if($daemon['status'] === 'stopped') exit;
if($daemon['status'] === 'stopping') {
$daemon['status'] = 'stopped';
CronHelper::saveDaemon($daemon);
exit;
}
}
function abortWithError($message) {
exit('[mailpoet_cron_error:' . base64_encode($message) . ']');
}
function callSelf() {
$payload = json_encode(array('token' => $this->refreshed_token));
Supervisor::accessRemoteUrl(
'/?mailpoet-api&section=queue&action=run&request_payload=' . urlencode($payload)
);
exit;
}
function abortWithError($error) {
wp_send_json(
array(
'result' => false,
'errors' => array($error)
));
CronHelper::accessDaemon($this->token);
exit;
}
}

View File

@ -1,103 +1,94 @@
<?php
namespace MailPoet\Cron;
use MailPoet\Config\Env;
use MailPoet\Models\Setting;
if(!defined('ABSPATH')) exit;
class Supervisor {
public $daemon;
public $token;
public $force_run;
function __construct($force_start = false) {
$this->force_start = $force_start;
if(!Env::isPluginActivated()) {
throw new \Exception(__('MailPoet is not activated.'));
}
$this->daemon = $this->getDaemon();
function __construct($force_run = false) {
$this->daemon = CronHelper::getDaemon();
$this->token = CronHelper::createToken();
$this->force_run = $force_run;
}
function checkDaemon() {
if(!$this->daemon) {
return $this->startDaemon();
$daemon = $this->daemon;
if(!$daemon) {
$daemon = CronHelper::createDaemon($this->token);
return $this->runDaemon($daemon);
}
if(
!$this->force_start &&
in_array($this->daemon['status'], array('stopped', 'stopping'))
// if the daemon is stopped, return its status and do nothing
if(!$this->force_run &&
isset($daemon['status']) &&
$daemon['status'] === 'stopped'
) {
return $this->daemon['status'];
return $this->formatDaemonStatusMessage($daemon['status']);
}
$elapsed_time = time() - (int)$this->daemon['updated_at'];
if($elapsed_time < 40) {
if(!$this->force_start) {
return;
}
if($this->daemon['status'] === 'stopping' ||
$this->daemon['status'] === 'starting'
) {
return $this->daemon['status'];
}
$elapsed_time = time() - (int) $daemon['updated_at'];
// if it's been less than 40 seconds since last execution and we're not
// force-running the daemon, return its status and do nothing
if($elapsed_time < CronHelper::$daemon_timeout_limit && !$this->force_run) {
return $this->formatDaemonStatusMessage($daemon['status']);
}
$this->daemon['status'] = 'starting';
$this->saveDaemon($this->daemon);
return $this->startDaemon();
// if it's been less than 40 seconds since last execution, we are
// force-running the daemon and it's either being started or stopped,
// return its status and do nothing
elseif($elapsed_time < CronHelper::$daemon_timeout_limit &&
$this->force_run &&
in_array($daemon['status'], array(
'stopping',
'starting'
))
) {
return $this->formatDaemonStatusMessage($daemon['status']);
}
// re-create (restart) daemon
CronHelper::createDaemon($this->token);
return $this->runDaemon();
}
function startDaemon() {
if(!session_id()) session_start();
$sessionId = session_id();
session_write_close();
$_SESSION['cron_daemon'] = null;
$requestPayload = json_encode(array('session' => $sessionId));
self::accessRemoteUrl(
'/?mailpoet-api&section=queue&action=start&request_payload=' .
urlencode($requestPayload)
);
session_start();
$daemonStatus = $_SESSION['cron_daemon'];
unset($_SESSION['daemon']);
session_write_close();
return $daemonStatus;
function runDaemon() {
$request = CronHelper::accessDaemon($this->token);
preg_match('/\[(mailpoet_cron_error:.*?)\]/i', $request, $status);
$daemon = CronHelper::getDaemon();
if(!empty($status) || !$daemon) {
if(!$daemon) {
$message = __('Daemon failed to run.');
} else {
list(, $message) = explode(':', $status[0]);
$message = base64_decode($message);
}
return $this->formatResultMessage(
false,
$message
);
}
return $this->formatDaemonStatusMessage($daemon['status']);
}
function getDaemon() {
return Setting::getValue('cron_daemon');
}
function saveDaemon($daemon_data) {
return Setting::setValue(
'cron_daemon',
$daemon_data
private function formatDaemonStatusMessage($status) {
return $this->formatResultMessage(
true,
sprintf(
__('Daemon is currently %s.'),
__($status)
)
);
}
static function accessRemoteUrl($url) {
$args = array(
'timeout' => 1,
'user-agent' => 'MailPoet (www.mailpoet.com) Cron'
private function formatResultMessage($result, $message) {
$formattedResult = array(
'result' => $result
);
wp_remote_get(
self::getSiteUrl() . $url,
$args
);
}
static function getSiteUrl() {
// additional check for some sites running on a virtual machine or behind
// proxy where there could be different ports (e.g., host:8080 => guest:80)
// if the site URL does not contain a port, return the URL
if(!preg_match('!^https?://.*?:\d+!', site_url())) return site_url();
preg_match('!://(?P<host>.*?):(?P<port>\d+)!', site_url(), $server);
// connect to the URL with port
$fp = @fsockopen($server['host'], $server['port'], $errno, $errstr, 1);
if($fp) return site_url();
// connect to the URL without port
$fp = @fsockopen($server['host'], $server['port'], $errno, $errstr, 1);
if($fp) return preg_replace('!(?=:\d+):\d+!', '$1', site_url());
// throw an error if all connections fail
throw new \Exception(__('Site URL is unreachable.'));
if(!$result) {
$formattedResult['errors'] = array($message);
} else {
$formattedResult['message'] = $message;
}
return $formattedResult;
}
}

View File

@ -1,16 +1,18 @@
<?php
namespace MailPoet\Cron\Workers;
use MailPoet\Cron\CronHelper;
use MailPoet\Mailer\Mailer;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterStatistics;
use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Newsletter\Shortcodes\Shortcodes;
if(!defined('ABSPATH')) exit;
class SendingQueue {
public $timer;
private $timer;
function __construct($timer = false) {
$this->timer = ($timer) ? $timer : microtime(true);
@ -19,13 +21,12 @@ class SendingQueue {
function process() {
// TODO: implement mailer sending frequency limits
foreach($this->getQueues() as $queue) {
$newsletter = Newsletter::findOne($queue->newsletter_id)
->asArray();
$newsletter = Newsletter::findOne($queue->newsletter_id);
if(!$newsletter) {
continue;
};
$newsletter = $newsletter->asArray();
$mailer = $this->configureMailerForNewsletter($newsletter);
$newsletter = $this->renderNewsletter($newsletter);
$subscribers = json_decode($queue->subscribers, true);
$subscribers_to_process = $subscribers['to_process'];
if(!isset($subscribers['processed'])) $subscribers['processed'] = array();
@ -37,7 +38,7 @@ class SendingQueue {
$this->checkExecutionTimer();
$result = $this->sendNewsletter(
$mailer,
$this->processNewsletter($newsletter),
$this->processNewsletter($newsletter, $db_subscriber),
$db_subscriber);
if($result) {
$this->updateStatistics($newsletter['id'], $db_subscriber['id'], $queue->id);
@ -51,9 +52,14 @@ class SendingQueue {
}
}
function processNewsletter($newsletter) {
// TODO: replace shortcodes, etc..
return $newsletter;
function processNewsletter($newsletter, $subscriber) {
$rendered_newsletter = $this->renderNewsletter($newsletter);
$shortcodes = new Shortcodes($rendered_newsletter['body']['html'], $newsletter, $subscriber);
$processed_newsletter['body']['html'] = $shortcodes->replace();
$shortcodes = new Shortcodes($rendered_newsletter['body']['text'], $newsletter, $subscriber);
$processed_newsletter['body']['text'] = $shortcodes->replace();
$processed_newsletter['subject'] = $rendered_newsletter['subject'];
return $processed_newsletter;
}
function sendNewsletter($mailer, $newsletter, $subscriber) {
@ -115,7 +121,7 @@ class SendingQueue {
function checkExecutionTimer() {
$elapsed_time = microtime(true) - $this->timer;
if($elapsed_time >= 30) throw new \Exception('Maximum execution time reached.');
if($elapsed_time >= CronHelper::$daemon_execution_limit) throw new \Exception(__('Maximum execution time reached.'));
}
function getQueues() {
@ -126,9 +132,8 @@ class SendingQueue {
}
function renderNewsletter($newsletter) {
$renderer = new Renderer(json_decode($newsletter['body'], true));
// TODO: update once text rendering is implemented/enderer returns an array
$newsletter['body'] = array('html' => $renderer->render(), 'text' => '');
$renderer = new Renderer($newsletter);
$newsletter['body'] = $renderer->render();
return $newsletter;
}
}

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

@ -1,7 +1,7 @@
<?php
namespace MailPoet\Form\Block;
class Input extends Base {
class Text extends Base {
static function render($block) {
$type = 'text';
@ -15,7 +15,7 @@ class Input extends Base {
$html .= static::renderLabel($block);
$html .= '<input type="'.$type.'" class="mailpoet_input" ';
$html .= '<input type="'.$type.'" class="mailpoet_text" ';
$html .= 'name="'.static::getFieldName($block).'" ';

View File

@ -49,7 +49,7 @@ class Renderer {
private static function renderBlock($block = array()) {
$html = '';
switch ($block['type']) {
switch($block['type']) {
case 'html':
$html .= Block\Html::render($block);
break;
@ -78,8 +78,8 @@ class Renderer {
$html .= Block\Select::render($block);
break;
case 'input':
$html .= Block\Input::render($block);
case 'text':
$html .= Block\Text::render($block);
break;
case 'textarea':

View File

@ -65,6 +65,8 @@ class Widget extends \WP_Widget {
)
);
$form_edit_url = admin_url('admin.php?page=mailpoet-form-editor&id=');
// set title
$title = isset($instance['title']) ? strip_tags($instance['title']) : '';
@ -102,8 +104,9 @@ class Widget extends \WP_Widget {
endpoint: 'forms',
action: 'create'
}).done(function(response) {
if(response !== false) {
window.location = response;
if(response.result && response.form_id) {
window.location =
"<?php echo $form_edit_url; ?>" + response.form_id;
}
});
return false;

View File

@ -19,82 +19,77 @@ class Mailer {
$this->reply_to = $this->getReplyTo($reply_to);
$this->mailer_instance = $this->buildMailer();
}
function send($newsletter, $subscriber) {
$subscriber = $this->transformSubscriber($subscriber);
return $this->mailer_instance->send($newsletter, $subscriber);
}
function buildMailer() {
switch($this->mailer['method']) {
case 'AmazonSES':
$mailer_instance = new $this->mailer['class'](
$this->mailer['region'],
$this->mailer['access_key'],
$this->mailer['secret_key'],
$this->sender['from_name_email']
);
break;
case 'ElasticEmail':
$mailer_instance = new $this->mailer['class'](
$this->mailer['api_key'],
$this->sender['from_email'],
$this->sender['from_name']
);
break;
case 'MailGun':
$mailer_instance = new $this->mailer['class'](
$this->mailer['domain'],
$this->mailer['api_key'],
$this->sender['from_name_email']
);
break;
case 'MailPoet':
$mailer_instance = new $this->mailer['class'](
$this->mailer['mailpoet_api_key'],
$this->sender['from_email'],
$this->sender['from_name']
);
break;
case 'Mandrill':
$mailer_instance = new $this->mailer['class'](
$this->mailer['api_key'],
$this->sender['from_email'],
$this->sender['from_name']
);
break;
case 'SendGrid':
$mailer_instance = new $this->mailer['class'](
$this->mailer['api_key'],
$this->sender['from_email'],
$this->sender['from_name']
);
break;
case 'WPMail':
$mailer_instance = new $this->mailer['class'](
$this->sender['from_email'],
$this->sender['from_name']
);
break;
case 'SMTP':
$mailer_instance = new $this->mailer['class'](
$this->mailer['host'],
$this->mailer['port'],
$this->mailer['authentication'],
$this->mailer['login'],
$this->mailer['password'],
$this->mailer['encryption'],
$this->sender['from_email'],
$this->sender['from_name']
);
break;
default:
throw new \Exception(__('Mailing method does not exist.'));
break;
case 'AmazonSES':
$mailer_instance = new $this->mailer['class'](
$this->mailer['region'],
$this->mailer['access_key'],
$this->mailer['secret_key'],
$this->sender,
$this->reply_to
);
break;
case 'ElasticEmail':
$mailer_instance = new $this->mailer['class'](
$this->mailer['api_key'],
$this->sender,
$this->reply_to
);
break;
case 'MailGun':
$mailer_instance = new $this->mailer['class'](
$this->mailer['domain'],
$this->mailer['api_key'],
$this->sender,
$this->reply_to
);
break;
case 'MailPoet':
$mailer_instance = new $this->mailer['class'](
$this->mailer['mailpoet_api_key'],
$this->sender,
$this->reply_to
);
break;
case 'SendGrid':
$mailer_instance = new $this->mailer['class'](
$this->mailer['api_key'],
$this->sender,
$this->reply_to
);
break;
case 'PHPMail':
$mailer_instance = new $this->mailer['class'](
$this->sender,
$this->reply_to
);
break;
case 'SMTP':
$mailer_instance = new $this->mailer['class'](
$this->mailer['host'],
$this->mailer['port'],
$this->mailer['authentication'],
$this->mailer['login'],
$this->mailer['password'],
$this->mailer['encryption'],
$this->sender,
$this->reply_to
);
break;
default:
throw new \Exception(__('Mailing method does not exist.'));
break;
}
return $mailer_instance;
}
function getMailer($mailer = false) {
if(!$mailer) {
$mailer = Setting::getValue('mta', null);
@ -126,6 +121,9 @@ class Mailer {
);
}
}
if(!$reply_to['address']) {
$reply_to['address'] = $this->sender['from_email'];
}
return array(
'reply_to_name' => $reply_to['name'],
'reply_to_email' => $reply_to['address'],

View File

@ -13,25 +13,27 @@ class AmazonSES {
public $aws_termination_string;
public $hash_algorithm;
public $url;
public $from;
public $sender;
public $reply_to;
public $date;
public $date_without_time;
function __construct($region, $access_key, $secret_key, $from) {
function __construct($region, $access_key, $secret_key, $sender, $reply_to) {
$this->aws_access_key = $access_key;
$this->aws_secret_key = $secret_key;
$this->aws_region = $region;
$this->aws_endpoint = sprintf('email.%s.amazonaws.com', $region);
$this->aws_endpoint = sprintf('email.%s.amazonaws.com', $this->aws_region);
$this->aws_signing_algorithm = 'AWS4-HMAC-SHA256';
$this->aws_service = 'ses';
$this->aws_termination_string = 'aws4_request';
$this->hash_algorithm = 'sha256';
$this->url = 'https://' . $this->aws_endpoint;
$this->from = $from;
$this->sender = $sender;
$this->reply_to = $reply_to;
$this->date = gmdate('Ymd\THis\Z');
$this->date_without_time = gmdate('Ymd');
}
function send($newsletter, $subscriber) {
$result = wp_remote_post(
$this->url,
@ -42,15 +44,16 @@ class AmazonSES {
wp_remote_retrieve_response_code($result) === 200
);
}
function getBody($newsletter, $subscriber) {
$body = array(
'Action' => 'SendEmail',
'Version' => '2010-12-01',
'Source' => $this->from,
'Destination.ToAddresses.member.1' => $subscriber,
'Source' => $this->sender['from_name_email'],
'ReplyToAddresses.member.1' => $this->reply_to['reply_to_name_email'],
'Message.Subject.Data' => $newsletter['subject'],
'ReturnPath' => $this->from
'ReturnPath' => $this->sender['from_name_email'],
);
if(!empty($newsletter['body']['html'])) {
$body['Message.Body.Html.Data'] = $newsletter['body']['html'];
@ -60,7 +63,7 @@ class AmazonSES {
}
return $body;
}
function request($newsletter, $subscriber) {
$body = $this->getBody($newsletter, $subscriber);
return array(
@ -75,7 +78,7 @@ class AmazonSES {
'body' => urldecode(http_build_query($body))
);
}
function signRequest($body) {
$string_to_sign = $this->createStringToSign(
$this->getCredentialScope(),
@ -86,7 +89,7 @@ class AmazonSES {
$string_to_sign,
$this->getSigningKey()
);
return sprintf(
'%s Credential=%s/%s, SignedHeaders=host;x-amz-date, Signature=%s',
$this->aws_signing_algorithm,
@ -94,7 +97,7 @@ class AmazonSES {
$this->getCredentialScope(),
$signature);
}
function getCredentialScope() {
return sprintf(
'%s/%s/%s/%s',
@ -103,7 +106,7 @@ class AmazonSES {
$this->aws_service,
$this->aws_termination_string);
}
function getCanonicalRequest($body) {
return implode("\n", array(
'POST',
@ -116,7 +119,7 @@ class AmazonSES {
hash($this->hash_algorithm, urldecode(http_build_query($body)))
));
}
function createStringToSign($credential_scope, $canonical_request) {
return implode("\n", array(
$this->aws_signing_algorithm,
@ -125,7 +128,7 @@ class AmazonSES {
hash($this->hash_algorithm, $canonical_request)
));
}
function getSigningKey() {
$date_key = hash_hmac(
$this->hash_algorithm,

View File

@ -6,13 +6,13 @@ if(!defined('ABSPATH')) exit;
class ElasticEmail {
public $url = 'https://api.elasticemail.com/mailer/send';
public $api_key;
public $from_email;
public $from_name;
function __construct($api_key, $from_email, $from_name) {
public $sender;
public $reply_to;
function __construct($api_key, $sender, $reply_to) {
$this->api_key = $api_key;
$this->from_email = $from_email;
$this->from_name = $from_name;
$this->sender = $sender;
$this->reply_to = $reply_to;
}
function send($newsletter, $subscriber) {
@ -28,9 +28,11 @@ class ElasticEmail {
function getBody($newsletter, $subscriber) {
$body = array(
'api_key' => $this->api_key,
'from' => $this->from_email,
'from_name' => $this->from_name,
'to' => $subscriber,
'from' => $this->sender['from_email'],
'from_name' => $this->sender['from_name'],
'reply_to' => $this->reply_to['reply_to_email'],
'reply_to_name' => $this->reply_to['reply_to_name'],
'subject' => $newsletter['subject']
);
if(!empty($newsletter['body']['html'])) {

View File

@ -6,12 +6,14 @@ if(!defined('ABSPATH')) exit;
class MailGun {
public $url;
public $api_key;
public $from;
function __construct($domain, $api_key, $from) {
public $sender;
public $reply_to;
function __construct($domain, $api_key, $sender, $reply_to) {
$this->url = sprintf('https://api.mailgun.net/v3/%s/messages', $domain);
$this->api_key = $api_key;
$this->from = $from;
$this->sender = $sender;
$this->reply_to = $reply_to;
}
function send($newsletter, $subscriber) {
@ -27,8 +29,9 @@ class MailGun {
function getBody($newsletter, $subscriber) {
$body = array(
'from' => $this->from,
'to' => $subscriber,
'from' => $this->sender['from_name_email'],
'h:Reply-To' => $this->reply_to['reply_to_name_email'],
'subject' => $newsletter['subject']
);
if(!empty($newsletter['body']['html'])) {

View File

@ -6,26 +6,27 @@ if(!defined('ABSPATH')) exit;
class MailPoet {
public $url = 'https://bridge.mailpoet.com/api/messages';
public $api_key;
public $from_email;
public $from_name;
function __construct($api_key, $from_email, $from_name) {
public $sender;
public $reply_to;
function __construct($api_key, $sender, $reply_to) {
$this->api_key = $api_key;
$this->from_email = $from_email;
$this->from_name = $from_name;
$this->sender = $sender;
$this->reply_to = $reply_to;
}
function send($newsletter, $subscriber) {
$message_body = $this->getBody($newsletter, $subscriber);
$result = wp_remote_post(
$this->url,
$this->request($newsletter, $this->processSubscriber($subscriber))
$this->request($message_body)
);
return (
!is_wp_error($result) === true &&
wp_remote_retrieve_response_code($result) === 201
);
}
function processSubscriber($subscriber) {
preg_match('!(?P<name>.*?)\s<(?P<email>.*?)>!', $subscriber, $subscriber_data);
if(!isset($subscriber_data['email'])) {
@ -38,34 +39,51 @@ class MailPoet {
'name' => (isset($subscriber_data['name'])) ? $subscriber_data['name'] : ''
);
}
function getBody($newsletter, $subscriber) {
$body = array(
'to' => (array(
'address' => $subscriber['email'],
'name' => $subscriber['name']
)),
'from' => (array(
'address' => $this->from_email,
'name' => $this->from_name
)),
'subject' => $newsletter['subject']
);
if(!empty($newsletter['body']['html'])) {
$body['html'] = $newsletter['body']['html'];
}
if(!empty($newsletter['body']['text'])) {
$body['text'] = $newsletter['body']['text'];
$composeBody = function ($newsletter, $subscriber) {
$body = array(
'to' => (array(
'address' => $subscriber['email'],
'name' => $subscriber['name']
)),
'from' => (array(
'address' => $this->sender['from_email'],
'name' => $this->sender['from_name']
)),
'reply_to' => (array(
'address' => $this->reply_to['reply_to_email'],
'name' => $this->reply_to['reply_to_name']
)),
'subject' => $newsletter['subject']
);
if(!empty($newsletter['body']['html'])) {
$body['html'] = $newsletter['body']['html'];
}
if(!empty($newsletter['body']['text'])) {
$body['text'] = $newsletter['body']['text'];
}
return $body;
};
if(is_array($newsletter) && is_array($subscriber)) {
$body = array();
for($record = 0; $record < count($newsletter); $record++) {
$body[] = $composeBody(
$newsletter[$record],
$this->processSubscriber($subscriber[$record])
);
}
} else {
$body[] = $composeBody($newsletter, $this->processSubscriber($subscriber));
}
return $body;
}
function auth() {
return 'Basic ' . base64_encode('api:' . $this->api_key);
}
function request($newsletter, $subscriber) {
$body = array($this->getBody($newsletter, $subscriber));
function request($body) {
return array(
'timeout' => 10,
'httpversion' => '1.0',
@ -74,7 +92,7 @@ class MailPoet {
'Content-Type' => 'application/json',
'Authorization' => $this->auth()
),
'body' => $body
'body' => json_encode($body)
);
}
}

View File

@ -1,75 +0,0 @@
<?php
namespace MailPoet\Mailer\Methods;
if(!defined('ABSPATH')) exit;
class Mandrill {
public $url = 'https://mandrillapp.com/api/1.0/messages/send.json';
public $api_key;
public $from_email;
public $from_name;
function __construct($api_key, $from_email, $from_name) {
$this->api_key = $api_key;
$this->from_name = $from_name;
$this->from_email = $from_email;
}
function send($newsletter, $subscriber) {
$result = wp_remote_post(
$this->url,
$this->request($newsletter, $this->processSubscriber($subscriber))
);
return (
!is_wp_error($result) === true &&
!preg_match('!invalid!', $result['body']) === true &&
wp_remote_retrieve_response_code($result) === 200
);
}
function processSubscriber($subscriber) {
preg_match('!(?P<name>.*?)\s<(?P<email>.*?)>!', $subscriber, $subscriber_data);
if(!isset($subscriber_data['email'])) {
$subscriber_data = array(
'email' => $subscriber,
);
}
return array(
'email' => $subscriber_data['email'],
'name' => (isset($subscriber_data['name'])) ? $subscriber_data['name'] : ''
);
}
function getBody($newsletter, $subscriber) {
$body = array(
'key' => $this->api_key,
'message' => array(
'from_email' => $this->from_email,
'from_name' => $this->from_name,
'to' => array($subscriber),
'subject' => $newsletter['subject']
),
'async' => false,
);
if(!empty($newsletter['body']['html'])) {
$body['message']['html'] = $newsletter['body']['html'];
}
if(!empty($newsletter['body']['text'])) {
$body['message']['text'] = $newsletter['body']['text'];
}
return $body;
}
function request($newsletter, $subscriber) {
$body = $this->getBody($newsletter, $subscriber);
return array(
'timeout' => 10,
'httpversion' => '1.0',
'method' => 'POST',
'headers' => array(
'Content-Type' => 'application/json'
),
'body' => json_encode($body)
);
}
}

View File

@ -0,0 +1,64 @@
<?php
namespace MailPoet\Mailer\Methods;
if(!defined('ABSPATH')) exit;
class PHPMail {
public $sender;
public $reply_to;
public $mailer;
function __construct($sender, $reply_to) {
$this->sender = $sender;
$this->reply_to = $reply_to;
$this->mailer = $this->buildMailer();
}
function send($newsletter, $subscriber) {
try {
$message = $this->createMessage($newsletter, $subscriber);
$result = $this->mailer->send($message);
} catch(\Exception $e) {
$result = false;
}
return ($result === 1);
}
function buildMailer() {
$transport = \Swift_SmtpTransport::newInstance();
$transport->setTimeout(10);
return \Swift_Mailer::newInstance($transport);
}
function createMessage($newsletter, $subscriber) {
$message = \Swift_Message::newInstance()
->setTo($this->processSubscriber($subscriber))
->setFrom(array(
$this->sender['from_email'] => $this->sender['from_name']
))
->setReplyTo(array(
$this->reply_to['reply_to_email'] => $this->reply_to['reply_to_name']
))
->setSubject($newsletter['subject']);
if(!empty($newsletter['body']['html'])) {
$message = $message->setBody($newsletter['body']['html'], 'text/html');
}
if(!empty($newsletter['body']['text'])) {
$message = $message->addPart($newsletter['body']['text'], 'text/plain');
}
return $message;
}
function processSubscriber($subscriber) {
preg_match('!(?P<name>.*?)\s<(?P<email>.*?)>!', $subscriber, $subscriber_data);
if(!isset($subscriber_data['email'])) {
$subscriber_data = array(
'email' => $subscriber,
);
}
return array(
$subscriber_data['email'] =>
(isset($subscriber_data['name'])) ? $subscriber_data['name'] : ''
);
}
}

View File

@ -10,21 +10,21 @@ class SMTP {
public $login;
public $password;
public $encryption;
public $from_name;
public $from_email;
public $sender;
public $reply_to;
public $mailer;
function __construct(
$host, $port, $authentication, $login = null, $password = null, $encryption,
$from_email, $from_name) {
$sender, $reply_to) {
$this->host = $host;
$this->port = $port;
$this->authentication = $authentication;
$this->login = $login;
$this->password = $password;
$this->encryption = $encryption;
$this->from_name = $from_name;
$this->from_email = $from_email;
$this->sender = $sender;
$this->reply_to = $reply_to;
$this->mailer = $this->buildMailer();
}
@ -50,11 +50,15 @@ class SMTP {
return \Swift_Mailer::newInstance($transport);
}
function createMessage($newsletter, $subscriber) {
$message = \Swift_Message::newInstance()
->setFrom(array($this->from_email => $this->from_name))
->setTo($this->processSubscriber($subscriber))
->setFrom(array(
$this->sender['from_email'] => $this->sender['from_name']
))
->setReplyTo(array(
$this->reply_to['reply_to_email'] => $this->reply_to['reply_to_name']
))
->setSubject($newsletter['subject']);
if(!empty($newsletter['body']['html'])) {
$message = $message->setBody($newsletter['body']['html'], 'text/html');

View File

@ -6,13 +6,13 @@ if(!defined('ABSPATH')) exit;
class SendGrid {
public $url = 'https://api.sendgrid.com/api/mail.send.json';
public $api_key;
public $from_email;
public $from_name;
public $sender;
public $reply_to;
function __construct($api_key, $from_email, $from_name) {
function __construct($api_key, $sender, $reply_to) {
$this->api_key = $api_key;
$this->from_email = $from_email;
$this->from_name = $from_name;
$this->sender = $sender;
$this->reply_to = $reply_to;
}
function send($newsletter, $subscriber) {
@ -20,10 +20,11 @@ class SendGrid {
$this->url,
$this->request($newsletter, $subscriber)
);
$result_body = json_decode($result['body'], true);
return (
!is_wp_error($result) === true &&
!preg_match('!invalid!', $result['body']) === true &&
!isset(json_decode($result['body'], true)['errors']) === true &&
!isset($result_body['errors']) === true &&
wp_remote_retrieve_response_code($result) === 200
);
}
@ -31,8 +32,9 @@ class SendGrid {
function getBody($newsletter, $subscriber) {
$body = array(
'to' => $subscriber,
'from' => $this->from_email,
'from_name' => $this->from_name,
'from' => $this->sender['from_email'],
'fromname' => $this->sender['from_name'],
'replyto' => $this->reply_to['reply_to_email'],
'subject' => $newsletter['subject']
);
if(!empty($newsletter['body']['html'])) {
@ -57,7 +59,7 @@ class SendGrid {
'headers' => array(
'Authorization' => $this->auth()
),
'body' => urldecode(http_build_query($body))
'body' => http_build_query($body)
);
}
}

View File

@ -1,63 +0,0 @@
<?php
namespace MailPoet\Mailer\Methods;
require_once(ABSPATH . 'wp-includes/pluggable.php');
if(!defined('ABSPATH')) exit;
class WPMail {
public $from_email;
public $from_name;
public $filters = array(
'wp_mail_from' => 'setFromEmail',
'wp_mail_from_name' => 'setFromName',
'wp_mail_content_type' => 'setContentType'
);
function __construct($from_email, $from_name) {
$this->from_email = $from_email;
$this->from_name = $from_name;
}
function addFilters() {
foreach($this->filters as $filter => $method) {
add_filter($filter, array(
$this,
$method
));
}
}
function removeFilters() {
foreach($this->filters as $filter => $method) {
remove_filter($filter, array(
$this,
$method
));
}
}
function setFromEmail() {
return $this->from_email;
}
function setFromName() {
return $this->from_name;
}
function setContentType() {
return 'text/html';
}
function send($newsletter, $subscriber) {
$this->addFilters();
$result = wp_mail(
$subscriber, $newsletter['subject'],
(!empty($newsletter['body']['html'])) ?
$newsletter['body']['html'] :
$newsletter['body']['text']
);
$this->removeFilters();
return ($result === true);
}
}

View File

@ -19,20 +19,24 @@ class CustomField extends Model {
function asArray() {
$model = parent::asArray();
$model['params'] = (
is_serialized($this->params)
? unserialize($this->params)
: $this->params
);
if(isset($model['params'])) {
$model['params'] = (
is_array($this->params)
? $this->params
: unserialize($this->params)
);
}
return $model;
}
function save() {
if(is_null($this->params)) {
$this->params = array();
}
$this->set('params', (
is_serialized($this->params)
? $this->params
: serialize($this->params)
is_array($this->params)
? serialize($this->params)
: $this->params
));
return parent::save();
}
@ -54,7 +58,7 @@ class CustomField extends Model {
}
// set name as label by default
if(empty($data['params']['label'])) {
if(empty($data['params']['label']) && isset($data['name'])) {
$data['params']['label'] = $data['name'];
}
@ -66,12 +70,6 @@ class CustomField extends Model {
$custom_field->set($data);
}
try {
$custom_field->save();
return $custom_field;
} catch(Exception $e) {
return $custom_field->getValidationErrors();
}
return false;
return $custom_field->save();
}
}

View File

@ -4,7 +4,10 @@ namespace MailPoet\Models;
if(!defined('ABSPATH')) exit;
class Model extends \Sudzy\ValidModel {
protected $_errors;
function __construct() {
$this->_errors = array();
$customValidators = new CustomValidator();
parent::__construct($customValidators->init());
}
@ -13,16 +16,37 @@ class Model extends \Sudzy\ValidModel {
return parent::create();
}
function getErrors() {
if(empty($this->_errors)) {
return false;
} else {
return $this->_errors;
}
}
function setError($error = '') {
if(!empty($error)) {
if(is_array($error)) {
$this->_errors = array_merge($this->_errors, $error);
$this->_errors = array_unique($this->_errors);
} else {
$this->_errors[] = $error;
}
}
}
function save() {
$this->setTimestamp();
try {
parent::save();
return true;
} catch (\Sudzy\ValidationException $e) {
return array_unique($e->getValidationErrors());
} catch (\PDOException $e) {
return $e->getMessage();
} catch(\Sudzy\ValidationException $e) {
$this->setError($e->getValidationErrors());
} catch(\PDOException $e) {
$this->setError($e->getMessage());
} catch(\Exception $e) {
$this->setError($e->getMessage());
}
return $this;
}
function trash() {

View File

@ -8,6 +8,10 @@ class Newsletter extends Model {
function __construct() {
parent::__construct();
$this->addValidations('type', array(
'required' => __('You need to specify a type.')
));
}
function save() {
@ -15,9 +19,23 @@ class Newsletter extends Model {
$this->set_expr('deleted_at', 'NULL');
}
$this->set('body',
is_array($this->body)
? json_encode($this->body)
: $this->body
);
return parent::save();
}
function asArray() {
$model = parent::asArray();
if(isset($model['body'])) {
$model['body'] = json_decode($model['body'], true);
}
return $model;
}
function delete() {
// delete all relations to segments
NewsletterSegment::where('newsletter_id', $this->id)->deleteMany();

View File

@ -17,6 +17,14 @@ class NewsletterTemplate extends Model {
));
}
function asArray() {
$template = parent::asArray();
if(isset($template['body'])) {
$template['body'] = json_decode($template['body'], true);
}
return $template;
}
static function createOrUpdate($data = array()) {
$template = false;
@ -32,16 +40,7 @@ class NewsletterTemplate extends Model {
$template->set($data);
}
$saved = $template->save();
if($saved === true) {
return true;
} else {
$errors = $template->getValidationErrors();
if(!empty($errors)) {
return $errors;
}
}
return false;
$template->save();
return $template;
}
}

View File

@ -164,8 +164,7 @@ class Segment extends Model {
$segment->set($data);
}
$segment->save();
return $segment;
return $segment->save();
}
static function getPublic() {

View File

@ -10,8 +10,7 @@ class Setting extends Model {
parent::__construct();
$this->addValidations('name', array(
'required' => 'name_is_blank',
'isString' => 'name_is_not_string'
'required' => __('You need to specify a name.')
));
}
@ -55,10 +54,11 @@ class Setting extends Model {
$value = serialize($value);
}
return Setting::createOrUpdate(array(
$setting = Setting::createOrUpdate(array(
'name' => $key,
'value' => $value
));
return ($setting->id() > 0 && $setting->getErrors() === false);
} else {
$main_key = array_shift($keys);
@ -101,8 +101,7 @@ class Setting extends Model {
}
public static function createOrUpdate($model) {
$exists = self::where('name', $model['name'])
->find_one();
$exists = self::where('name', $model['name'])->findOne();
if($exists === false) {
$new_model = self::create();

View File

@ -237,6 +237,16 @@ class Subscriber extends Model {
}
}
// 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 = static::create();
$subscriber->hydrate($data);
@ -244,10 +254,54 @@ class Subscriber extends Model {
$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);

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

@ -0,0 +1,69 @@
<?php
namespace MailPoet\Newsletter;
use MailPoet\Newsletter\Editor\Transformer;
if(!defined('ABSPATH')) exit;
class AutomatedLatestContent {
function getPosts($args) {
$parameters = array(
'posts_per_page' => (isset($args['amount'])) ? (int) $args['amount'] : 10,
'post_type' => (isset($args['contentType'])) ? $args['contentType'] : 'post',
'post_status' => (isset($args['postStatus'])) ? $args['postStatus'] : 'publish',
'orderby' => 'date',
'order' => ($args['sortBy'] === 'newest') ? 'DESC' : 'ASC',
);
if(isset($args['search'])) {
$parameters['s'] = $args['search'];
}
if(isset($args['posts']) && is_array($args['posts'])) {
$parameters['post__in'] = $args['posts'];
}
$parameters['tax_query'] = $this->constructTaxonomiesQuery($args);
return get_posts($parameters);
}
function transformPosts($args, $posts) {
$transformer = new Transformer($args);
return $transformer->transform($posts);
}
function constructTaxonomiesQuery($args) {
$taxonomies_query = array();
if(isset($args['terms']) && is_array($args['terms'])) {
// Add filtering by tags and categories
$tags = array();
$categories = array();
foreach($args['terms'] as $term) {
if($term['taxonomy'] === 'category') {
$categories[] = $term['id'];
} else if($term['taxonomy'] === 'post_tag') $tags[] = $term['id'];
}
$taxonomies = array(
'post_tag' => $tags,
'category' => $categories
);
foreach($taxonomies as $taxonomy => $terms) {
if(!empty($terms)) {
$tax = array(
'taxonomy' => $taxonomy,
'field' => 'id',
'terms' => $terms,
);
if($args['inclusionType'] === 'exclude') $tax['operator'] = 'NOT IN';
$taxonomies_query[] = $tax;
}
}
if(!empty($taxonomies_query)) {
// With exclusion we want to use 'AND', because we want posts that
// don't have excluded tags/categories. But with inclusion we want to
// use 'OR', because we want posts that have any of the included
// tags/categories
$taxonomies_query['relation'] = ($args['inclusionType'] === 'exclude') ? 'AND' : 'OR';
return $taxonomies_query;
}
}
return $taxonomies_query;
}
}

View File

@ -62,7 +62,7 @@ class PostTransformer {
// get attachment data (src, width, height)
$image_info = wp_get_attachment_image_src(
$thumbnail_id,
'single-post-thumbnail'
'mailpoet_newsletter_max'
);
// get alt text

View File

@ -0,0 +1,12 @@
<?php
namespace MailPoet\Newsletter\Renderer\Blocks;
class AutomatedLatestContent {
static function render($element, $column_count) {
$ALC = new \MailPoet\Newsletter\AutomatedLatestContent();
$posts = $ALC->getPosts($element);
$transformed_posts = array('blocks' => $ALC->transformPosts($element, $posts));
$renderer = new Renderer();
return $renderer->render($transformed_posts, $column_count);
}
}

View File

@ -5,21 +5,23 @@ use MailPoet\Newsletter\Renderer\StylesHelper;
class Footer {
static function render($element) {
if(isset($element['styles']['link'])) {
$element['text'] = str_replace(
'<a',
'<a style="'
. StylesHelper::getStyles($element['styles'], 'link')
. '"', $element['text']
);
}
$element['text'] = preg_replace('/\n/', '<br /><br />', $element['text']);
$element['text'] = preg_replace('/(<\/?p>)/', '', $element['text']);
$element['text'] = preg_replace('/(<\/?p.*?>)/i', '', $element['text']);
$DOM_parser = new \pQuery();
$DOM = $DOM_parser->parseStr($element['text']);
if(isset($element['styles']['link'])) {
$links = $DOM->query('a');
if($links->count()) {
foreach($links as $link) {
$link->style = StylesHelper::getStyles($element['styles'], 'link');
}
}
}
$template = '
<tr>
<td class="mailpoet_padded_header_footer mailpoet_footer" bgcolor="' . $element['styles']['block']['backgroundColor'] . '"
style="' . StylesHelper::getBlockStyles($element) . StylesHelper::getStyles($element['styles'], 'text') . '">
' . $element['text'] . '
' . $DOM->html() . '
</td>
</tr>';
return $template;

View File

@ -5,21 +5,23 @@ use MailPoet\Newsletter\Renderer\StylesHelper;
class Header {
static function render($element) {
if(isset($element['styles']['link'])) {
$element['text'] = str_replace(
'<a',
'<a style="'
. StylesHelper::getStyles($element['styles'], 'link')
. '"', $element['text']
);
}
$element['text'] = preg_replace('/\n/', '<br /><br />', $element['text']);
$element['text'] = preg_replace('/(<\/?p>)/', '', $element['text']);
$element['text'] = preg_replace('/(<\/?p.*?>)/', '', $element['text']);
$DOM_parser = new \pQuery();
$DOM = $DOM_parser->parseStr($element['text']);
if(isset($element['styles']['link'])) {
$links = $DOM->query('a');
if($links->count()) {
foreach($links as $link) {
$link->style = StylesHelper::getStyles($element['styles'], 'link');
}
}
}
$template = '
<tr>
<td class="mailpoet_padded_header_footer mailpoet_header" bgcolor="' . $element['styles']['block']['backgroundColor'] . '"
style="' . StylesHelper::getBlockStyles($element) . StylesHelper::getStyles($element['styles'], 'text') . '">
' . $element['text'] . '
' . $DOM->html() . '
</td>
</tr>';
return $template;

View File

@ -6,10 +6,9 @@ class Text {
$html = $element['text'];
$html = self::convertBlockquotesToTables($html);
$html = self::convertParagraphsToTables($html);
$html = self::addLineBreakAfterTags($html);
$html = self::styleLists($html);
$html = self::styleHeadings($html);
$html = self::removeLastElementBreakLine($html);
$html = self::addLineBreakAfterTags($html);
$template = '
<tr>
<td class="mailpoet_text mailpoet_padded" valign="top" style="word-break:break-word;word-wrap:break-word;">
@ -19,42 +18,26 @@ class Text {
return $template;
}
static function convertParagraphsToTables($html) {
$html = preg_replace('/<p>(.*?)<\/p>/', '
<table width="100%" cellpadding="0" cellspacing="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0">
<tr>
<td class="mailpoet_paragraph" style="word-break:break-word;word-wrap:break-word;">
$1
<br /><br />
</td>
</tr>
</table>'
, $html);
$html = preg_replace('/<p style=\"(.*)\">(.*?)<\/p>/', '
<table width="100%" cellpadding="0" cellspacing="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0">
<tr>
<td class="mailpoet_paragraph" style="word-break:break-word;word-wrap:break-word;$1">
$2
<br /><br />
</td>
</tr>
</table>'
, $html);
return $html;
}
static function removeLastElementBreakLine($html) {
return preg_replace('/<br\/>([^<br\/>]*)$/s', '', $html);
}
static function addLineBreakAfterTags($html) {
return preg_replace('/(<\/(ul|ol|h\d)>)/', '$1<br />', $html);
}
static function convertBlockquotesToTables($html) {
$template = '
<table width="100%" cellpadding="0" cellspacing="0" border="0">
$DOM_parser = new \pQuery();
$DOM = $DOM_parser->parseStr($html);
$blockquotes = $DOM->query('blockquote');
if(!$blockquotes->count()) return $html;
foreach($blockquotes as $blockquote) {
$paragraphs = $blockquote->query('p', 0);
foreach($paragraphs as $index => $paragraph) {
$contents[] = $paragraph->html();
if($index + 1 < $paragraphs->count()) $contents[] = '<br />';
$paragraph->remove();
}
$paragraph->remove();
$blockquote->setTag('table');
$blockquote->addClass('mailpoet_blockquote');
$blockquote->width = '100%';
$blockquote->spacing = 0;
$blockquote->border = 0;
$blockquote->cellpadding = 0;
$blockquote->html('
<tbody>
<tr>
<td width="2" bgcolor="#565656"></td>
@ -63,48 +46,90 @@ class Text {
<table style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0">
<tr>
<td class="mailpoet_blockquote">
$1
' . implode('', $contents) . '
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
<br/>';
preg_match('/<blockquote>.*?<\/blockquote>/s', $html, $blockquotes);
foreach($blockquotes as $index => $blockquote) {
$blockquote = preg_replace('/<\/p>\n<p>/', '<br/><br/>', $blockquote);
$blockquote = preg_replace('/<\/?p>/', '', $blockquote);
$blockquote = preg_replace(
'/<blockquote>(.*?)<\/blockquote>/s',
$template,
$blockquote
);
$html = preg_replace(
'/' . preg_quote($blockquotes[$index], '/') . '/',
$blockquote,
$html
</tbody>'
);
}
return $html;
return $DOM->__toString();
}
static function styleHeadings($html) {
return preg_replace(
'/<(h[1-6])(?:.+style=\"(.*)?\")?>/',
'<$1 style="margin:0;font-style:normal;font-weight:normal;$2">',
$html
);
static function convertParagraphsToTables($html) {
$DOM_parser = new \pQuery();
$DOM = $DOM_parser->parseStr($html);
$paragraphs = $DOM->query('p');
if(!$paragraphs->count()) return $html;
foreach($paragraphs as $paragraph) {
// remove empty paragraphs
if(!trim($paragraph->html())) {
$paragraph->remove();
continue;
}
$style = $paragraph->style;
$contents = $paragraph->html();
$paragraph->setTag('table');
$paragraph->style = 'border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;';
$paragraph->width = '100%';
$paragraph->cellpadding = 0;
$paragraph->html('
<tr>
<td class="mailpoet_paragraph" style="word-break:break-word;word-wrap:break-word;' . $style . '">
' . $contents . '
<br /><br />
</td>
</tr>'
);
}
return $DOM->__toString();
}
static function styleLists($html) {
$html = preg_replace(
'/<(ul|ol)>/',
'<$1 class="mailpoet_paragraph" style="padding-top:0;padding-bottom:0;margin-top:0;margin-bottom:0;">',
$html
);
$html = preg_replace('/<li>/', '<li class="mailpoet_paragraph">', $html);
return $html;
$DOM_parser = new \pQuery();
$DOM = $DOM_parser->parseStr($html);
$lists = $DOM->query('ol, ul, li');
if(!$lists->count()) return $html;
foreach($lists as $list) {
if($list->tag === 'li') {
$list->class = 'mailpoet_paragraph';
} else {
$list->class = 'mailpoet_paragraph';
$list->style .= 'padding-top:0;padding-bottom:0;margin-top:0;margin-bottom:0;';
}
}
return $DOM->__toString();
}
static function styleHeadings($html) {
$DOM_parser = new \pQuery();
$DOM = $DOM_parser->parseStr($html);
$headings = $DOM->query('h1, h2, h3, h4');
if(!$headings->count()) return $html;
foreach($headings as $heading) {
$heading->style .= 'margin:0;font-style:normal;font-weight:normal;';
}
return $DOM->__toString();
}
static function addLineBreakAfterTags($html) {
$DOM_parser = new \pQuery();
$DOM = $DOM_parser->parseStr($html);
$tags = $DOM->query('ul, ol, h1, h2, h3, h4, table.mailpoet_blockquote');
if(!$tags->count()) return $html;
foreach($tags as $tag) {
$tag->parent->insertChild(
array(
'tag_name' => 'br',
'self_close' => true,
'attributes' => array()
),
$tag->index() + 1
);
}
// remove last line break
return preg_replace('/(^)?(<br.*?\/?>)+$/i', '', $DOM->__toString());
}
}

View File

@ -35,7 +35,11 @@ class Renderer {
$newsletter_body
));
$template = $this->inlineCSSStyles($template);
return $this->postProcessTemplate($template);
$template = $this->postProcessTemplate($template);
return array(
'html' => $template,
'text' => $this->renderTextVersion($template)
);
}
function renderBody($content) {
@ -65,7 +69,7 @@ class Renderer {
$selector = 'h3';
break;
case 'text':
$selector = '.mailpoet_paragraph, .mailpoet_blockquote';
$selector = '.mailpoet_paragraph, td.mailpoet_blockquote';
break;
case 'body':
$selector = 'body, .mailpoet-wrapper';
@ -107,8 +111,8 @@ class Renderer {
}
function renderTextVersion($template) {
// TODO: add text rendering
return $template;
$template = mb_convert_encoding($template, 'HTML-ENTITIES', 'UTF-8');
return \Html2Text\Html2Text::convert($template);
}
function postProcessTemplate($template) {
@ -118,7 +122,6 @@ class Renderer {
$template->html(
str_replace('!important', '', $template->html())
);
// TODO: return array with html and text body
return $DOM->__toString();
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace MailPoet\Newsletter\Shortcodes\Categories;
class Date {
/*
{
text: '<%= __('Current day of the month number') %>',
shortcode: 'date:d',
},
{
text: '<%= __('Current day of the month in ordinal, ie. 2nd, 3rd, etc.') %>',
shortcode: 'date:dordinal',
},
{
text: '<%= __('Full name of current day') %>',
shortcode: 'date:dtext',
},
{
text: '<%= __('Current month number') %>',
shortcode: 'date:m',
},
{
text: '<%= __('Full name of current month') %>',
shortcode: 'date:mtext',
},
{
text: '<%= __('Year') %>',
shortcode: 'date:y',
}
*/
static function process($action) {
$date = new \DateTime('now');
$actions = array(
'd' => $date->format('d'),
'dordinal' => $date->format('dS'),
'dtext' => $date->format('D'),
'm' => $date->format('m'),
'mtext' => $date->format('F'),
'y' => $date->format('Y')
);
return (isset($actions[$action])) ? $actions[$action] : false;
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace MailPoet\Newsletter\Shortcodes\Categories;
require_once(ABSPATH . 'wp-includes/pluggable.php');
class Link {
/*
{
text: '<%= __('Unsubscribe link') %>',
shortcode: 'global:unsubscribe',
},
{
text: '<%= __('Edit subscription page link') %>',
shortcode: 'global:manage',
},
{
text: '<%= __('View in browser link') %>',
shortcode: 'global:browser',
}
*/
static function process($action) {
// TODO: implement
$actions = array(
'unsubscribe' => '',
'manage' => '',
'browser' => ''
);
return (isset($actions[$action])) ? $actions[$action] : false;
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace MailPoet\Newsletter\Shortcodes\Categories;
class Newsletter {
/*
{
text: '<%= __('Newsletter Subject') %>',-
shortcode: 'newsletter:subject',
},
{
text: '<%= __('Total number of posts or pages') %>',
shortcode: 'newsletter:total',
},
{
text: '<%= __('Latest post title') %>',
shortcode: 'newsletter:post_title',
},
{
text: '<%= __('Issue number') %>',
shortcode: 'newsletter:number',
}
*/
static function process($action, $default_value = false, $newsletter) {
if(is_object($newsletter)) {
$newsletter = $newsletter->asArray();
}
switch($action) {
case 'subject':
return ($newsletter) ? $newsletter['subject'] : false;
case 'total':
$posts = wp_count_posts();
return $posts->publish;
case 'post_title':
$post = wp_get_recent_posts(array('numberposts' => 1));
return (isset($post[0])) ? $post[0]['post_title'] : false;
case 'number':
// TODO: implement
return;
default:
return false;
}
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace MailPoet\Newsletter\Shortcodes\Categories;
use MailPoet\Models\Subscriber;
require_once(ABSPATH . 'wp-includes/pluggable.php');
class User {
/*
{
text: '<%= __('First Name') %>',
shortcode: 'user:firstname | default:reader',
},
{
text: '<%= __('Last Name') %>',
shortcode: 'user:lastname | default:reader',
},
{
text: '<%= __('Email Address') %>',
shortcode: 'user:email',
},
{
text: '<%= __('Wordpress user display name') %>',
shortcode: 'user:displayname | default:member',
},
{
text: '<%= __('Total of subscribers') %>',
shortcode: 'user:count',
}
*/
static function process($action, $default_value, $newsletter = false, $subscriber) {
if(is_object($subscriber)) {
$subscriber = $subscriber->asArray();
}
switch($action) {
case 'firstname':
return ($subscriber) ? $subscriber['first_name'] : $default_value;
case 'lastname':
return ($subscriber) ? $subscriber['last_name'] : $default_value;
case 'email':
return ($subscriber) ? $subscriber['email'] : false;
case 'displayname':
if($subscriber && $subscriber['wp_user_id']) {
$wp_user = get_userdata($subscriber['wp_user_id']);
return $wp_user->user_login;
};
return $default_value;
case 'count':
return Subscriber::count();
default:
return false;
}
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace MailPoet\Newsletter\Shortcodes;
class Shortcodes {
public $rendered_newsletter;
public $newsletter;
public $subscriber;
function __construct(
$rendered_newsletter,
$newsletter = false,
$subscriber = false) {
$this->rendered_newsletter = $rendered_newsletter;
$this->newsletter = $newsletter;
$this->subscriber = $subscriber;
}
function extract() {
preg_match_all('/\[(?:\w+):.*?\]/', $this->rendered_newsletter, $shortcodes);
return array_unique($shortcodes[0]);
}
function process($shortcodes) {
$processed_shortcodes = array_map(
function ($shortcode) {
// TODO: discuss renaming "global". It is a reserved name in PHP.
if($shortcode === 'global') $shortcode = 'link';
preg_match(
'/\[(?P<type>\w+):(?P<action>\w+)(?:.*?default:(?P<default>.*?))?\]/',
$shortcode,
$shortcode_details
);
$shortcode_class =
__NAMESPACE__ . '\\Categories\\' . ucfirst($shortcode_details['type']);
if(!class_exists($shortcode_class)) return false;
return $shortcode_class::process(
$shortcode_details['action'],
isset($shortcode_details['default'])
? $shortcode_details['default'] : false,
$this->newsletter,
$this->subscriber
);
}, $shortcodes);
return array_filter($processed_shortcodes);
}
function replace() {
$shortcodes = $this->extract($this->rendered_newsletter);
$processed_shortcodes = $this->process($shortcodes);
$shortcodes = array_intersect_key($shortcodes, $processed_shortcodes);
return str_replace($shortcodes, $processed_shortcodes, $this->rendered_newsletter);
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace MailPoet\Router;
if(!defined('ABSPATH')) exit;
class AutomatedLatestContent {
public $ALC;
function __construct() {
$this->ALC = new \MailPoet\Newsletter\AutomatedLatestContent();
}
function getPostTypes() {
return get_post_types(array(), 'objects');
}
function getTaxonomies($args) {
$post_type = (isset($args['postType'])) ? $args['postType'] : 'post';
return get_object_taxonomies($post_type, 'objects');
}
function getTerms($args) {
$taxonomies = (isset($args['taxonomies'])) ? $args['taxonomies'] : array();
$search = (isset($args['search'])) ? $args['search'] : '';
$limit = (isset($args['limit'])) ? (int)$args['limit'] : 10;
$page = (isset($args['page'])) ? (int)$args['page'] : 1;
return get_terms(
$taxonomies,
array(
'hide_empty' => false,
'search' => $search,
'number' => $limit,
'offset' => $limit * ($page - 1)
)
);
}
function getPosts($args) {
return $this->ALC->getPosts($args);
}
function getTransformedPosts($args) {
$posts = $this->ALC->getPosts($args);
return $this->ALC->transformPosts($args, $posts);
}
}

View File

@ -2,7 +2,7 @@
namespace MailPoet\Router;
use Carbon\Carbon;
use MailPoet\Cron\Daemon;
use MailPoet\Cron\CronHelper;
use MailPoet\Cron\Supervisor;
use MailPoet\Models\Setting;
@ -10,35 +10,27 @@ if(!defined('ABSPATH')) exit;
class Cron {
function start() {
$supervisor = new Supervisor($forceStart = true);
wp_send_json(
array(
'result' => $supervisor->checkDaemon() ? true : false
)
);
$supervisor = new Supervisor($force_run = true);
return $supervisor->checkDaemon();
}
function stop() {
$daemon = new Daemon();
if(!$daemon->daemon ||
$daemon->daemon['status'] !== 'started'
) {
$daemon = CronHelper::getDaemon();
if(!$daemon || $daemon['status'] !== 'started') {
$result = false;
} else {
$daemon->daemon['status'] = 'stopping';
$result = $daemon->saveDaemon($daemon->daemon);
$daemon['status'] = 'stopping';
$result = CronHelper::saveDaemon($daemon);
}
wp_send_json(
array(
'result' => $result
)
return array(
'result' => $result
);
}
function getStatus() {
$daemon = Setting::where('name', 'cron_daemon')
->findOne();
wp_send_json(
return (
($daemon) ?
array_merge(
array(

View File

@ -14,56 +14,46 @@ class CustomFields {
return $custom_field->asArray();
}, $collection);
wp_send_json($custom_fields);
return $custom_fields;
}
function delete($id) {
$result = false;
$custom_field = CustomField::findOne($id);
if($custom_field !== false) {
if($custom_field === false or !$custom_field->id()) {
return array('result' => false);
} else {
$custom_field->delete();
$result = true;
}
wp_send_json($result);
return array(
'result' => true,
'field' => $custom_field->asArray()
);
}
}
function save($data = array()) {
$custom_field = CustomField::createOrUpdate($data);
$errors = $custom_field->getErrors();
if($custom_field === false) {
$result = array(
if(!empty($errors)) {
return array(
'result' => false,
'errors' => array(
__('The custom field could not be created.')
)
'errors' => $errors
);
} else {
$errors = $custom_field->getValidationErrors();
if(!empty($errors)) {
$result = array(
'result' => false,
'errors' => $errors
);
} else {
$result = array(
'result' => true,
'field' => $custom_field->asArray()
);
}
return array(
'result' => true,
'field' => $custom_field->asArray()
);
}
wp_send_json($result);
}
function get($id) {
$custom_field = CustomField::findOne($id);
if($custom_field === false) {
wp_send_json(false);
return false;
} else {
$custom_field = $custom_field->asArray();
wp_send_json($custom_field);
return $custom_field->asArray();
}
}
}

View File

@ -11,15 +11,12 @@ class Forms {
function __construct() {
}
function get($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : 0);
function get($id = false) {
$form = Form::findOne($id);
if($form === false) {
wp_send_json(false);
return false;
} else {
$form = $form->asArray();
wp_send_json($form);
return $form->asArray();
}
}
@ -47,12 +44,7 @@ class Forms {
);
}
wp_send_json($listing_data);
}
function getAll() {
$collection = Form::findArray();
wp_send_json($collection);
return $listing_data;
}
function create() {
@ -63,7 +55,7 @@ class Forms {
array(
'id' => 'email',
'name' => __('Email'),
'type' => 'input',
'type' => 'text',
'static' => true,
'params' => array(
'label' => __('Email'),
@ -88,24 +80,23 @@ class Forms {
)
);
$form = Form::createOrUpdate($form_data);
if($form !== false && $form->id()) {
wp_send_json(
admin_url('admin.php?page=mailpoet-form-editor&id='.$form->id())
);
} else {
wp_send_json(false);
}
return $this->save($form_data);
}
function save($data = array()) {
$form = Form::createOrUpdate($data);
$errors = $form->getErrors();
if($form !== false && $form->id()) {
wp_send_json($form->id());
if(!empty($errors)) {
return array(
'result' => false,
'errors' => $errors
);
} else {
wp_send_json($form);
return array(
'result' => true,
'form_id' => $form->id()
);
}
}
@ -119,10 +110,10 @@ class Forms {
// styles
$css = new Util\Styles(FormRenderer::getStyles($data));
wp_send_json(array(
return array(
'html' => $html,
'css' => $css->render()
));
);
}
function exportsEditor($id) {
@ -134,19 +125,19 @@ class Forms {
$exports = Util\Export::getAll($form->asArray());
}
wp_send_json($exports);
return $exports;
}
function saveEditor($data = array()) {
$form_id = (isset($data['id']) ? (int)$data['id'] : 0);
$name = (isset($data['name']) ? $data['name'] : array());
$name = (isset($data['name']) ? $data['name'] : __('New form'));
$body = (isset($data['body']) ? $data['body'] : array());
$settings = (isset($data['settings']) ? $data['settings'] : array());
$styles = (isset($data['styles']) ? $data['styles'] : array());
$styles = (isset($data['styles']) ? $data['styles'] : '');
if(empty($body) || empty($settings)) {
// error
wp_send_json(false);
return false;
} else {
// check if the form is used as a widget
$is_widget = false;
@ -178,7 +169,7 @@ class Forms {
}
}
// check list selectio
// check list selection
if($has_segment_selection === true) {
$settings['segments_selected_by'] = 'user';
} else {
@ -194,45 +185,42 @@ class Forms {
'styles' => $styles
));
// response
wp_send_json(array(
'result' => ($form !== false),
'is_widget' => $is_widget
));
if($form->getErrors() === false) {
return array(
'result' => true,
'is_widget' => $is_widget
);
} else {
return array(
'result' => false,
'errors' => $form->getErrors()
);
}
}
function restore($id) {
$result = false;
$form = Form::findOne($id);
if($form !== false) {
$result = $form->restore();
$form->restore();
}
wp_send_json($result);
return ($form->getErrors() === false);
}
function trash($id) {
$result = false;
$form = Form::findOne($id);
if($form !== false) {
$result = $form->trash();
$form->trash();
}
wp_send_json($result);
return ($form->getErrors() === false);
}
function delete($id) {
$result = false;
$form = Form::findOne($id);
if($form !== false) {
$form->delete();
$result = 1;
return 1;
}
wp_send_json($result);
return false;
}
function duplicate($id) {
@ -246,7 +234,7 @@ class Forms {
$result = $form->duplicate($data)->asArray();
}
wp_send_json($result);
return $result;
}
function bulkAction($data = array()) {
@ -255,6 +243,6 @@ class Forms {
$data
);
wp_send_json($bulk_action->apply());
return $bulk_action->apply();
}
}

View File

@ -10,17 +10,17 @@ if(!defined('ABSPATH')) exit;
class ImportExport {
function getMailChimpLists($data) {
$mailChimp = new MailChimp($data['api_key']);
wp_send_json($mailChimp->getLists());
return $mailChimp->getLists();
}
function getMailChimpSubscribers($data) {
$mailChimp = new MailChimp($data['api_key']);
wp_send_json($mailChimp->getSubscribers($data['lists']));
return $mailChimp->getSubscribers($data['lists']);
}
function addSegment($data) {
$segment = Segment::createOrUpdate($data);
wp_send_json(
return (
($segment->id) ?
array(
'result' => true,
@ -36,7 +36,7 @@ class ImportExport {
$customField = CustomField::create();
$customField->hydrate($data);
$result = $customField->save();
wp_send_json(
return (
($result) ?
array(
'result' => true,
@ -49,12 +49,16 @@ class ImportExport {
}
function processImport($data) {
$import = new \MailPoet\Subscribers\ImportExport\Import\Import(json_decode($data, true));
wp_send_json($import->process());
$import = new \MailPoet\Subscribers\ImportExport\Import\Import(
json_decode($data, true)
);
return $import->process();
}
function processExport($data) {
$export = new \MailPoet\Subscribers\ImportExport\Export\Export(json_decode($data, true));
wp_send_json($export->process());
$export = new \MailPoet\Subscribers\ImportExport\Export\Export(
json_decode($data, true)
);
return $export->process();
}
}

View File

@ -5,16 +5,19 @@ if(!defined('ABSPATH')) exit;
class Mailer {
function send($data) {
$mailer = new \MailPoet\Mailer\Mailer(
(isset($data['mailer'])) ? $data['mailer'] : false,
(isset($data['sender'])) ? $data['sender'] : false,
(isset($data['reply_to'])) ? $data['reply_to'] : false
);
$result = $mailer->send($data['newsletter'], $data['subscriber']);
wp_send_json(
array(
'result' => ($result) ? true : false
)
);
$response = array();
try {
$mailer = new \MailPoet\Mailer\Mailer(
(isset($data['mailer'])) ? $data['mailer'] : false,
(isset($data['sender'])) ? $data['sender'] : false,
(isset($data['reply_to'])) ? $data['reply_to'] : false
);
$result = $mailer->send($data['newsletter'], $data['subscriber']);
} catch(\Exception $e) {
$result = false;
$response['errors'] = array($e->getMessage());
}
$response['result'] = ($result) ? true : false;
return $response;
}
}

View File

@ -9,42 +9,33 @@ class NewsletterTemplates {
function __construct() {
}
function get($data = array()) {
$id = (isset($data['id'])) ? (int) $data['id'] : 0;
function get($id = false) {
$template = NewsletterTemplate::findOne($id);
if($template === false) {
wp_send_json(false);
return false;
} else {
$template->body = json_decode($template->body);
wp_send_json($template->asArray());
return $template->asArray();
}
}
function getAll() {
$collection = NewsletterTemplate::findArray();
$collection = array_map(function($item) {
$item['body'] = json_decode($item['body']);
return $item;
$collection = NewsletterTemplate::findMany();
return array_map(function($item) {
return $item->asArray();
}, $collection);
wp_send_json($collection);
}
function save($data = array()) {
$result = NewsletterTemplate::createOrUpdate($data);
if($result !== true) {
wp_send_json($result);
} else {
wp_send_json(true);
}
$template = NewsletterTemplate::createOrUpdate($data);
return ($template->getErrors() === false && $template->id() > 0);
}
function delete($id) {
$template = NewsletterTemplate::findOne($id);
if($template !== false) {
$result = $template->delete();
return $template->delete();
} else {
$result = false;
return false;
}
wp_send_json($result);
}
}

View File

@ -1,6 +1,7 @@
<?php
namespace MailPoet\Router;
use MailPoet\Config\Shortcodes;
use MailPoet\Listing;
use MailPoet\Mailer\API\MailPoet;
use MailPoet\Models\Newsletter;
@ -20,11 +21,10 @@ class Newsletters {
function __construct() {
}
function get($data = array()) {
$id = (isset($data['id'])) ? (int) $data['id'] : 0;
function get($id = false) {
$newsletter = Newsletter::findOne($id);
if($newsletter === false) {
wp_send_json(false);
return false;
} else {
$segments = $newsletter->segments()->findArray();
$options = $newsletter->options()->findArray();
@ -33,42 +33,32 @@ class Newsletters {
return $segment['id'];
}, $segments);
$newsletter['options'] = $options;
$newsletter['body'] = json_decode($newsletter['body']);
wp_send_json($newsletter);
return $newsletter;
}
}
function getAll() {
$collection = Newsletter::findArray();
$collection = array_map(function($item) {
$item['body'] = json_decode($item['body']);
return $item;
}, $collection);
wp_send_json($collection);
}
function save($data = array()) {
$segment_ids = array();
if(isset($data['segments'])) {
$segment_ids = $data['segments'];
unset($data['segments']);
}
$options = array();
if(isset($data['options'])) {
$options = $data['options'];
unset($data['options']);
}
$errors = array();
$result = false;
$newsletter = Newsletter::createOrUpdate($data);
$errors = $newsletter->getErrors();
if($newsletter !== false && !$newsletter->id()) {
$errors = $newsletter->getValidationErrors();
if(!empty($errors)) {
return array(
'result' => false,
'errors' => $errors
);
} else {
$result = true;
if(!empty($segment_ids)) {
NewsletterSegment::where('newsletter_id', $newsletter->id)
->deleteMany();
@ -85,136 +75,75 @@ class Newsletters {
NewsletterOption::where('newsletter_id', $newsletter->id)
->deleteMany();
$optionFields = NewsletterOptionField::where(
$option_fields = NewsletterOptionField::where(
'newsletter_type',
$data['type']
)->findArray();
foreach($optionFields as $optionField) {
if(isset($options[$optionField['name']])) {
foreach($option_fields as $option_field) {
if(isset($options[$option_field['name']])) {
$relation = NewsletterOption::create();
$relation->newsletter_id = $newsletter->id;
$relation->option_field_id = $optionField['id'];
$relation->value = $options[$optionField['name']];
$relation->option_field_id = $option_field['id'];
$relation->value = $options[$option_field['name']];
$relation->save();
}
}
}
return array(
'result' => true
);
}
wp_send_json(array(
'result' => $result,
'errors' => $errors
));
}
function restore($id) {
$result = false;
$newsletter = Newsletter::findOne($id);
if($newsletter !== false) {
$result = $newsletter->restore();
$newsletter->restore();
}
wp_send_json($result);
return ($newsletter->getErrors() === false);
}
function trash($id) {
$result = false;
$newsletter = Newsletter::findOne($id);
if($newsletter !== false) {
$result = $newsletter->trash();
$newsletter->trash();
}
wp_send_json($result);
return ($newsletter->getErrors() === false);
}
function delete($id) {
$result = false;
$newsletter = Newsletter::findOne($id);
if($newsletter !== false) {
$newsletter->delete();
$result = 1;
return 1;
}
wp_send_json($result);
return false;
}
function duplicate($id) {
$result = false;
function duplicate($id = false) {
$newsletter = Newsletter::findOne($id);
if($newsletter !== false) {
$data = array(
return $newsletter->duplicate(array(
'subject' => sprintf(__('Copy of %s'), $newsletter->subject)
);
$result = $newsletter->duplicate($data)->asArray();
))->asArray();
}
wp_send_json($result);
}
function send($data = array()) {
$newsletter = Newsletter::findOne($data['id'])->asArray();
if(empty($data['segments'])) {
return wp_send_json(array(
'errors' => array(
__("You need to select a list.")
)
));
}
$segments = Segment::whereIdIn($data['segments'])->findMany();
$subscribers = array();
foreach($segments as $segment) {
$segment_subscribers = $segment->subscribers()->findMany();
foreach($segment_subscribers as $segment_subscriber) {
$subscribers[$segment_subscriber->email] = $segment_subscriber
->asArray();
}
}
if(empty($subscribers)) {
return wp_send_json(array(
'errors' => array(
__("No subscribers found.")
)
));
}
// TODO: TO REMOVE once we add the columns from/reply_to
$newsletter = array_merge($newsletter, $data['newsletter']);
// END - TO REMOVE
$renderer = new Renderer(json_decode($newsletter['body'], true));
$newsletter['body']['html'] = $renderer->render();
$newsletter['body']['text'] = '';
$subscribers = Subscriber::find_array();
$fromEmail = Setting::where('name', 'from_address')->findOne()->value;
$fromName = Setting::where('name', 'from_name')->findOne()->value;
$apiKey = Setting::where('name', 'api_key')->findOne()->value;
$mailer = new MailPoet($apiKey, $fromEmail, $fromName);
foreach ($subscribers as $subscriber) {
$result = $mailer->send(
$newsletter,
sprintf('%s %s <%s>', $subscriber['first_name'], $subscriber['last_name'], $subscriber['email'])
);
if ($result !== true) wp_send_json(false);
}
wp_send_json(true);
return false;
}
function render($data = array()) {
if(!isset($data['body'])) {
wp_send_json(false);
return false;
}
$renderer = new Renderer($data);
wp_send_json(array('rendered_body' => $renderer->render()));
$rendered_newsletter = $renderer->render();
$shortcodes = new \MailPoet\Newsletter\Shortcodes\Shortcodes(
$rendered_newsletter['html'],
$data
);
$rendered_newsletter = $shortcodes->replace();
return array('rendered_body' => $rendered_newsletter);
}
function sendPreview($data = array()) {
@ -222,37 +151,50 @@ class Newsletters {
$newsletter = Newsletter::findOne($id);
if($newsletter === false) {
wp_send_json(array(
return array(
'result' => false
));
);
}
if(empty($data['subscriber'])) {
wp_send_json(array(
return array(
'result' => false,
'errors' => array(__('Please specify receiver information')),
));
'errors' => array(__('Please specify receiver information'))
);
}
$newsletter = $newsletter->asArray();
$renderer = new Renderer($newsletter);
$rendered_body = $renderer->render();
$rendered_newsletter = $renderer->render();
$shortcodes = new \MailPoet\Newsletter\Shortcodes\Shortcodes(
$rendered_newsletter['html'],
$newsletter
);
$processed_newsletter['html'] = $shortcodes->replace();
$shortcodes = new \MailPoet\Newsletter\Shortcodes\Shortcodes(
$rendered_newsletter['text'],
$newsletter
);
$processed_newsletter['text'] = $shortcodes->replace();
$newsletter['body'] = array(
'html' => $rendered_body,
'text' => '',
'html' => $processed_newsletter['html'],
'text' => $processed_newsletter['text'],
);
try {
$mailer = new \MailPoet\Mailer\Mailer(false, false, false);
$mailer = new \MailPoet\Mailer\Mailer(
$mailer = false,
$sender = false,
$reply_to = false
);
$result = $mailer->send($newsletter, $data['subscriber']);
wp_send_json(array(
'result' => $mailer->send($newsletter, $data['subscriber'])
));
return array('result' => $result);
} catch(\Exception $e) {
wp_send_json(array(
return array(
'result' => false,
'errors' => array($e->getMessage()),
));
);
}
}
@ -280,7 +222,7 @@ class Newsletters {
$item['queue'] = ($queue !== false) ? $queue->asArray() : null;
}
wp_send_json($listing_data);
return $listing_data;
}
function bulkAction($data = array()) {
@ -288,46 +230,54 @@ class Newsletters {
'\MailPoet\Models\Newsletter',
$data
);
wp_send_json($bulk_action->apply());
return $bulk_action->apply();
}
function create($data = array()) {
$newsletter = Newsletter::create();
$newsletter->type = $data['type'];
$newsletter->subject = $data['subject'];
$newsletter->body = '{}';
// try to load template data
$template_id = (!empty($data['template']) ? (int)$data['template'] : 0);
$template = NewsletterTemplate::findOne($template_id);
if($template !== false) {
$newsletter->body = $template->body;
}
$options = array();
if(isset($data['options'])) {
$options = $data['options'];
unset($data['options']);
}
$result = $newsletter->save();
if($result !== true) {
wp_send_json($newsletter->getValidationErrors());
$newsletter = Newsletter::createOrUpdate($data);
// try to load template data
$template_id = (!empty($data['template']) ? (int)$data['template'] : false);
$template = NewsletterTemplate::findOne($template_id);
if($template !== false) {
$newsletter->body = $template->body;
} else {
$newsletter->body = array();
}
$newsletter->save();
$errors = $newsletter->getErrors();
if(!empty($errors)) {
return array(
'result' => false,
'errors' =>$errors
);
} else {
if(!empty($options)) {
$optionFields = NewsletterOptionField::where('newsletter_type', $newsletter->type)->findArray();
$option_fields = NewsletterOptionField::where(
'newsletter_type', $newsletter->type
)->findArray();
foreach($optionFields as $optionField) {
if(isset($options[$optionField['name']])) {
foreach($option_fields as $option_field) {
if(isset($options[$option_field['name']])) {
$relation = NewsletterOption::create();
$relation->newsletter_id = $newsletter->id;
$relation->option_field_id = $optionField['id'];
$relation->value = $options[$optionField['name']];
$relation->option_field_id = $option_field['id'];
$relation->value = $options[$option_field['name']];
$relation->save();
}
}
}
$newsletter->body = json_decode($newsletter->body);
wp_send_json($newsletter->asArray());
return array(
'result' => true,
'newsletter' => $newsletter->asArray()
);
}
}
}
}

View File

@ -8,7 +8,6 @@ class Permissions {
}
function set($permissions = array()) {
$result = \MailPoet\Util\Permissions::set($permissions);
wp_send_json($result);
return \MailPoet\Util\Permissions::set($permissions);
}
}

View File

@ -25,8 +25,15 @@ class Router {
$endpoint = __NAMESPACE__ . "\\" . $class;
$method = $_POST['method'];
$data = isset($_POST['data']) ? stripslashes_deep($_POST['data']) : array();
$endpoint = new $endpoint();
$endpoint->$method($data);
try {
$endpoint = new $endpoint();
$response = $endpoint->$method($data);
wp_send_json($response);
} catch(Exception $e) {
error_log($e->getMessage());
exit;
}
}
function setToken() {

View File

@ -12,14 +12,12 @@ class Segments {
function __construct() {
}
function get($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : 0);
function get($id = false) {
$segment = Segment::findOne($id);
if($segment === false) {
wp_send_json(false);
return false;
} else {
wp_send_json($segment->asArray());
return $segment->asArray();
}
}
@ -64,12 +62,7 @@ class Segments {
);
}
wp_send_json($listing_data);
}
function getAll() {
$collection = Segment::findArray();
wp_send_json($collection);
return $listing_data;
}
function save($data = array()) {
@ -83,44 +76,35 @@ class Segments {
} else {
$result = true;
}
wp_send_json(array(
return array(
'result' => $result,
'errors' => $errors
));
);
}
function restore($id) {
$result = false;
$segment = Segment::findOne($id);
if($segment !== false) {
$result = $segment->restore();
$segment->restore();
}
wp_send_json($result);
return ($segment->getErrors() === false);
}
function trash($id) {
$result = false;
$segment = Segment::findOne($id);
if($segment !== false) {
$result = $segment->trash();
$segment->trash();
}
wp_send_json($result);
return ($segment->getErrors() === false);
}
function delete($id) {
$result = false;
$segment = Segment::findOne($id);
if($segment !== false) {
$segment->delete();
$result = 1;
return 1;
}
wp_send_json($result);
return false;
}
function duplicate($id) {
@ -134,13 +118,13 @@ class Segments {
$result = $segment->duplicate($data)->asArray();
}
wp_send_json($result);
return $result;
}
function synchronize() {
$result = WP::synchronizeUsers();
wp_send_json($result);
return $result;
}
function bulkAction($data = array()) {
@ -149,6 +133,6 @@ class Segments {
$data
);
wp_send_json($bulk_action->apply());
return $bulk_action->apply();
}
}

View File

@ -10,17 +10,17 @@ class Settings {
function get() {
$settings = Setting::getAll();
wp_send_json($settings);
return $settings;
}
function set($settings = array()) {
if(empty($settings)) {
wp_send_json(false);
return false;
} else {
foreach($settings as $name => $value) {
Setting::setValue($name, $value);
}
wp_send_json(true);
return true;
}
}
}

View File

@ -17,8 +17,8 @@ class Setup {
} catch(Exception $e) {
$result = false;
}
wp_send_json(array(
return array(
'result' => $result
));
);
}
}

View File

@ -15,22 +15,18 @@ class Subscribers {
function __construct() {
}
function get($data = array()) {
$id = (isset($data['id']) ? (int) $data['id'] : 0);
function get($id = false) {
$subscriber = Subscriber::findOne($id);
if($subscriber === false) {
wp_send_json(false);
} else {
if($subscriber !== false && $subscriber->id() > 0) {
$segments = $subscriber->segments()->findArray();
$subscriber = $subscriber->asArray();
$subscriber = $subscriber->withCustomFields()->asArray();
$subscriber['segments'] = array_map(function($segment) {
return $segment['id'];
}, $segments);
wp_send_json($subscriber);
}
return $subscriber;
}
function listing($data = array()) {
@ -57,39 +53,33 @@ class Subscribers {
}, $relations);
}
wp_send_json($listing_data);
}
function getAll() {
$collection = Subscriber::findArray();
wp_send_json($collection);
return $listing_data;
}
function save($data = array()) {
$errors = array();
$result = false;
$segment_ids = array();
if(array_key_exists('segments', $data)) {
if(isset($data['segments'])) {
$segment_ids = (array)$data['segments'];
unset($data['segments']);
}
$subscriber = Subscriber::createOrUpdate($data);
$errors = $subscriber->getErrors();
if($subscriber !== false && !$subscriber->id()) {
$errors = $subscriber->getValidationErrors();
if(!empty($errors)) {
return array(
'result' => false,
'errors' => $errors
);
} else {
$result = true;
if(!empty($segment_ids)) {
$subscriber->addToSegments($segment_ids);
}
return array(
'result' => true
);
}
wp_send_json(array(
'result' => $result,
'errors' => $errors
));
}
function subscribe($data = array()) {
@ -113,20 +103,26 @@ class Subscribers {
}
if(!empty($errors)) {
wp_send_json(array('errors' => $errors));
return array(
'result' => false,
'errors' => $errors
);
}
$subscriber = Subscriber::subscribe($data, $segment_ids);
$result = false;
if($subscriber === false || !$subscriber->id()) {
$errors = array_merge($errors, $subscriber->getValidationErrors());
} else {
$result = true;
}
if(!empty($errors)) {
wp_send_json(array(
return array(
'result' => false,
'errors' => $errors
));
);
}
// get success message to display after subscription
@ -157,15 +153,15 @@ class Subscribers {
}
}
switch ($form_settings['on_success']) {
switch($form_settings['on_success']) {
case 'page':
// response depending on context
if($doing_ajax === true) {
wp_send_json(array(
return array(
'result' => $result,
'page' => get_permalink($form_settings['success_page']),
'message' => $message
));
);
} else {
$redirect_to = ($result === false) ? $referer : get_permalink($form_settings['success_page']);
wp_redirect(add_query_arg($params, $redirect_to));
@ -176,10 +172,10 @@ class Subscribers {
default:
// response depending on context
if($doing_ajax === true) {
wp_send_json(array(
return array(
'result' => true,
'message' => $message
));
);
} else {
// redirect to previous page
wp_redirect(add_query_arg($params, $referer));
@ -190,37 +186,28 @@ class Subscribers {
}
function restore($id) {
$result = false;
$subscriber = Subscriber::findOne($id);
if($subscriber !== false) {
$result = $subscriber->restore();
$subscriber->restore();
}
wp_send_json($result);
return ($subscriber->getErrors() === false);
}
function trash($id) {
$result = false;
$subscriber = Subscriber::findOne($id);
if($subscriber !== false) {
$result = $subscriber->trash();
$subscriber->trash();
}
wp_send_json($result);
return ($subscriber->getErrors() === false);
}
function delete($id) {
$result = false;
$subscriber = Subscriber::findOne($id);
if($subscriber !== false) {
$subscriber->delete();
$result = 1;
return 1;
}
wp_send_json($result);
return false;
}
function bulkAction($data = array()) {
@ -229,6 +216,6 @@ class Subscribers {
$data
);
wp_send_json($bulk_action->apply());
return $bulk_action->apply();
}
}

View File

@ -1,111 +0,0 @@
<?php
namespace MailPoet\Router;
use \MailPoet\Newsletter\Editor\Transformer;
if(!defined('ABSPATH')) exit;
class Wordpress {
function __construct() {
}
function getPostTypes() {
wp_send_json(get_post_types(array(), 'objects'));
}
function getTaxonomies($args) {
$post_type = (isset($args['postType'])) ? $args['postType'] : 'post';
wp_send_json(get_object_taxonomies($post_type, 'objects'));
}
function getTerms($args) {
$taxonomies = (isset($args['taxonomies'])) ? $args['taxonomies'] : array();
$search = (isset($args['search'])) ? $args['search'] : '';
$limit = (isset($args['limit'])) ? (int)$args['limit'] : 10;
$page = (isset($args['page'])) ? (int)$args['page'] : 1;
wp_send_json(get_terms($taxonomies, array(
'hide_empty' => false,
'search' => $search,
'number' => $limit,
'offset' => $limit * ($page - 1),
)));
}
function getPosts($args) {
$parameters = array(
'posts_per_page' => (isset($args['amount'])) ? (int)$args['amount'] : 10,
'post_type' => (isset($args['contentType'])) ? $args['contentType'] : 'post',
'post_status' => (isset($args['postStatus'])) ? $args['postStatus'] : 'publish',
'orderby' => 'date',
'order' => ($args['sortBy'] === 'newest') ? 'DESC' : 'ASC',
);
if (isset($args['search'])) {
$parameters['s'] = $args['search'];
}
$parameters['tax_query'] = $this->constructTaxonomiesQuery($args);
wp_send_json(get_posts($parameters));
}
function getTransformedPosts($args) {
$parameters = array(
'posts_per_page' => (isset($args['amount'])) ? (int)$args['amount'] : 10,
'post_type' => (isset($args['contentType'])) ? $args['contentType'] : 'post',
'orderby' => 'date',
'order' => ($args['sortBy'] === 'newest') ? 'DESC' : 'ASC',
);
if (isset($args['posts']) && is_array($args['posts'])) {
$parameters['post__in'] = $args['posts'];
}
$parameters['tax_query'] = $this->constructTaxonomiesQuery($args);
$posts = get_posts($parameters);
$transformer = new Transformer($args);
wp_send_json($transformer->transform($posts));
}
private function constructTaxonomiesQuery($args) {
$taxonomies_query = array();
if (isset($args['terms']) && is_array($args['terms'])) {
// Add filtering by tags and categories
$tags = array();
$categories = array();
foreach($args['terms'] as $term) {
if ($term['taxonomy'] === 'category') $categories[] = $term['id'];
else if ($term['taxonomy'] === 'post_tag') $tags[] = $term['id'];
}
$taxonomies = array('post_tag' => $tags, 'category' => $categories);
foreach ($taxonomies as $taxonomy => $terms) {
if (!empty($terms)) {
$tax = array(
'taxonomy' => $taxonomy,
'field' => 'id',
'terms' => $terms,
);
if ($args['inclusionType'] === 'exclude') $tax['operator'] = 'NOT IN';
$taxonomies_query[] = $tax;
}
}
if (!empty($taxonomies_query)) {
// With exclusion we want to use 'AND', because we want posts that
// don't have excluded tags/categories. But with inclusion we want to
// use 'OR', because we want posts that have any of the included
// tags/categories
$taxonomies_query['relation'] = ($args['inclusionType'] === 'exclude') ? 'AND' : 'OR';
return $taxonomies_query;
}
}
return $taxonomies_query;
}
}

View File

@ -61,10 +61,10 @@ class WP {
$segment = Segment::getWPUsers();
// count WP users
$users_count = \count_users()['total_users'];
$users_count = \count_users();
$linked_subscribers_count = $segment->subscribers()->count();
if($users_count !== $linked_subscribers_count) {
if($users_count['total_users'] !== $linked_subscribers_count) {
$linked_subscribers = Subscriber::select('wp_user_id')
->whereNotNull('wp_user_id')
->findArray();

View File

@ -13,9 +13,9 @@ class Hosts {
'secret_key'
),
'regions' => array(
'US East (N. Virginia)' => 'us-east-1.amazonaws.com',
'US West (Oregon)' => 'us-west-2.amazonaws.com',
'EU (Ireland)' => 'eu-west-1.amazonaws.com'
'US East (N. Virginia)' => 'us-east-1',
'US West (Oregon)' => 'us-west-2',
'EU (Ireland)' => 'eu-west-1'
)
),
'ElasticEmail' => array(

View File

@ -16,7 +16,7 @@ class Import {
public $subscribers_count;
public $import_time;
public $profiler_start;
public function __construct($data) {
$this->subscribers_data = $data['subscribers'];
$this->segments = $data['segments'];
@ -31,7 +31,7 @@ class Import {
$this->import_time = date('Y-m-d H:i:s');
$this->profiler_start = microtime(true);
}
function process() {
$subscriber_fields = $this->subscriber_fields;
$subscriber_custom_fields = $this->subscriber_custom_fields;
@ -89,7 +89,7 @@ class Import {
'profiler' => $this->timeExecution()
);
}
function filterExistingAndNewSubscribers($subscribers_data) {
$existing_records = array_filter(
array_map(function($subscriber_emails) {
@ -126,7 +126,7 @@ class Import {
}, $new_records);
}, $subscribers_data)
);
$existing_subscribers =
array_map(function($subscriber) use ($new_records) {
return array_values( // reindex array
@ -142,7 +142,7 @@ class Import {
$new_subscribers
);
}
function deleteExistingTrashedSubscribers($subscribers_data) {
$existing_trashed_records = array_filter(
array_map(function($subscriber_emails) {
@ -161,7 +161,7 @@ class Import {
->deleteMany();
}
}
function extendSubscribersAndFields($subscribers_data, $subscriber_fields) {
$subscribers_data['created_at'] = $this->filterSubscriberCreatedAtDate();
$subscriber_fields[] = 'created_at';
@ -170,7 +170,7 @@ class Import {
$subscriber_fields
);
}
function getSubscriberFields($subscriber_fields) {
return array_values(
array_filter(
@ -180,7 +180,7 @@ class Import {
)
);
}
function getCustomSubscriberFields($subscriber_fields) {
return array_values(
array_filter(
@ -190,11 +190,11 @@ class Import {
)
);
}
function filterSubscriberCreatedAtDate() {
return array_fill(0, $this->subscribers_count, $this->import_time);
}
function filterSubscriberStatus($subscribers_data, $subscriber_fields) {
if(!in_array('status', $subscriber_fields)) {
$subscribers_data['status'] =
@ -242,7 +242,7 @@ class Import {
$subscriber_fields
);
}
function createOrUpdateSubscribers(
$action,
$subscribers_data,
@ -295,7 +295,7 @@ class Import {
);
return $result;
}
function createOrUpdateCustomFields(
$action,
$db_subscribers,
@ -318,8 +318,8 @@ class Import {
$value
);
}, $count, $subscribers_data[$column]);
}, $subscriber_custom_fields)[0];
foreach(array_chunk($subscribers, 200) as $data) {
}, $subscriber_custom_fields);
foreach(array_chunk($subscribers[0], 200) as $data) {
if($action === 'create') {
SubscriberCustomField::createMultiple(
$data
@ -332,13 +332,13 @@ class Import {
}
}
}
function addSubscribersToSegments($subscribers, $segments) {
foreach(array_chunk($subscribers, 200) as $data) {
SubscriberSegment::createMultiple($segments, $data);
}
}
function timeExecution() {
$profiler_end = microtime(true);
return ($profiler_end - $this->profiler_start) / 60;

View File

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

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

@ -1,11 +1,10 @@
<?php
if(!defined('ABSPATH')) exit;
use \MailPoet\Config\Initializer;
if (!defined('ABSPATH')) exit;
/*
* Plugin Name: MailPoet
* Version: 0.0.11
* Version: 0.0.14
* Plugin URI: http://www.mailpoet.com
* Description: MailPoet Newsletters.
* Author: MailPoet
@ -23,10 +22,10 @@ if (!defined('ABSPATH')) exit;
require 'vendor/autoload.php';
define('MAILPOET_VERSION', '0.0.11');
define('MAILPOET_VERSION', '0.0.14');
$initializer = new Initializer(array(
'file' => __FILE__,
'version' => MAILPOET_VERSION
));
'file' => __FILE__,
'version' => MAILPOET_VERSION
));
$initializer->init();

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

@ -9,6 +9,7 @@ $console->writeln('Loading WP core... ('.$wp_load_file.')');
$console->writeln('Cleaning up database...');
$models = array(
'CustomField',
'Form',
'Newsletter',
'NewsletterSegment',
'NewsletterTemplate',

View File

@ -94,6 +94,7 @@ class MailerCest {
}
function itCanSend() {
if(getenv('WP_TEST_MAILER_ENABLE_SENDING') !== 'true') return;
$mailer = new Mailer($this->mailer, $this->sender, $this->reply_to);
expect($mailer->send($this->newsletter, $this->subscriber))->true();
}

View File

@ -6,16 +6,33 @@ class AmazonSESCest {
function _before() {
$this->settings = array(
'method' => 'AmazonSES',
'access_key' => 'AKIAJM6Y5HMGXBLDNSRA',
'secret_key' => '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',
'region' => 'us-east-1',
'access_key' => getenv('WP_TEST_MAILER_AMAZON_ACCESS') ?
getenv('WP_TEST_MAILER_AMAZON_ACCESS') :
'1234567890',
'secret_key' => getenv('WP_TEST_MAILER_AMAZON_SECRET') ?
getenv('WP_TEST_MAILER_AMAZON_SECRET') :
'abcdefghijk',
'region' => getenv('WP_TEST_MAILER_AMAZON_REGION') ?
getenv('WP_TEST_MAILER_AMAZON_REGION') :
'us-west-2',
);
$this->sender = array(
'from_name' => 'Sender',
'from_email' => 'staff@mailpoet.com',
'from_name_email' => 'Sender <staff@mailpoet.com>'
);
$this->reply_to = array(
'reply_to_name' => 'Reply To',
'reply_to_email' => 'reply-to@mailpoet.com',
'reply_to_name_email' => 'Reply To <reply-to@mailpoet.com>'
);
$this->from = 'Sender <vlad@mailpoet.com>';
$this->mailer = new AmazonSES(
$this->settings['region'],
$this->settings['access_key'],
$this->settings['secret_key'],
$this->from);
$this->sender,
$this->reply_to
);
$this->subscriber = 'Recipient <mailpoet-phoenix-test@mailinator.com>';
$this->newsletter = array(
'subject' => 'testing AmazonSES',
@ -33,8 +50,8 @@ class AmazonSESCest {
);
expect($this->mailer->url)
->equals(
sprintf('https://email.%s.amazonaws.com', $this->settings['region'])
);
sprintf('https://email.%s.amazonaws.com', $this->settings['region'])
);
expect(preg_match('!^\d{8}T\d{6}Z$!', $this->mailer->date))->equals(1);
expect(preg_match('!^\d{8}$!', $this->mailer->date_without_time))->equals(1);
}
@ -43,7 +60,9 @@ class AmazonSESCest {
$body = $this->mailer->getBody($this->newsletter, $this->subscriber);
expect($body['Action'])->equals('SendEmail');
expect($body['Version'])->equals('2010-12-01');
expect($body['Source'])->equals($this->from);
expect($body['Source'])->equals($this->sender['from_name_email']);
expect($body['ReplyToAddresses.member.1'])
->equals($this->reply_to['reply_to_name_email']);
expect($body['Destination.ToAddresses.member.1'])
->contains($this->subscriber);
expect($body['Message.Subject.Data'])
@ -52,7 +71,7 @@ class AmazonSESCest {
->equals($this->newsletter['body']['html']);
expect($body['Message.Body.Text.Data'])
->equals($this->newsletter['body']['text']);
expect($body['ReturnPath'])->equals($this->from);
expect($body['ReturnPath'])->equals($this->sender['from_name_email']);
}
function itCanCreateRequest() {
@ -137,6 +156,7 @@ class AmazonSESCest {
}
function itCannotSendWithoutProperAccessKey() {
if(getenv('WP_TEST_MAILER_ENABLE_SENDING') !== 'true') return;
$this->mailer->aws_access_key = 'somekey';
$result = $this->mailer->send(
$this->newsletter,
@ -146,10 +166,11 @@ class AmazonSESCest {
}
function itCanSend() {
if(getenv('WP_TEST_MAILER_ENABLE_SENDING') !== 'true') return;
$result = $this->mailer->send(
$this->newsletter,
$this->subscriber
);
expect($result)->true();
}
}
}

Some files were not shown because too many files have changed in this diff Show More