Compare commits

...

40 Commits

Author SHA1 Message Date
a1441dfde6 Bump up version to 0.0.19 2016-03-04 18:43:22 +02:00
8e7336d352 Merge pull request #373 from mailpoet/editor_rendering
Editor rendering (Part 2)
2016-03-04 11:11:42 -05:00
e0e2933cdf Merge pull request #379 from mailpoet/scheduled_sending
Fix mistyped method name
2016-03-04 11:06:40 -05:00
c93ec629ea Fix mistyped method name 2016-03-04 18:03:02 +02:00
d613df6558 Merge pull request #378 from mailpoet/scheduled_sending
Prepares codebase for future scheduled sending
2016-03-04 17:58:59 +02:00
01c9096543 - Rewrites hooks to not use closures 2016-03-04 10:47:55 -05:00
f120e839dd Merge pull request #375 from mailpoet/export_large_dataset_fix
Export update
2016-03-04 14:12:20 +02:00
f0ab592c04 Merge pull request #376 from mailpoet/rendering_serverside_update
Removes padding from the last column element
2016-03-04 13:30:31 +02:00
1a269d28b3 Merge pull request #374 from mailpoet/edit_subscription
edit subscription form + save
2016-03-04 12:54:56 +02:00
8d4a666bf0 added logged out handling of subscriber save request 2016-03-04 11:52:06 +01:00
4a96e483a6 edit profile rendering (missing segments list) + fallback for Url::redirectBack() 2016-03-04 11:20:17 +01:00
263a66407f - Removes mailpoet_padded class from the last element in a column 2016-03-03 19:13:50 -05:00
c4b728f4e1 - Updates Daemon to execute workers via WP's action hook
- Bootstraps Scheduler class
2016-03-03 15:42:10 -05:00
9970ad7fb6 Merge pull request #369 from mailpoet/manage_subscription
Manage subscription (part 2)
2016-03-03 15:33:56 -05:00
eb380499d9 - Rewrites export to support large datasets
- Updates unit tests
- Fixes an issue with export UI not displaying subscribers without segment
2016-03-03 14:33:10 -05:00
4b528549f5 edit subscription form + save 2016-03-03 15:57:42 +01:00
a903017bc2 Update "Please include unsubscribe link" message 2016-03-03 15:48:59 +02:00
afd25e9174 Remove old and unused editor template 2016-03-03 15:39:46 +02:00
b1bbf1b3bc Add default email for newsletter preview, disallow empty email 2016-03-03 12:59:49 +02:00
9e84e8df93 Show loading icon when loading newsletter preview 2016-03-02 16:57:04 +02:00
df775b5a07 Set font-style for paragraph and headings 2016-03-02 16:57:04 +02:00
c9c22b9b52 Switch 'Comic Sans' to 'Comic Sans MS' 2016-03-02 16:57:04 +02:00
93d20688ea Prevent heading line height and font weight from being overridden 2016-03-02 16:57:04 +02:00
9ebcddfa2a Update button defaults 2016-03-02 16:57:04 +02:00
85995bc8a6 Merge pull request #369 from mailpoet/manage_subscription
Manage subscription (part 2)
2016-03-02 16:42:12 +02:00
a8f2959bc6 added Subscriber::generateToken() 2016-03-02 15:07:37 +01:00
36242bd580 Fixed sending confirmation email to new subscribers (first time)
- fixed newsletters listing issue with queue
2016-03-02 13:11:06 +01:00
1e7dbc8449 refactor pages 2016-03-01 18:27:31 +01:00
12c159c627 bugfix + refactoring 2016-03-01 16:23:15 +01:00
82ed7e51c5 Replaced "contains" by "indexOf" (chrome issue)
- added public ajax routing (not checking permissions)
- exception handling in form subscription
2016-03-01 13:18:36 +01:00
0b3aa0d12a Merge pull request #370 from mailpoet/import_batch_update
Import batch size increase
2016-03-01 12:10:46 +02:00
4d788f69aa - Updates batch size to 2000 2016-02-29 11:41:17 -05:00
c721843c12 extracted subscription pages code from router 2016-02-29 13:34:17 +01:00
d2be407ccb rendering of edit subscription form 2016-02-29 13:34:17 +01:00
e6337216cf improved demo view of subscription pages 2016-02-29 13:34:17 +01:00
f1c396f0b0 basic implementation of confirm/edit/unsubscribe pages 2016-02-29 13:34:17 +01:00
3622bc9fcb fixed sending issue of confirmation email 2016-02-29 13:34:17 +01:00
14fe333678 Send confirmation email + page 2016-02-29 13:34:17 +01:00
cf6466197a Fixed Subscribers' bulk actions when filtering by a segment
- filter by segment is now affected by the selected group (all, trash,...)
- updated relationship methods between subscribers & segments (to account for subsegment status)
2016-02-29 13:34:17 +01:00
f56bee76f2 MailPoet.Date to handle localized dates and times 2016-02-29 13:34:17 +01:00
57 changed files with 1231 additions and 2179 deletions

View File

@ -32,5 +32,6 @@ $block-hover-highlight-color = $primary-active-color
position: relative position: relative
line-height: 1.61803398875 line-height: 1.61803398875
p p, h1, h2, h3, h4, h5, h6
line-height: 1.61803398875 line-height: 1.61803398875
font-style: normal

View File

@ -12,9 +12,11 @@
h1, h2, h3, h4, h5, h6 h1, h2, h3, h4, h5, h6
padding: 0 padding: 0
margin: 0 margin: 0
font-weight: normal
p p
margin-top: 0 margin-top: 0
font-weight: normal
blockquote blockquote
margin: 1em margin: 1em

View File

@ -2,3 +2,13 @@
@require 'parsley' @require 'parsley'
@require 'form_validation' @require 'form_validation'
/* labels */
.mailpoet_text_label
.mailpoet_textarea_label
.mailpoet_select_label
.mailpoet_radio_label
.mailpoet_checkbox_label
.mailpoet_list_label
.mailpoet_date_label
display:block

136
assets/js/src/date.js Normal file
View File

@ -0,0 +1,136 @@
define('date',
[
'mailpoet',
'jquery',
'moment'
], function(
MailPoet,
jQuery,
Moment
) {
'use strict';
MailPoet.Date = {
version: 0.1,
options: {},
defaults: {
offset: 0,
format: 'F, d Y H:i:s'
},
init: function(options) {
options = options || {};
// set UTC offset
if (
options.offset === undefined
&& window.mailpoet_date_offset !== undefined
) {
options.offset = window.mailpoet_date_offset;
}
// set date format
if (
options.format === undefined
&& window.mailpoet_date_format !== undefined
) {
options.format = window.mailpoet_date_format;
}
// merge options
this.options = jQuery.extend({}, this.defaults, options);
return this;
},
format: function(date, options) {
this.init(options);
return Moment.utc(date)
.local()
.format(this.convertFormat(this.options.format));
},
short: function(date) {
return this.format(date, {
format: 'F, j Y'
});
},
full: function(date) {
return this.format(date, {
format: 'F, j Y H:i:s'
});
},
time: function(date) {
return this.format(date, {
format: 'H:i:s'
});
},
convertFormat: function(format) {
const format_mappings = {
date: {
D: 'ddd',
l: 'dddd',
d: 'DD',
j: 'D',
z: 'DDDD',
N: 'E',
S: '',
M: 'MMM',
F: 'MMMM',
m: 'MM',
n: '',
t: '',
y: 'YY',
Y: 'YYYY',
H: 'HH',
h: 'hh',
g: 'h',
A: 'A',
i: 'mm',
s: 'ss',
T: 'z',
O: 'ZZ',
w: 'd',
W: 'WW'
},
strftime: {
a: 'ddd',
A: 'dddd',
b: 'MMM',
B: 'MMMM',
d: 'DD',
e: 'D',
F: 'YYYY-MM-DD',
H: 'HH',
I: 'hh',
j: 'DDDD',
k: 'H',
l: 'h',
m: 'MM',
M: 'mm',
p: 'A',
S: 'ss',
u: 'E',
w: 'd',
W: 'WW',
y: 'YY',
Y: 'YYYY',
z: 'ZZ',
Z: 'z'
}
};
const replacements = format_mappings['date'];
let outputFormat = '';
Object.keys(replacements).forEach(function(key) {
if (format.indexOf(key) !== -1) {
format = format.replace(key, '%'+key);
}
});
outputFormat = format;
Object.keys(replacements).forEach(function(key) {
if (outputFormat.indexOf('%'+key) !== -1) {
outputFormat = outputFormat.replace('%'+key, replacements[key]);
}
});
return outputFormat;
}
};
});

View File

@ -146,7 +146,7 @@ const FormList = React.createClass({
{ segments } { segments }
</td> </td>
<td className="column-date" data-colname="Created on"> <td className="column-date" data-colname="Created on">
<abbr>{ form.created_at }</abbr> <abbr>{ MailPoet.Date.full(form.created_at) }</abbr>
</td> </td>
</div> </div>
); );

View File

@ -230,15 +230,17 @@ define([
json.body = JSON.stringify(json.body); json.body = JSON.stringify(json.body);
} }
MailPoet.Modal.loading(true);
MailPoet.Ajax.post({ MailPoet.Ajax.post({
endpoint: 'newsletters', endpoint: 'newsletters',
action: 'render', action: 'render',
data: json, data: json,
}).done(function(response){ }).done(function(response){
console.log('Should open a new window'); MailPoet.Modal.loading(false);
window.open('data:text/html;charset=utf-8,' + encodeURIComponent(response.rendered_body), '_blank'); window.open('data:text/html;charset=utf-8,' + encodeURIComponent(response.rendered_body), '_blank');
}).fail(function(error) { }).fail(function(error) {
console.log('Preview error', json); MailPoet.Modal.loading(false);
alert('Something went wrong, check console'); alert('Something went wrong, check console');
}); });
}, },
@ -246,11 +248,23 @@ define([
// testing sending method // testing sending method
console.log('trying to send a preview'); console.log('trying to send a preview');
// get form data // get form data
var $emailField = this.$('#mailpoet_preview_to_email');
var data = { var data = {
subscriber: this.$('#mailpoet_preview_to_email').val(), subscriber: $emailField.val(),
id: App.getNewsletter().get('id'), id: App.getNewsletter().get('id'),
}; };
if (data.subscriber.length <= 0) {
MailPoet.Notice.error(
App.getConfig().get('translations.newsletterPreviewEmailMissing'),
{
positionAfter: $emailField,
scroll: true,
}
);
return false;
}
// send test email // send test email
MailPoet.Modal.loading(true); MailPoet.Modal.loading(true);

View File

@ -135,7 +135,7 @@ define(
}); });
}, },
renderStatus: function(item) { renderStatus: function(item) {
if(item.queue === null) { if(!item.queue) {
return ( return (
<span>Not sent yet.</span> <span>Not sent yet.</span>
); );
@ -208,10 +208,8 @@ define(
'has-row-actions' 'has-row-actions'
); );
var segments = mailpoet_segments.filter(function(segment) { var segments = newsletter.segments.map(function(segment) {
return (jQuery.inArray(segment.id, newsletter.segments) !== -1); return segment.name
}).map(function(segment) {
return segment.name;
}).join(', '); }).join(', ');
return ( return (
@ -229,10 +227,10 @@ define(
{ segments } { segments }
</td> </td>
<td className="column-date" data-colname="Subscribed on"> <td className="column-date" data-colname="Subscribed on">
<abbr>{ newsletter.created_at }</abbr> <abbr>{ MailPoet.Date.full(newsletter.created_at) }</abbr>
</td> </td>
<td className="column-date" data-colname="Last modified on"> <td className="column-date" data-colname="Last modified on">
<abbr>{ newsletter.updated_at }</abbr> <abbr>{ MailPoet.Date.full(newsletter.updated_at) }</abbr>
</td> </td>
</div> </div>
); );

View File

@ -190,7 +190,7 @@ const SegmentList = React.createClass({
<abbr>{ segment.subscribers_count.unsubscribed || 0 }</abbr> <abbr>{ segment.subscribers_count.unsubscribed || 0 }</abbr>
</td> </td>
<td className="column-date" data-colname="Created on"> <td className="column-date" data-colname="Created on">
<abbr>{ segment.created_at }</abbr> <abbr>{ MailPoet.Date.full(segment.created_at) }</abbr>
</td> </td>
</div> </div>
); );

View File

@ -3,15 +3,13 @@ define(
'react', 'react',
'react-router', 'react-router',
'mailpoet', 'mailpoet',
'form/form.jsx', 'form/form.jsx'
'moment'
], ],
function( function(
React, React,
Router, Router,
MailPoet, MailPoet,
Form, Form
Moment
) { ) {
var fields = [ var fields = [
{ {
@ -69,9 +67,8 @@ define(
label = segment.name; label = segment.name;
if (subscription.status === 'unsubscribed') { if (subscription.status === 'unsubscribed') {
const unsubscribed_at = Moment(subscription.updated_at) const unsubscribed_at = MailPoet.Date
.utcOffset(parseInt(mailpoet_date_offset)) .format(subscription.updated_at);
.format('ddd, D MMM YYYY HH:mm:ss');
label += ' (Unsubscribed on '+unsubscribed_at+')'; label += ' (Unsubscribed on '+unsubscribed_at+')';
} }
} }

View File

@ -1056,7 +1056,7 @@ define(
var columns = {}, var columns = {},
queue = new jQuery.AsyncQueue(), queue = new jQuery.AsyncQueue(),
batchNumber = 0, batchNumber = 0,
batchSize = 500, batchSize = 2000,
timestamp = Date.now() / 1000, timestamp = Date.now() / 1000,
subscribers = [], subscribers = [],
importResults = { importResults = {

View File

@ -208,6 +208,16 @@ const bulk_actions = [
); );
} }
}, },
{
name: 'sendConfirmationEmail',
label: 'Resend confirmation email',
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d confirmation emails have been sent.'
.replace('%$1d', ~~response)
);
}
},
{ {
name: 'trash', name: 'trash',
label: 'Trash', label: 'Trash',
@ -272,13 +282,13 @@ const SubscriberList = React.createClass({
subscriber.subscriptions.map((subscription) => { subscriber.subscriptions.map((subscription) => {
const segment = this.getSegmentFromId(subscription.segment_id); const segment = this.getSegmentFromId(subscription.segment_id);
if(segment === false) return;
if (subscription.status === 'subscribed') { if (subscription.status === 'subscribed') {
subscribed_segments.push(segment.name); subscribed_segments.push(segment.name);
} else { } else if (subscription.status === 'unsubscribed') {
unsubscribed_segments.push(segment.name); unsubscribed_segments.push(segment.name);
} }
}); });
segments = ( segments = (
<span> <span>
<span className="mailpoet_segments_subscribed"> <span className="mailpoet_segments_subscribed">
@ -331,10 +341,10 @@ const SubscriberList = React.createClass({
{ segments } { segments }
</td> </td>
<td className="column-date" data-colname="Subscribed on"> <td className="column-date" data-colname="Subscribed on">
<abbr>{ subscriber.created_at }</abbr> <abbr>{ MailPoet.Date.full(subscriber.created_at) }</abbr>
</td> </td>
<td className="column-date" data-colname="Last modified on"> <td className="column-date" data-colname="Last modified on">
<abbr>{ subscriber.updated_at }</abbr> <abbr>{ MailPoet.Date.full(subscriber.updated_at) }</abbr>
</td> </td>
</div> </div>
); );

View File

@ -1,8 +1,12 @@
<?php <?php
namespace MailPoet\Config; namespace MailPoet\Config;
use \MailPoet\Models\Setting; use \MailPoet\Models\Setting;
use \MailPoet\Util\Url;
class Changelog { class Changelog {
function __construct() {
}
function init() { function init() {
$doing_ajax = (bool)(defined('DOING_AJAX') && DOING_AJAX); $doing_ajax = (bool)(defined('DOING_AJAX') && DOING_AJAX);
@ -42,20 +46,7 @@ class Changelog {
// save version number // save version number
Setting::setValue('version', Env::$version); Setting::setValue('version', Env::$version);
global $wp; Url::redirectWithReferer($redirect_url);
$current_url = home_url(add_query_arg($wp->query_string, $wp->request));
if($redirect_url !== $current_url) {
wp_safe_redirect(
add_query_arg(
array(
'mailpoet_redirect' => urlencode($current_url)
),
$redirect_url
)
);
exit;
}
} }
} }
} }

View File

@ -1,5 +1,7 @@
<?php <?php
namespace MailPoet\Config; namespace MailPoet\Config;
use MailPoet\Cron\Workers\Scheduler;
use MailPoet\Cron\Workers\SendingQueue;
use \MailPoet\Models\Setting; use \MailPoet\Models\Setting;
class Hooks { class Hooks {
@ -11,6 +13,7 @@ class Hooks {
$this->setupWPUsers(); $this->setupWPUsers();
$this->setupImageSize(); $this->setupImageSize();
$this->setupListing(); $this->setupListing();
$this->setupCronWorkers();
} }
function setupSubscribe() { function setupSubscribe() {
@ -144,4 +147,19 @@ class Hooks {
return $status; return $status;
} }
} }
function setupCronWorkers() {
add_action('mailpoet_cron_worker', array($this, 'runSchedulerWorker'), 10, 1);
add_action('mailpoet_cron_worker', array($this, 'runSendingQueueWorker'), 10, 1);
}
function runSchedulerWorker($timer) {
$scheduler = new Scheduler($timer);
$scheduler->process();
}
function runSendingQueueWorker($timer) {
$sending_queue = new SendingQueue($timer);
$sending_queue->process();
}
} }

View File

@ -4,8 +4,6 @@ namespace MailPoet\Config;
use MailPoet\Models; use MailPoet\Models;
use MailPoet\Cron\Supervisor; use MailPoet\Cron\Supervisor;
use MailPoet\Router; use MailPoet\Router;
use MailPoet\Models\Setting;
use MailPoet\Settings\Pages;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
@ -26,6 +24,7 @@ class Initializer {
register_activation_hook(Env::$file, array($this, 'runPopulator')); register_activation_hook(Env::$file, array($this, 'runPopulator'));
add_action('plugins_loaded', array($this, 'setup')); add_action('plugins_loaded', array($this, 'setup'));
add_action('init', array($this, 'onInit'));
add_action('widgets_init', array($this, 'setupWidget')); add_action('widgets_init', array($this, 'setupWidget'));
} }
@ -34,16 +33,14 @@ class Initializer {
$this->setupRenderer(); $this->setupRenderer();
$this->setupLocalizer(); $this->setupLocalizer();
$this->setupMenu(); $this->setupMenu();
$this->setupRouter();
$this->setupPermissions(); $this->setupPermissions();
$this->setupPublicAPI();
$this->setupAnalytics(); $this->setupAnalytics();
$this->setupChangelog(); $this->setupChangelog();
$this->runQueueSupervisor();
$this->setupShortcodes(); $this->setupShortcodes();
$this->setupHooks(); $this->setupHooks();
$this->setupPages();
$this->setupImages(); $this->setupImages();
$this->setupPublicAPI();
$this->runQueueSupervisor();
} catch(\Exception $e) { } catch(\Exception $e) {
// if anything goes wrong during init // if anything goes wrong during init
// automatically deactivate the plugin // automatically deactivate the plugin
@ -51,11 +48,20 @@ class Initializer {
} }
} }
function onInit() {
$this->setupRouter();
$this->setupPages();
$this->runQueueSupervisor();
}
function setupDB() { function setupDB() {
\ORM::configure(Env::$db_source_name); \ORM::configure(Env::$db_source_name);
\ORM::configure('username', Env::$db_username); \ORM::configure('username', Env::$db_username);
\ORM::configure('password', Env::$db_password); \ORM::configure('password', Env::$db_password);
\ORM::configure('logging', WP_DEBUG); \ORM::configure('logging', WP_DEBUG);
\ORM::configure('logger', function($query, $time) {
// error_log("\n".$query."\n");
});
\ORM::configure('driver_options', array( \ORM::configure('driver_options', array(
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', \PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
@ -144,8 +150,11 @@ class Initializer {
} }
function setupPages() { function setupPages() {
$pages = new Pages(); $pages = new \MailPoet\Settings\Pages();
$pages->init(); $pages->init();
$subscription_pages = new \MailPoet\Subscription\Pages();
$subscription_pages->init();
} }
function setupShortcodes() { function setupShortcodes() {

View File

@ -380,6 +380,7 @@ class Menu {
$data = array( $data = array(
'customFields' => $custom_fields, 'customFields' => $custom_fields,
'settings' => Setting::getAll(),
); );
wp_enqueue_media(); wp_enqueue_media();
wp_enqueue_script('tinymce-wplink', includes_url('js/tinymce/plugins/wplink/plugin.js')); wp_enqueue_script('tinymce-wplink', includes_url('js/tinymce/plugins/wplink/plugin.js'));

View File

@ -68,4 +68,11 @@ class CronHelper {
// throw an error if all connection attempts failed // throw an error if all connection attempts failed
throw new \Exception(__('Site URL is unreachable.')); throw new \Exception(__('Site URL is unreachable.'));
} }
static function checkExecutionTimer($timer) {
$elapsed_time = microtime(true) - $timer;
if($elapsed_time >= self::daemon_execution_limit) {
throw new \Exception(__('Maximum execution time reached.'));
}
}
} }

View File

@ -1,7 +1,9 @@
<?php <?php
namespace MailPoet\Cron; namespace MailPoet\Cron;
use MailPoet\Cron\Workers\Scheduler;
use MailPoet\Cron\Workers\SendingQueue; use MailPoet\Cron\Workers\SendingQueue;
use MailPoet\Models\Newsletter;
require_once(ABSPATH . 'wp-includes/pluggable.php'); require_once(ABSPATH . 'wp-includes/pluggable.php');
@ -35,8 +37,7 @@ class Daemon {
} }
$this->abortIfStopped($daemon); $this->abortIfStopped($daemon);
try { try {
$sending_queue = new SendingQueue($this->timer); do_action('mailpoet_cron_worker', $this->timer);
$sending_queue->process();
} catch(\Exception $e) { } catch(\Exception $e) {
} }
$elapsed_time = microtime(true) - $this->timer; $elapsed_time = microtime(true) - $this->timer;

View File

@ -0,0 +1,27 @@
<?php
namespace MailPoet\Cron\Workers;
use MailPoet\Cron\CronHelper;
use MailPoet\Models\Setting;
use MailPoet\Util\Security;
if(!defined('ABSPATH')) exit;
class Scheduler {
public $timer;
function __construct($timer = false) {
$this->timer = ($timer) ? $timer : microtime(true);
CronHelper::checkExecutionTimer($this->timer);
}
function process() {
}
function checkExecutionTimer() {
$elapsed_time = microtime(true) - $this->timer;
if($elapsed_time >= CronHelper::daemon_execution_limit) {
throw new \Exception(__('Maximum execution time reached.'));
}
}
}

View File

@ -10,6 +10,7 @@ use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\Renderer\Renderer; use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Newsletter\Shortcodes\Shortcodes; use MailPoet\Newsletter\Shortcodes\Shortcodes;
use MailPoet\Util\Helpers; use MailPoet\Util\Helpers;
use MailPoet\Util\Security;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
@ -27,6 +28,7 @@ class SendingQueue {
'processBulkSubscribers' : 'processBulkSubscribers' :
'processIndividualSubscriber'; 'processIndividualSubscriber';
$this->timer = ($timer) ? $timer : microtime(true); $this->timer = ($timer) ? $timer : microtime(true);
CronHelper::checkExecutionTimer($this->timer);
} }
function process() { function process() {
@ -102,7 +104,7 @@ class SendingQueue {
} }
$this->updateQueue($queue); $this->updateQueue($queue);
$this->checkSendingLimit(); $this->checkSendingLimit();
$this->checkExecutionTimer(); CronHelper::checkExecutionTimer($this->timer);
return $queue->subscribers; return $queue->subscribers;
} }
@ -129,7 +131,7 @@ class SendingQueue {
$this->updateNewsletterStatistics($newsletter_statistics); $this->updateNewsletterStatistics($newsletter_statistics);
} }
$this->updateQueue($queue); $this->updateQueue($queue);
$this->checkExecutionTimer(); CronHelper::checkExecutionTimer($this->timer);
} }
return $queue->subscribers; return $queue->subscribers;
} }
@ -259,11 +261,4 @@ class SendingQueue {
} }
return; return;
} }
function checkExecutionTimer() {
$elapsed_time = microtime(true) - $this->timer;
if($elapsed_time >= CronHelper::daemon_execution_limit) {
throw new \Exception(__('Maximum execution time reached.'));
}
}
} }

View File

@ -20,18 +20,22 @@ class Checkbox extends Base {
foreach($options as $option) { foreach($options as $option) {
$html .= '<label class="mailpoet_checkbox_label">'; $html .= '<label class="mailpoet_checkbox_label">';
$html .= '<input type="hidden" name="'.$field_name.'" value="" />';
$html .= '<input type="checkbox" class="mailpoet_checkbox" '; $html .= '<input type="checkbox" class="mailpoet_checkbox" ';
$html .= 'name="'.$field_name.'" '; $html .= 'name="'.$field_name.'" ';
$html .= 'value="1" '; $html .= 'value="1" ';
$html .= (isset($option['is_checked']) && $option['is_checked']) $html .= (
? 'checked="checked"' : ''; (isset($option['is_checked']) && $option['is_checked'])
||
(self::getFieldValue($block))
) ? 'checked="checked"' : '';
$html .= $field_validation; $html .= $field_validation;
$html .= ' />'.$option['value']; $html .= ' /> '.esc_attr($option['value']);
$html .= '</label>'; $html .= '</label>';
} }

View File

@ -33,18 +33,37 @@ class Date extends Base {
// generate an array of selectors based on date format // generate an array of selectors based on date format
$date_selectors = explode('/', $date_format); $date_selectors = explode('/', $date_format);
// format value if present
$value = self::getFieldValue($block);
$day = null;
$month = null;
$year = null;
if($value) {
$day = (int)strftime('%d', $value);
$month = (int)strftime('%m', $value);
$year = (int)strftime('%Y', $value);
} else if(!empty($block['params']['is_default_today'])) {
$day = (int)strftime('%d');
$month = (int)strftime('%m');
$year = (int)strftime('%Y');
}
foreach($date_selectors as $date_selector) { foreach($date_selectors as $date_selector) {
if($date_selector === 'dd') { if($date_selector === 'dd') {
$block['selected'] = $day;
$html .= '<select class="mailpoet_date_day" '; $html .= '<select class="mailpoet_date_day" ';
$html .= 'name="'.$field_name.'[day]" placeholder="'.__('Day').'">'; $html .= 'name="'.$field_name.'[day]" placeholder="'.__('Day').'">';
$html .= static::getDays($block); $html .= static::getDays($block);
$html .= '</select>'; $html .= '</select>';
} else if($date_selector === 'mm') { } else if($date_selector === 'mm') {
$block['selected'] = $month;
$html .= '<select class="mailpoet_date_month" '; $html .= '<select class="mailpoet_date_month" ';
$html .= 'name="'.$field_name.'[month]" placeholder="'.__('Month').'">'; $html .= 'name="'.$field_name.'[month]" placeholder="'.__('Month').'">';
$html .= static::getMonths($block); $html .= static::getMonths($block);
$html .= '</select>'; $html .= '</select>';
} else if($date_selector === 'yyyy') { } else if($date_selector === 'yyyy') {
$block['selected'] = $year;
$html .= '<select class="mailpoet_date_year" '; $html .= '<select class="mailpoet_date_year" ';
$html .= 'name="'.$field_name.'[year]" placeholder="'.__('Year').'">'; $html .= 'name="'.$field_name.'[year]" placeholder="'.__('Year').'">';
$html .= static::getYears($block); $html .= static::getYears($block);
@ -84,11 +103,6 @@ class Date extends Base {
'selected' => null 'selected' => null
); );
// is default today
if(!empty($block['params']['is_default_today'])) {
$defaults['selected'] = (int)strftime('%m');
}
// merge block with defaults // merge block with defaults
$block = array_merge($defaults, $block); $block = array_merge($defaults, $block);

View File

@ -27,11 +27,15 @@ class Radio extends Base {
$html .= 'value="'.esc_attr($option['value']).'" '; $html .= 'value="'.esc_attr($option['value']).'" ';
$html .= (isset($option['is_checked']) && $option['is_checked']) $html .= (
? 'checked="checked"' : ''; (isset($option['is_checked']) && $option['is_checked'])
||
(self::getFieldValue($block) === $option['value'])
) ? 'checked="checked"' : '';
$html .= $field_validation; $html .= $field_validation;
$html .= ' />&nbsp;'.esc_attr($option['value']); $html .= ' /> '.esc_attr($option['value']);
$html .= '</label>'; $html .= '</label>';
} }

View File

@ -28,7 +28,7 @@ class Segment extends Base {
$html .= 'name="'.$field_name.'[]" '; $html .= 'name="'.$field_name.'[]" ';
$html .= 'value="'.$option['id'].'" '.$is_checked.' '; $html .= 'value="'.$option['id'].'" '.$is_checked.' ';
$html .= $field_validation; $html .= $field_validation;
$html .= ' />'.$option['name']; $html .= ' /> '.esc_attr($option['name']);
$html .= '</label>'; $html .= '</label>';
} }

View File

@ -10,9 +10,7 @@ class Select extends Base {
$field_validation = static::getInputValidation($block); $field_validation = static::getInputValidation($block);
$html .= '<p class="mailpoet_paragraph">'; $html .= '<p class="mailpoet_paragraph">';
$html .= static::renderLabel($block); $html .= static::renderLabel($block);
$html .= '<select class="mailpoet_select" name="'.$field_name.'">'; $html .= '<select class="mailpoet_select" name="'.$field_name.'">';
if(isset($block['params']['label_within']) if(isset($block['params']['label_within'])
@ -21,10 +19,22 @@ class Select extends Base {
} }
foreach($block['params']['values'] as $option) { foreach($block['params']['values'] as $option) {
$is_selected = (isset($option['is_checked']) && $option['is_checked']) $is_selected = (
? 'selected="selected"' : ''; (isset($option['is_checked']) && $option['is_checked'])
$html .= '<option value="'.$option['value'].'" '.$is_selected.'>'; ||
$html .= $option['value']; (self::getFieldValue($block) === $option['value'])
) ? 'selected="selected"' : '';
if(is_array($option['value'])) {
$value = key($option['value']);
$label = reset($option['value']);
} else {
$value = $option['value'];
$label = $option['value'];
}
$html .= '<option value="'.$value.'" '.$is_selected.'>';
$html .= esc_attr($label);
$html .= '</option>'; $html .= '</option>';
} }
$html .= '</select>'; $html .= '</select>';

View File

@ -39,8 +39,7 @@ class Renderer {
} }
} }
// private: rendering methods static function renderBlocks($blocks = array()) {
private static function renderBlocks($blocks = array()) {
$html = ''; $html = '';
foreach ($blocks as $key => $block) { foreach ($blocks as $key => $block) {
$html .= static::renderBlock($block)."\n"; $html .= static::renderBlock($block)."\n";
@ -49,7 +48,7 @@ class Renderer {
return $html; return $html;
} }
private static function renderBlock($block = array()) { static function renderBlock($block = array()) {
$html = ''; $html = '';
switch($block['type']) { switch($block['type']) {
case 'html': case 'html':

View File

@ -17,7 +17,7 @@ class Styles {
} }
/* labels */ /* labels */
.mailpoet_input_label, .mailpoet_text_label,
.mailpoet_textarea_label, .mailpoet_textarea_label,
.mailpoet_select_label, .mailpoet_select_label,
.mailpoet_radio_label, .mailpoet_radio_label,
@ -28,7 +28,7 @@ class Styles {
} }
/* inputs */ /* inputs */
.mailpoet_input, .mailpoet_text,
.mailpoet_textarea, .mailpoet_textarea,
.mailpoet_select, .mailpoet_select,
.mailpoet_date { .mailpoet_date {
@ -36,9 +36,7 @@ class Styles {
} }
.mailpoet_checkbox { .mailpoet_checkbox {
display:inline;
margin-right: 5px;
vertical-align:middle;
} }
.mailpoet_validate_success { .mailpoet_validate_success {

View File

@ -93,7 +93,7 @@ class Handler {
return array( return array(
'count' => $count, 'count' => $count,
'filters' => $this->model->filter('filters'), 'filters' => $this->model->filter('filters', $this->data['group']),
'groups' => $this->model->filter('groups'), 'groups' => $this->model->filter('groups'),
'items' => $items 'items' => $items
); );

View File

@ -110,7 +110,7 @@ class Model extends \Sudzy\ValidModel {
$total = $orm->count(); $total = $orm->count();
if($total > 0) { if($total > 0) {
$models = $orm->select('id') $models = $orm->select(static::$_table.'.id')
->offset(null) ->offset(null)
->limit(null) ->limit(null)
->findArray(); ->findArray();

View File

@ -73,16 +73,20 @@ class Newsletter extends Model {
} }
function withSendingQueue() { function withSendingQueue() {
$this->queue = $this->getQueue(); $queue = $this->getQueue();
if($queue === false) {
$this->queue = false;
} else {
$this->queue = $queue->asArray();
}
return $this; return $this;
} }
static function search($orm, $search = '') { static function search($orm, $search = '') {
return $orm->where_like('subject', '%' . $search . '%'); return $orm->where_like('subject', '%' . $search . '%');
} }
static function filters() { static function filters($orm, $group = 'all') {
$segments = Segment::orderByAsc('name')->findMany(); $segments = Segment::orderByAsc('name')->findMany();
$segment_list = array(); $segment_list = array();
$segment_list[] = array( $segment_list[] = array(
@ -91,7 +95,9 @@ class Newsletter extends Model {
); );
foreach($segments as $segment) { foreach($segments as $segment) {
$newsletters_count = $segment->newsletters()->count(); $newsletters_count = $segment->newsletters()
->filter('groupBy', $group)
->count();
if($newsletters_count > 0) { if($newsletters_count > 0) {
$segment_list[] = array( $segment_list[] = array(
'label' => sprintf('%s (%d)', $segment->name, $newsletters_count), 'label' => sprintf('%s (%d)', $segment->name, $newsletters_count),

View File

@ -35,7 +35,7 @@ class Segment extends Model {
__NAMESPACE__.'\SubscriberSegment', __NAMESPACE__.'\SubscriberSegment',
'segment_id', 'segment_id',
'subscriber_id' 'subscriber_id'
); )->where(MP_SUBSCRIBER_SEGMENT_TABLE.'.status', 'subscribed');
} }
function duplicate($data = array()) { function duplicate($data = array()) {
@ -186,6 +186,6 @@ class Segment extends Model {
} }
static function getPublic() { static function getPublic() {
return self::getPublished()->where('type', 'default'); return self::getPublished()->where('type', 'default')->orderByAsc('name');
} }
} }

View File

@ -6,6 +6,8 @@ if (!defined('ABSPATH')) exit;
class Setting extends Model { class Setting extends Model {
public static $_table = MP_SETTINGS_TABLE; public static $_table = MP_SETTINGS_TABLE;
public static $defaults = null;
function __construct() { function __construct() {
parent::__construct(); parent::__construct();
@ -14,8 +16,26 @@ class Setting extends Model {
)); ));
} }
public static function getDefaults() {
if(self::$defaults === null) {
self::loadDefaults();
}
return self::$defaults;
}
public static function loadDefaults() {
self::$defaults = array(
'signup_confirmation' => array(
'enabled' => true,
'subject' => sprintf(__('Confirm your subscription to %1$s'), get_option('blogname')),
'body' => __("Hello!\n\nHurray! You've subscribed to our site.\nWe need you to activate your subscription to the list(s): [lists_to_confirm] by clicking the link below: \n\n[activation_link]Click here to confirm your subscription.[/activation_link]\n\nThank you,\n\nThe team!")
)
);
}
public static function getValue($key, $default = null) { public static function getValue($key, $default = null) {
$keys = explode('.', $key); $keys = explode('.', $key);
$defaults = self::getDefaults();
if(count($keys) === 1) { if(count($keys) === 1) {
$setting = Setting::where('name', $key)->findOne(); $setting = Setting::where('name', $key)->findOne();
@ -23,9 +43,14 @@ class Setting extends Model {
return $default; return $default;
} else { } else {
if(is_serialized($setting->value)) { if(is_serialized($setting->value)) {
return unserialize($setting->value); $value = unserialize($setting->value);
} else { } else {
return $setting->value; $value = $setting->value;
}
if(is_array($value) && array_key_exists($key, $defaults)) {
return array_replace_recursive($defaults[$key], $value);
} else {
return $value;
} }
} }
} else { } else {
@ -93,7 +118,7 @@ class Setting extends Model {
$settings[$setting->name] = $value; $settings[$setting->name] = $value;
} }
} }
return $settings; return array_replace_recursive(self::getDefaults(), $settings);
} }
public static function createOrUpdate($data = array()) { public static function createOrUpdate($data = array()) {

View File

@ -1,12 +1,17 @@
<?php <?php
namespace MailPoet\Models; namespace MailPoet\Models;
use MailPoet\Mailer\Mailer;
use MailPoet\Util\Helpers; use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class Subscriber extends Model { class Subscriber extends Model {
public static $_table = MP_SUBSCRIBERS_TABLE; public static $_table = MP_SUBSCRIBERS_TABLE;
const STATUS_SUBSCRIBED = 'subscribed';
const STATUS_UNSUBSCRIBED = 'unsubscribed';
const STATUS_UNCONFIRMED = 'unconfirmed';
function __construct() { function __construct() {
parent::__construct(); parent::__construct();
@ -16,13 +21,22 @@ class Subscriber extends Model {
)); ));
} }
static function findOne($id = null) {
if(is_int($id) || (string)(int)$id === $id) {
return parent::findOne($id);
} else {
return parent::where('email', $id)->findOne();
}
}
function segments() { function segments() {
return $this->has_many_through( return $this->has_many_through(
__NAMESPACE__.'\Segment', __NAMESPACE__.'\Segment',
__NAMESPACE__.'\SubscriberSegment', __NAMESPACE__.'\SubscriberSegment',
'subscriber_id', 'subscriber_id',
'segment_id' 'segment_id'
); )
->where(MP_SUBSCRIBER_SEGMENT_TABLE.'.status', self::STATUS_SUBSCRIBED);
} }
function delete() { function delete() {
@ -56,10 +70,116 @@ class Subscriber extends Model {
} }
} }
function sendConfirmationEmail() { function getConfirmationUrl() {
$this->set('status', 'unconfirmed'); $post = get_post(Setting::getValue('signup_confirmation.page'));
return $this->getSubscriptionUrl($post, 'confirm');
}
// TODO function getEditSubscriptionUrl() {
$post = get_post(Setting::getValue('subscription.page'));
return $this->getSubscriptionUrl($post, 'edit');
}
function getUnsubscribeUrl() {
$post = get_post(Setting::getValue('subscription.page'));
return $this->getSubscriptionUrl($post, 'unsubscribe');
}
private function getSubscriptionUrl($post = null, $action = null) {
if($post === null || $action === null) return;
$url = get_permalink($post);
$params = array(
'mailpoet_action='.$action,
'mailpoet_token='.self::generateToken($this->email),
'mailpoet_email='.$this->email
);
// add parameters
$url .= (parse_url($url, PHP_URL_QUERY) ? '&' : '?').join('&', $params);
$url_params = parse_url($url);
if(empty($url_params['scheme'])) {
$url = get_bloginfo('url').$url;
}
return $url;
}
function sendConfirmationEmail() {
if($this->status === self::STATUS_UNCONFIRMED) {
$signup_confirmation = Setting::getValue('signup_confirmation');
$segments = $this->segments()->findMany();
$segment_names = array_map(function($segment) {
return $segment->name;
}, $segments);
$body = nl2br($signup_confirmation['body']);
// replace list of segments shortcode
$body = str_replace(
'[lists_to_confirm]',
'<strong>'.join(', ', $segment_names).'</strong>',
$body
);
// replace activation link
$body = str_replace(
array(
'[activation_link]',
'[/activation_link]'
),
array(
'<a href="'.htmlentities($this->getConfirmationUrl()).'">',
'</a>'
),
$body
);
// build email data
$email = array(
'subject' => $signup_confirmation['subject'],
'body' => array(
'html' => $body,
'text' => $body
)
);
// convert subscriber to array
$subscriber = $this->asArray();
// set from
$from = (
!empty($signup_confirmation['from'])
&& !empty($signup_confirmation['from']['email'])
) ? $signup_confirmation['from']
: false;
// set reply to
$reply_to = (
!empty($signup_confirmation['reply_to'])
&& !empty($signup_confirmation['reply_to']['email'])
) ? $signup_confirmation['reply_to']
: false;
// send email
try {
$mailer = new Mailer(false, $from, $reply_to);
return $mailer->send($email, $subscriber);
} catch(\Exception $e) {
$this->setError($e->getMessage());
return false;
}
}
return false;
}
static function generateToken($email = null) {
if($email !== null) {
return md5(AUTH_KEY.$email);
}
return false;
} }
static function subscribe($subscriber_data = array(), $segment_ids = array()) { static function subscribe($subscriber_data = array(), $segment_ids = array()) {
@ -68,19 +188,22 @@ class Subscriber extends Model {
} }
$subscriber = self::createOrUpdate($subscriber_data); $subscriber = self::createOrUpdate($subscriber_data);
$errors = $subscriber->getErrors();
if($errors === false && $subscriber->id > 0) {
$subscriber = self::findOne($subscriber->id);
if($subscriber !== false && $subscriber->id() > 0) {
// restore deleted subscriber // restore deleted subscriber
if($subscriber->deleted_at !== NULL) { if($subscriber->deleted_at !== NULL) {
$subscriber->setExpr('deleted_at', 'NULL'); $subscriber->setExpr('deleted_at', 'NULL');
} }
if((bool)Setting::getValue('signup_confirmation.enabled')) { if((bool)Setting::getValue('signup_confirmation.enabled')) {
if($subscriber->status !== 'subscribed') { if($subscriber->status !== self::STATUS_SUBSCRIBED) {
$subscriber->sendConfirmationEmail(); $subscriber->sendConfirmationEmail();
} }
} else { } else {
$subscriber->set('status', 'subscribed'); $subscriber->set('status', self::STATUS_SUBSCRIBED);
} }
if($subscriber->save()) { if($subscriber->save()) {
@ -102,7 +225,7 @@ class Subscriber extends Model {
); );
} }
static function filters() { static function filters($orm, $group = 'all') {
$segments = Segment::orderByAsc('name')->findMany(); $segments = Segment::orderByAsc('name')->findMany();
$segment_list = array(); $segment_list = array();
$segment_list[] = array( $segment_list[] = array(
@ -119,8 +242,9 @@ class Subscriber extends Model {
foreach($segments as $segment) { foreach($segments as $segment) {
$subscribers_count = $segment->subscribers() $subscribers_count = $segment->subscribers()
->whereNull('deleted_at') ->filter('groupBy', $group)
->count(); ->count();
$segment_list[] = array( $segment_list[] = array(
'label' => sprintf('%s (%d)', $segment->name, $subscribers_count), 'label' => sprintf('%s (%d)', $segment->name, $subscribers_count),
'value' => $segment->id() 'value' => $segment->id()
@ -161,19 +285,19 @@ class Subscriber extends Model {
'count' => self::getPublished()->count() 'count' => self::getPublished()->count()
), ),
array( array(
'name' => 'subscribed', 'name' => self::STATUS_SUBSCRIBED,
'label' => __('Subscribed'), 'label' => __('Subscribed'),
'count' => self::filter('subscribed')->count() 'count' => self::filter(self::STATUS_SUBSCRIBED)->count()
), ),
array( array(
'name' => 'unconfirmed', 'name' => self::STATUS_UNCONFIRMED,
'label' => __('Unconfirmed'), 'label' => __('Unconfirmed'),
'count' => self::filter('unconfirmed')->count() 'count' => self::filter(self::STATUS_UNCONFIRMED)->count()
), ),
array( array(
'name' => 'unsubscribed', 'name' => self::STATUS_UNSUBSCRIBED,
'label' => __('Unsubscribed'), 'label' => __('Unsubscribed'),
'count' => self::filter('unsubscribed')->count() 'count' => self::filter(self::STATUS_UNSUBSCRIBED)->count()
), ),
array( array(
'name' => 'trash', 'name' => 'trash',
@ -412,21 +536,23 @@ class Subscriber extends Model {
static function bulkConfirmUnconfirmed($orm) { static function bulkConfirmUnconfirmed($orm) {
$subscribers = $orm->findResultSet(); $subscribers = $orm->findResultSet();
$subscribers->set('status', 'subscribed')->save(); $subscribers->set('status', self::STATUS_SUBSCRIBED)->save();
return $subscribers->count(); return $subscribers->count();
} }
static function bulkResendConfirmationEmail($orm) { static function bulkSendConfirmationEmail($orm) {
$subscribers = $orm $subscribers = $orm
->where('status', 'unconfirmed') ->where('status', self::STATUS_UNCONFIRMED)
->findResultSet(); ->findMany();
$emails_sent = 0;
if(!empty($subscribers)) { if(!empty($subscribers)) {
foreach($subscribers as $subscriber) { foreach($subscribers as $subscriber) {
$subscriber->sendConfirmationEmail(); if($subscriber->sendConfirmationEmail()) {
$emails_sent++;
}
} }
return $emails_sent;
return $subscribers->count();
} }
return false; return false;
} }
@ -467,19 +593,19 @@ class Subscriber extends Model {
static function subscribed($orm) { static function subscribed($orm) {
return $orm return $orm
->whereNull('deleted_at') ->whereNull('deleted_at')
->where('status', 'subscribed'); ->where('status', self::STATUS_SUBSCRIBED);
} }
static function unsubscribed($orm) { static function unsubscribed($orm) {
return $orm return $orm
->whereNull('deleted_at') ->whereNull('deleted_at')
->where('status', 'unsubscribed'); ->where('status', self::STATUS_UNSUBSCRIBED);
} }
static function unconfirmed($orm) { static function unconfirmed($orm) {
return $orm return $orm
->whereNull('deleted_at') ->whereNull('deleted_at')
->where('status', 'unconfirmed'); ->where('status', self::STATUS_UNCONFIRMED);
} }
static function withoutSegments($orm) { static function withoutSegments($orm) {

View File

@ -18,7 +18,7 @@ class SubscriberSegment extends Model {
SubscriberSegment::where('subscriber_id', $subscriber->id) SubscriberSegment::where('subscriber_id', $subscriber->id)
->whereNotIn('segment_id', $segment_ids) ->whereNotIn('segment_id', $segment_ids)
->findResultSet() ->findResultSet()
->set('status', 'unsubscribed') ->set('status', Subscriber::STATUS_UNSUBSCRIBED)
->save(); ->save();
// subscribe to segments // subscribe to segments
@ -26,7 +26,7 @@ class SubscriberSegment extends Model {
self::createOrUpdate(array( self::createOrUpdate(array(
'subscriber_id' => $subscriber->id, 'subscriber_id' => $subscriber->id,
'segment_id' => $segment_id, 'segment_id' => $segment_id,
'status' => 'subscribed' 'status' => Subscriber::STATUS_SUBSCRIBED
)); ));
} }
} }
@ -34,29 +34,8 @@ class SubscriberSegment extends Model {
return $subscriber; return $subscriber;
} }
static function filterWithCustomFields($orm) {
$orm = $orm->select(MP_SUBSCRIBERS_TABLE.'.*');
$customFields = CustomField::findArray();
foreach ($customFields as $customField) {
$orm = $orm->select_expr(
'CASE WHEN ' .
MP_CUSTOM_FIELDS_TABLE . '.id=' . $customField['id'] . ' THEN ' .
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE . '.value END as "' . $customField['name'].'"');
}
$orm = $orm
->left_outer_join(
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE,
array(MP_SUBSCRIBERS_TABLE.'.id', '=',
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE.'.subscriber_id'))
->left_outer_join(
MP_CUSTOM_FIELDS_TABLE,
array(MP_CUSTOM_FIELDS_TABLE.'.id','=',
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE.'.custom_field_id'));
return $orm;
}
static function subscribed($orm) { static function subscribed($orm) {
return $orm->where('status', 'subscribed'); return $orm->where('status', Subscriber::STATUS_SUBSCRIBED);
} }
static function createOrUpdate($data = array()) { static function createOrUpdate($data = array()) {

View File

@ -19,7 +19,7 @@ class Footer {
} }
$template = ' $template = '
<tr> <tr>
<td class="mailpoet_padded_header_footer mailpoet_footer" bgcolor="' . $element['styles']['block']['backgroundColor'] . '" <td class="mailpoet_header_footer_padded mailpoet_footer" bgcolor="' . $element['styles']['block']['backgroundColor'] . '"
style="' . StylesHelper::getBlockStyles($element) . StylesHelper::getStyles($element['styles'], 'text') . '"> style="' . StylesHelper::getBlockStyles($element) . StylesHelper::getStyles($element['styles'], 'text') . '">
' . $DOM->html() . ' ' . $DOM->html() . '
</td> </td>

View File

@ -19,7 +19,7 @@ class Header {
} }
$template = ' $template = '
<tr> <tr>
<td class="mailpoet_padded_header_footer mailpoet_header" bgcolor="' . $element['styles']['block']['backgroundColor'] . '" <td class="mailpoet_header_footer_padded mailpoet_header" bgcolor="' . $element['styles']['block']['backgroundColor'] . '"
style="' . StylesHelper::getBlockStyles($element) . StylesHelper::getStyles($element['styles'], 'text') . '"> style="' . StylesHelper::getBlockStyles($element) . StylesHelper::getStyles($element['styles'], 'text') . '">
' . $DOM->html() . ' ' . $DOM->html() . '
</td> </td>

View File

@ -10,7 +10,8 @@ class Renderer {
$template = ($columns_count === 1) ? $template = ($columns_count === 1) ?
$this->getOneColumnTemplate($styles, $class) : $this->getOneColumnTemplate($styles, $class) :
$this->getMultipleColumnsTemplate($styles, $width, $alignment, $class); $this->getMultipleColumnsTemplate($styles, $width, $alignment, $class);
$result = array_map(function ($content) use ($template) { $result = array_map(function($content) use ($template) {
$content = self::removePaddingFromLastElement($content);
return $template['content_start'] . $content . $template['content_end']; return $template['content_start'] . $content . $template['content_end'];
}, $columns_data); }, $columns_data);
$result = implode('', $result); $result = implode('', $result);
@ -75,4 +76,8 @@ class Renderer {
</tr>'; </tr>';
return $template; return $template;
} }
function removePaddingFromLastElement($element) {
return preg_replace('/mailpoet_padded(?!.*mailpoet_padded)/ism', '', $element);
}
} }

View File

@ -40,7 +40,7 @@
padding-right: 20px; padding-right: 20px;
padding-bottom: 20px; padding-bottom: 20px;
} }
.mailpoet_padded_header_footer { .mailpoet_header_footer_padded {
padding: 10px 20px; padding: 10px 20px;
} }
@media screen and (max-width: 599px) and (-webkit-min-device-pixel-ratio: 1) { @media screen and (max-width: 599px) and (-webkit-min-device-pixel-ratio: 1) {

View File

@ -15,12 +15,26 @@ class Router {
); );
add_action( add_action(
'wp_ajax_mailpoet', 'wp_ajax_mailpoet',
array($this, 'setup') array($this, 'setupAdmin')
);
add_action(
'wp_ajax_nopriv_mailpoet',
array($this, 'setupPublic')
); );
} }
function setup() { function setupAdmin() {
$this->securityCheck(); $this->verifyToken();
$this->checkPermissions();
return $this->processRoute();
}
function setupPublic() {
$this->verifyToken();
return $this->processRoute();
}
function processRoute() {
$class = ucfirst($_POST['endpoint']); $class = ucfirst($_POST['endpoint']);
$endpoint = __NAMESPACE__ . "\\" . $class; $endpoint = __NAMESPACE__ . "\\" . $class;
$method = $_POST['method']; $method = $_POST['method'];
@ -43,8 +57,11 @@ class Router {
echo $global; echo $global;
} }
function securityCheck() { function checkPermissions() {
if(!current_user_can('manage_options')) { die(); } if(!current_user_can('manage_options')) { die(); }
}
function verifyToken() {
if(!wp_verify_nonce($_POST['token'], 'mailpoet_token')) { die(); } if(!wp_verify_nonce($_POST['token'], 'mailpoet_token')) { die(); }
} }
} }

View File

@ -15,7 +15,7 @@ class Subscribers {
function __construct() { function __construct() {
} }
function get($id = false) { function get($id = null) {
$subscriber = Subscriber::findOne($id); $subscriber = Subscriber::findOne($id);
if($subscriber !== false) { if($subscriber !== false) {
$subscriber = $subscriber $subscriber = $subscriber
@ -88,18 +88,10 @@ class Subscribers {
} }
$subscriber = Subscriber::subscribe($data, $segment_ids); $subscriber = Subscriber::subscribe($data, $segment_ids);
if($subscriber->getErrors() !== false) {
$result = false;
if($subscriber === false || !$subscriber->id()) {
$errors = array_merge($errors, $subscriber->getValidationErrors());
} else {
$result = true;
}
if(!empty($errors)) {
return array( return array(
'result' => false, 'result' => false,
'errors' => $errors 'errors' => $subscriber->getErrors()
); );
} }

View File

@ -13,13 +13,13 @@ class Pages {
), ),
'public' => true, 'public' => true,
'has_archive' => false, 'has_archive' => false,
'show_ui' => true, 'show_ui' => WP_DEBUG,
'show_in_menu' => false, 'show_in_menu' => WP_DEBUG,
'rewrite' => false, 'rewrite' => false,
'show_in_nav_menus'=>false, 'show_in_nav_menus' => false,
'can_export'=>false, 'can_export' => false,
'publicly_queryable'=>true, 'publicly_queryable' => true,
'exclude_from_search'=>true 'exclude_from_search' => true
)); ));
} }
@ -65,7 +65,9 @@ class Pages {
return array( return array(
'id' => $page->ID, 'id' => $page->ID,
'title' => $page->post_title, 'title' => $page->post_title,
'preview_url' => get_permalink($page->ID), 'preview_url' => add_query_arg(array(
'preview' => 1
), get_permalink($page->ID)),
'edit_url' => get_edit_post_link($page->ID) 'edit_url' => get_edit_post_link($page->ID)
); );
} }

View File

@ -17,6 +17,8 @@ class BootStrapMenu {
Segment::getSegmentsWithSubscriberCount() : Segment::getSegmentsWithSubscriberCount() :
Segment::getSegmentsForExport($with_confirmed_subscribers); Segment::getSegmentsForExport($with_confirmed_subscribers);
return array_map(function($segment) { return array_map(function($segment) {
if (!$segment['name']) $segment['name'] = __('Not In Segment');
if (!$segment['id']) $segment['id'] = 0;
return array( return array(
'id' => $segment['id'], 'id' => $segment['id'],
'name' => $segment['name'], 'name' => $segment['name'],
@ -31,7 +33,7 @@ class BootStrapMenu {
'first_name' => __('First name'), 'first_name' => __('First name'),
'last_name' => __('Last name'), 'last_name' => __('Last name'),
'status' => __('Status') 'status' => __('Status')
// TODO: add additional fiels from MP2 // TODO: add additional fields from MP2
/* /*
'confirmed_ip' => __('IP address') 'confirmed_ip' => __('IP address')
'confirmed_at' => __('Subscription date') 'confirmed_at' => __('Subscription date')

View File

@ -17,22 +17,30 @@ class Export {
public $segments; public $segments;
public $subscribers_without_segment; public $subscribers_without_segment;
public $subscriber_fields; public $subscriber_fields;
public $subscriber_custom_fields;
public $formatted_subscriber_fields;
public $export_path; public $export_path;
public $export_file; public $export_file;
public $export_file_URL; public $export_file_URL;
public $profiler_start; public $subscriber_batch_size;
public function __construct($data) { public function __construct($data) {
set_time_limit(0);
$this->export_confirmed_option = $data['export_confirmed_option']; $this->export_confirmed_option = $data['export_confirmed_option'];
$this->export_format_option = $data['export_format_option']; $this->export_format_option = $data['export_format_option'];
$this->group_by_segment_option = $data['group_by_segment_option']; $this->group_by_segment_option = $data['group_by_segment_option'];
$this->segments = $data['segments']; $this->segments = $data['segments'];
$this->subscribers_without_segment = array_search(0, $this->segments); $this->subscribers_without_segment = array_search(0, $this->segments);
$this->subscriber_fields = $data['subscriber_fields']; $this->subscriber_fields = $data['subscriber_fields'];
$this->subscriber_custom_fields = $this->getSubscriberCustomFields();
$this->formatted_subscriber_fields = $this->formatSubscriberFields(
$this->subscriber_fields,
$this->subscriber_custom_fields
);
$this->export_path = Env::$temp_path; $this->export_path = Env::$temp_path;
$this->export_file = $this->getExportFile($this->export_format_option); $this->export_file = $this->getExportFile($this->export_format_option);
$this->export_file_URL = $this->getExportFileURL($this->export_file); $this->export_file_URL = $this->getExportFileURL($this->export_file);
$this->profiler_start = microtime(true); $this->subscriber_batch_size = 15000;
} }
function process() { function process() {
@ -40,75 +48,12 @@ class Export {
if(is_writable($this->export_path) === false) { if(is_writable($this->export_path) === false) {
throw new \Exception(__("Couldn't save export file on the server.")); throw new \Exception(__("Couldn't save export file on the server."));
} }
$subscribers = $this->getSubscribers(); $processed_subscribers = call_user_func(
$subscriber_custom_fields = $this->getSubscriberCustomFields(); array(
$formatted_subscriber_fields = $this->formatSubscriberFields( $this,
$this->subscriber_fields, 'generate' . strtoupper($this->export_format_option)
$subscriber_custom_fields )
); );
if($this->export_format_option === 'csv') {
$CSV_file = fopen($this->export_file, 'w');
$format_CSV = function($row) {
return '"' . str_replace('"', '\"', $row) . '"';
};
// add UTF-8 BOM (3 bytes, hex EF BB BF) at the start of the file for
// Excel to automatically recognize the encoding
fwrite($CSV_file, chr(0xEF) . chr(0xBB) . chr(0xBF));
if($this->group_by_segment_option) {
$formatted_subscriber_fields[] = __('Segment');
}
fwrite(
$CSV_file,
implode(
',',
array_map(
$format_CSV,
$formatted_subscriber_fields
)
) . "\n"
);
foreach($subscribers as $subscriber) {
$row = $this->formatSubscriberData($subscriber);
if($this->group_by_segment_option) {
$row[] = ucwords($subscriber['segment_name']);
}
fwrite($CSV_file, implode(',', array_map($format_CSV, $row)) . "\n");
}
fclose($CSV_file);
} else {
$writer = new XLSXWriter();
$writer->setAuthor('MailPoet (www.mailpoet.com)');
$header_row = array($formatted_subscriber_fields);
$last_segment = false;
$rows = array();
foreach($subscribers as $subscriber) {
if($last_segment && $last_segment !== $subscriber['segment_name'] &&
$this->group_by_segment_option
) {
$writer->writeSheet(
array_merge($header_row, $rows), ucwords($last_segment)
);
$rows = array();
}
// detect RTL language and set Excel to properly display the sheet
$RTL_regex = '/\p{Arabic}|\p{Hebrew}/u';
if(!$writer->rtl && (
preg_grep($RTL_regex, $subscriber) ||
preg_grep($RTL_regex, $formatted_subscriber_fields))
) {
$writer->rtl = true;
}
$rows[] = $this->formatSubscriberData($subscriber);
$last_segment = $subscriber['segment_name'];
}
$writer->writeSheet(
array_merge($header_row, $rows),
($this->group_by_segment_option) ?
ucwords($subscriber['segment_name']) :
__('All Segments')
);
$writer->writeToFile($this->export_file);
}
} catch(\Exception $e) { } catch(\Exception $e) {
return array( return array(
'result' => false, 'result' => false,
@ -118,14 +63,118 @@ class Export {
return array( return array(
'result' => true, 'result' => true,
'data' => array( 'data' => array(
'totalExported' => count($subscribers), 'totalExported' => $processed_subscribers,
'exportFileURL' => $this->export_file_URL 'exportFileURL' => $this->export_file_URL
), )
'profiler' => $this->timeExecution()
); );
} }
function getSubscribers() { function generateCSV() {
$processed_subscribers = 0;
$offset = 0;
$formatted_subscriber_fields = $this->formatted_subscriber_fields;
$CSV_file = fopen($this->export_file, 'w');
$format_CSV = function($row) {
return '"' . str_replace('"', '\"', $row) . '"';
};
// add UTF-8 BOM (3 bytes, hex EF BB BF) at the start of the file for
// Excel to automatically recognize the encoding
fwrite($CSV_file, chr(0xEF) . chr(0xBB) . chr(0xBF));
if($this->group_by_segment_option) {
$formatted_subscriber_fields[] = __('Segment');
}
fwrite(
$CSV_file,
implode(
',',
array_map(
$format_CSV,
$formatted_subscriber_fields
)
) . PHP_EOL
);
do {
$subscribers = $this->getSubscribers($offset, $this->subscriber_batch_size);
$processed_subscribers += count($subscribers);
foreach($subscribers as $subscriber) {
$row = $this->formatSubscriberData($subscriber);
if($this->group_by_segment_option) {
$row[] = ucwords($subscriber['segment_name']);
}
fwrite($CSV_file, implode(',', array_map($format_CSV, $row)) . "\n");
}
$offset += $this->subscriber_batch_size;
} while(count($subscribers) === $this->subscriber_batch_size);
fclose($CSV_file);
return $processed_subscribers;
}
function generateXLSX() {
$processed_subscribers = 0;
$offset = 0;
$XLSX_writer = new XLSXWriter();
$XLSX_writer->setAuthor('MailPoet (www.mailpoet.com)');
$last_segment = false;
$processed_segments = array();
do {
$subscribers = $this->getSubscribers($offset, $this->subscriber_batch_size);
$processed_subscribers += count($subscribers);
foreach($subscribers as $i => $subscriber) {
$current_segment = ucwords($subscriber['segment_name']);
// Sheet header (1st row) will be written only if:
// * This is the first time we're processing a segment
// * "Group by subscriber option" is turned AND the previous subscriber's
// segment is different from the current subscriber's segment
// Header will NOT be written if:
// * We have already processed the segment. Because SQL results are not
// sorted by segment name (due to slow queries when using ORDER BY and LIMIT),
// we need to keep track of processed segments so that we do not create header
// multiple times when switching from one segment to another and back.
if((!count($processed_segments) ||
($last_segment !== $current_segment && $this->group_by_segment_option)
) &&
(!in_array($last_segment, $processed_segments) ||
!in_array($current_segment, $processed_segments)
)
) {
$this->writeXLSX(
$XLSX_writer,
$subscriber['segment_name'],
$this->formatted_subscriber_fields
);
$processed_segments[] = $current_segment;
}
$last_segment = ucwords($subscriber['segment_name']);
// detect RTL language and set Excel to properly display the sheet
$RTL_regex = '/\p{Arabic}|\p{Hebrew}/u';
if(!$XLSX_writer->rtl && (
preg_grep($RTL_regex, $subscriber) ||
preg_grep($RTL_regex, $this->formatted_subscriber_fields))
) {
$XLSX_writer->rtl = true;
}
$this->writeXLSX(
$XLSX_writer,
$last_segment,
$this->formatSubscriberData($subscriber)
);
}
$offset += $this->subscriber_batch_size;
} while(count($subscribers) === $this->subscriber_batch_size);
$XLSX_writer->writeToFile($this->export_file);
return $processed_subscribers;
}
function writeXLSX($XLSX_writer, $segment, $data) {
return $XLSX_writer->writeSheetRow(
($this->group_by_segment_option) ?
ucwords($segment) :
__('All Segments'),
$data
);
}
function getSubscribers($offset, $limit) {
$subscribers = Subscriber:: $subscribers = Subscriber::
left_outer_join( left_outer_join(
SubscriberSegment::$_table, SubscriberSegment::$_table,
@ -141,7 +190,6 @@ class Export {
'=', '=',
SubscriberSegment::$_table . '.segment_id' SubscriberSegment::$_table . '.segment_id'
)) ))
->orderByAsc('segment_name')
->filter('filterWithCustomFieldsForExport'); ->filter('filterWithCustomFieldsForExport');
if($this->subscribers_without_segment !== false) { if($this->subscribers_without_segment !== false) {
$subscribers = $subscribers $subscribers = $subscribers
@ -168,9 +216,11 @@ class Export {
$subscribers = $subscribers =
$subscribers->where(Subscriber::$_table . '.status', 'subscribed'); $subscribers->where(Subscriber::$_table . '.status', 'subscribed');
} }
$subscribers = $subscribers->whereNull(Subscriber::$_table . '.deleted_at'); $subscribers = $subscribers
->whereNull(Subscriber::$_table . '.deleted_at')
return $subscribers->findArray(); ->limit(sprintf('%d, %d', $offset, $limit))
->findArray();
return $subscribers;
} }
function getExportFileURL($file) { function getExportFileURL($file) {
@ -216,9 +266,4 @@ class Export {
return $subscriber[$field]; return $subscriber[$field];
}, $this->subscriber_fields); }, $this->subscriber_fields);
} }
function timeExecution() {
$profiler_end = microtime(true);
return ($profiler_end - $this->profiler_start) / 60;
}
} }

344
lib/Subscription/Pages.php Normal file
View File

@ -0,0 +1,344 @@
<?php
namespace MailPoet\Subscription;
use \MailPoet\Router\Subscribers;
use \MailPoet\Models\Subscriber;
use \MailPoet\Models\CustomField;
use \MailPoet\Models\Setting;
use \MailPoet\Models\Segment;
use \MailPoet\Util\Helpers;
use \MailPoet\Util\Url;
class Pages {
const DEMO_EMAIL = 'demo@mailpoet.com';
function __construct() {
}
function init() {
if(isset($_GET['mailpoet_page'])) {
add_filter('wp_title', array($this,'setWindowTitle'));
add_filter('the_title', array($this,'setPageTitle'));
add_filter('the_content', array($this,'setPageContent'));
}
add_action(
'admin_post_mailpoet_subscriber_save',
array($this, 'subscriberSave')
);
add_action(
'admin_post_nopriv_mailpoet_subscriber_save',
array($this, 'subscriberSave')
);
}
function subscriberSave() {
$action = (isset($_POST['action']) ? $_POST['action'] : null);
if($action !== 'mailpoet_subscriber_save') {
Url::redirectBack();
}
$reserved_keywords = array('action', 'mailpoet_redirect');
$subscriber_data = array_diff_key(
$_POST,
array_flip($reserved_keywords)
);
$subscriber = Subscriber::createOrUpdate($subscriber_data);
$errors = $subscriber->getErrors();
// TBD: success/error messages (not present in MP2)
Url::redirectBack();
}
function isPreview() {
return (array_key_exists('preview', $_GET));
}
function setWindowTitle() {
// TODO
}
function setPageTitle($title = null) {
$subscriber = $this->getSubscriber();
switch($this->getAction()) {
case 'confirm':
return $this->getConfirmTitle($subscriber);
break;
case 'edit':
return $this->getEditTitle($subscriber);
break;
case 'unsubscribe':
if($subscriber !== false) {
if($subscriber->status !== Subscriber::STATUS_UNSUBSCRIBED) {
$subscriber->status = Subscriber::STATUS_UNSUBSCRIBED;
$subscriber->save();
}
}
return $this->getUnsubscribeTitle($subscriber);
break;
}
return $title;
}
function setPageContent($page_content = '[mailpoet_page]') {
$content = '';
$subscriber = $this->getSubscriber();
switch($this->getAction()) {
case 'confirm':
$content = $this->getConfirmContent($subscriber);
break;
case 'edit':
$content = $this->getEditContent($subscriber);
break;
case 'unsubscribe':
$content = $this->getUnsubscribeContent($subscriber);
break;
}
return str_replace('[mailpoet_page]', $content, $page_content);
}
private function getConfirmTitle($subscriber) {
if($this->isPreview()) {
$title = sprintf(
__("You've subscribed to: %s"),
'demo 1, demo 2'
);
} else if($subscriber === false) {
$title = __('Your confirmation link expired, please subscribe again.');
} else {
if($subscriber->status !== Subscriber::STATUS_SUBSCRIBED) {
$subscriber->status = Subscriber::STATUS_SUBSCRIBED;
$subscriber->save();
}
$segment_names = array_map(function($segment) {
return $segment->name;
}, $subscriber->segments()->findMany());
if(empty($segment_names)) {
$title = __("You've subscribed!");
} else {
$title = sprintf(
__("You've subscribed to: %s"),
join(', ', $segment_names)
);
}
}
return $title;
}
private function getEditTitle($subscriber) {
if($this->isPreview()) {
return sprintf(
__('Edit your subscriber profile: %s'),
self::DEMO_EMAIL
);
} else if($subscriber !== false) {
return sprintf(
__('Edit your subscriber profile: %s'),
$subscriber->email
);
}
}
private function getUnsubscribeTitle($subscriber) {
if($this->isPreview() || $subscriber !== false) {
return __("You've unsubscribed!");
}
}
private function getConfirmContent($subscriber) {
if($this->isPreview() || $subscriber !== false) {
return __("Yup, we've added you to our list. You'll hear from us shortly.");
}
}
private function getEditContent($subscriber) {
if($this->isPreview()) {
$subscriber = Subscriber::create();
$subscriber->hydrate(array(
'email' => self::DEMO_EMAIL
));
} else if($subscriber !== false) {
$subscriber = $subscriber
->withCustomFields()
->withSubscriptions();
} else {
return;
}
$custom_fields = array_map(function($custom_field) use($subscriber) {
$custom_field->id = 'cf_'.$custom_field->id;
$custom_field = $custom_field->asArray();
$custom_field['params']['value'] = $subscriber->{$custom_field['id']};
return $custom_field;
}, CustomField::findMany());
$segment_ids = Setting::getValue('subscription.segments', array());
if(!empty($segment_ids)) {
$segments = Segment::getPublic()
->whereIn('id', $segment_ids)
->findMany();
} else {
$segments = Segment::getPublic()
->findMany();
}
$subscribed_segment_ids = array();
if(!empty($subscriber->subscriptions)) {
foreach ($subscriber->subscriptions as $subscription) {
if($subscription['status'] === Subscriber::STATUS_SUBSCRIBED) {
$subscribed_segment_ids[] = $subscription['segment_id'];
}
}
}
$segments = array_map(function($segment) use($subscribed_segment_ids) {
return array(
'id' => $segment->id,
'name' => $segment->name,
'is_checked' => in_array($segment->id, $subscribed_segment_ids)
);
}, $segments);
$fields = array(
array(
'id' => 'email',
'type' => 'text',
'params' => array(
'label' => __('Email'),
'required' => true,
'value' => $subscriber->email
)
),
array(
'id' => 'first_name',
'type' => 'text',
'params' => array(
'label' => __('First name'),
'value' => $subscriber->first_name
)
),
array(
'id' => 'last_name',
'type' => 'text',
'params' => array(
'label' => __('Last name'),
'value' => $subscriber->last_name
)
),
array(
'id' => 'status',
'type' => 'select',
'params' => array(
'label' => __('Status'),
'values' => array(
array(
'value' => array(
Subscriber::STATUS_SUBSCRIBED => __('Subscribed')
),
'is_checked' => (
$subscriber->status === Subscriber::STATUS_SUBSCRIBED
)
),
array(
'value' => array(
Subscriber::STATUS_UNSUBSCRIBED => __('Unsubscribed')
),
'is_checked' => (
$subscriber->status === Subscriber::STATUS_UNSUBSCRIBED
)
),
array(
'value' => array(
Subscriber::STATUS_UNCONFIRMED => __('Unconfirmed')
),
'is_checked' => (
$subscriber->status === Subscriber::STATUS_UNCONFIRMED
)
)
)
)
)
);
$form = array_merge(
$fields,
$custom_fields,
array(
array(
'id' => 'segments',
'type' => 'segment',
'params' => array(
'label' => __('Your lists'),
'values' => $segments
)
),
array(
'id' => 'submit',
'type' => 'submit',
'params' => array(
'label' => __('Save')
)
)
)
);
$form_html = '<form method="POST" '.
'action="'.admin_url('admin-post.php').'" '.
'novalidate>';
$form_html .= '<input type="hidden" name="action" '.
'value="mailpoet_subscriber_save" />';
$form_html .= '<input type="hidden" name="segments" value="" />';
$form_html .= '<input type="hidden" name="mailpoet_redirect" '.
'value="'.Url::getCurrentUrl().'" />';
$form_html .= \MailPoet\Form\Renderer::renderBlocks($form);
$form_html .= '</form>';
return $form_html;
}
private function getUnsubscribeContent($subscriber) {
$content = '';
if($this->isPreview() || $subscriber !== false) {
$content = '<p>'.__("Great, you'll never hear from us again!").'</p>';
if($subscriber !== false) {
$content .= '<p><strong>'.
str_replace(
array('[link]', '[/link]'),
array('<a href="'.$subscriber->getConfirmationUrl().'">', '</a>'),
__('You made a mistake? [link]Undo unsubscribe.[/link]')
).
'</strong></p>';
}
}
return $content;
}
private function getSubscriber() {
$token = (isset($_GET['mailpoet_token']))
? $_GET['mailpoet_token']
: null;
$email = (isset($_GET['mailpoet_email']))
? $_GET['mailpoet_email']
: null;
if(Subscriber::generateToken($email) === $token) {
$subscriber = Subscriber::findOne($email);
if($subscriber !== false) {
return $subscriber;
}
}
return false;
}
private function getAction() {
return (isset($_GET['mailpoet_action']))
? $_GET['mailpoet_action']
: null;
}
}

62
lib/Util/Url.php Normal file
View File

@ -0,0 +1,62 @@
<?php
namespace MailPoet\Util;
class Url {
function __construct() {
}
static function getCurrentUrl() {
global $wp;
return home_url(
add_query_arg(
$wp->query_string,
$wp->request
)
);
}
static function redirectTo($url = null) {
wp_safe_redirect($url);
exit();
}
static function redirectBack() {
// check mailpoet_redirect parameter
$referer = (isset($_POST['mailpoet_redirect'])
? $_POST['mailpoet_redirect']
: null
);
// fallback: http referer
if($referer === null) {
if(!empty($_SERVER['HTTP_REFERER'])) {
$referer = $_SERVER['HTTP_REFERER'];
}
}
// fallback: home_url
if($referer === null) {
$referer = home_url();
}
if($referer !== null) {
self::redirectTo($referer);
}
exit();
}
static function redirectWithReferer($url = null) {
$current_url = self::getCurrentUrl();
$url = add_query_arg(
array(
'mailpoet_redirect' => urlencode($current_url)
),
$url
);
if($url !== $current_url) {
self::redirectTo($url);
}
exit();
}
}

View File

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

View File

@ -19,7 +19,13 @@ class SettingCest {
expect($errors[0])->equals('You need to specify a name.'); expect($errors[0])->equals('You need to specify a name.');
} }
function itCanGetAllSettings() { function itHasDefaultSettings() {
$default_settings = Setting::getDefaults();
expect($default_settings)->notEmpty();
expect($default_settings['signup_confirmation']['enabled'])->true();
}
function itCanGetAllSettingsIncludingDefaults() {
Setting::setValue('key_1', 'value_1'); Setting::setValue('key_1', 'value_1');
Setting::setValue('key_2', 'value_2'); Setting::setValue('key_2', 'value_2');
Setting::setValue('key_3', array( Setting::setValue('key_3', array(
@ -28,13 +34,17 @@ class SettingCest {
)); ));
$settings = Setting::getAll(); $settings = Setting::getAll();
expect(array_keys($settings))->count(3);
expect($settings['key_1'])->equals('value_1'); expect($settings['key_1'])->equals('value_1');
expect($settings['key_2'])->equals('value_2'); expect($settings['key_2'])->equals('value_2');
expect($settings['key_3'])->equals(array( expect($settings['key_3'])->equals(array(
'subkey_1' => 'subvalue_1', 'subkey_1' => 'subvalue_1',
'subkey_2' => 'subvalue_2' 'subkey_2' => 'subvalue_2'
)); ));
// default settings
$default_settings = Setting::getDefaults();
expect($settings['signup_confirmation'])
->equals($default_settings['signup_confirmation']);
} }
function itReturnsDefaultValueIfNotSet() { function itReturnsDefaultValueIfNotSet() {

View File

@ -105,6 +105,26 @@ class NewsletterRendererCest {
expect($rendered_column_content)->equals($column_content); expect($rendered_column_content)->equals($column_content);
} }
function itRemovesPaddingFromLastColumnElement() {
$column_content = array('
<tr><td class="mailpoet_padded"></td></tr>
<tr><td class="mailpoet_padded"></td></tr>
<tr><td class="mailpoet_padded"></td></tr>
<tr><td class="mailpoet_padded"></td></tr>'
);
$column_styles = array(
'block' => array(
'backgroundColor' => "#999999"
)
);
$rendered_column_content = $this->column_renderer->render(
$column_styles,
count($column_content),
$column_content
);
expect(substr_count($rendered_column_content, 'mailpoet_padded'))->equals(3);
}
function itRendersHeader() { function itRendersHeader() {
$newsletter = json_decode($this->newsletter['body'], true); $newsletter = json_decode($this->newsletter['body'], true);
$template = $newsletter['content']['blocks'][0]['blocks'][0]['blocks'][0]; $template = $newsletter['content']['blocks'][0]['blocks'][0]['blocks'][0];

View File

@ -16,7 +16,7 @@ class SettingsCest {
Setting::deleteMany(); Setting::deleteMany();
$settings = $router->get(); $settings = $router->get();
expect($settings)->isEmpty(); expect($settings)->equals(Setting::getDefaults());
} }
function itCanSetSettings() { function itCanSetSettings() {

View File

@ -122,6 +122,15 @@ class ExportCest {
'1' '1'
) )
); );
expect($this->export->subscriber_custom_fields)
->equals($this->export->getSubscriberCustomFields());
expect($this->export->formatted_subscriber_fields)
->equals(
$this->export->formatSubscriberFields(
$this->export->subscriber_fields,
$this->export->subscriber_custom_fields
)
);
expect( expect(
preg_match( preg_match(
'|' . '|' .
@ -137,6 +146,7 @@ class ExportCest {
'|' '|'
, $this->export->export_file_URL) , $this->export->export_file_URL)
)->equals(1); )->equals(1);
expect($this->export->subscriber_batch_size)->notNull();
} }
function itCanGetSubscriberCustomFields() { function itCanGetSubscriberCustomFields() {
@ -156,7 +166,7 @@ class ExportCest {
} }
function itProperlyReturnsSubscriberCustomFields() { function itProperlyReturnsSubscriberCustomFields() {
$subscribers = $this->export->getSubscribers(); $subscribers = $this->export->getSubscribers(0, 10);
foreach($subscribers as $subscriber) { foreach($subscribers as $subscriber) {
if($subscriber['email'] === $this->subscribers_data[1]) { if($subscriber['email'] === $this->subscribers_data[1]) {
expect($subscriber['Country']) expect($subscriber['Country'])
@ -167,37 +177,37 @@ class ExportCest {
function itCanGetSubscribers() { function itCanGetSubscribers() {
$this->export->segments = array(1); $this->export->segments = array(1);
$subscribers = $this->export->getSubscribers(); $subscribers = $this->export->getSubscribers(0, 10);
expect(count($subscribers))->equals(2); expect(count($subscribers))->equals(2);
$this->export->segments = array(2); $this->export->segments = array(2);
$subscribers = $this->export->getSubscribers(); $subscribers = $this->export->getSubscribers(0, 10);
expect(count($subscribers))->equals(2); expect(count($subscribers))->equals(2);
$this->export->segments = array( $this->export->segments = array(
1, 1,
2 2
); );
$subscribers = $this->export->getSubscribers(); $subscribers = $this->export->getSubscribers(0, 10);
expect(count($subscribers))->equals(3); expect(count($subscribers))->equals(3);
} }
function itCanGroupSubscribersBySegments() { function itCanGroupSubscribersBySegments() {
$this->export->group_by_segment_option = true; $this->export->group_by_segment_option = true;
$this->export->subscribers_without_segment = true; $this->export->subscribers_without_segment = true;
$subscribers = $this->export->getSubscribers(); $subscribers = $this->export->getSubscribers(0, 10);
expect(count($subscribers))->equals(5); expect(count($subscribers))->equals(5);
} }
function itCanGetSubscribersOnlyWithoutSegments() { function itCanGetSubscribersOnlyWithoutSegments() {
$this->export->segments = array(0); $this->export->segments = array(0);
$this->export->subscribers_without_segment = true; $this->export->subscribers_without_segment = true;
$subscribers = $this->export->getSubscribers(); $subscribers = $this->export->getSubscribers(0, 10);
expect(count($subscribers))->equals(1); expect(count($subscribers))->equals(1);
expect($subscribers[0]['segment_name'])->equals('Not In Segment'); expect($subscribers[0]['segment_name'])->equals('Not In Segment');
} }
function itCanGetOnlyConfirmedSubscribers() { function itCanGetOnlyConfirmedSubscribers() {
$this->export->export_confirmed_option = true; $this->export->export_confirmed_option = true;
$subscribers = $this->export->getSubscribers(); $subscribers = $this->export->getSubscribers(0, 10);
expect(count($subscribers))->equals(1); expect(count($subscribers))->equals(1);
expect($subscribers[0]['email']) expect($subscribers[0]['email'])
->equals($this->subscribers_data[1]['email']); ->equals($this->subscribers_data[1]['email']);
@ -207,7 +217,7 @@ class ExportCest {
SubscriberSegment::where('subscriber_id', 3) SubscriberSegment::where('subscriber_id', 3)
->findOne() ->findOne()
->delete(); ->delete();
$subscribers = $this->export->getSubscribers(); $subscribers = $this->export->getSubscribers(0, 10);
expect(count($subscribers))->equals(2); expect(count($subscribers))->equals(2);
} }
@ -223,8 +233,8 @@ class ExportCest {
$this->export->export_format_option = 'csv'; $this->export->export_format_option = 'csv';
$this->export->process(); $this->export->process();
$CSV_file_size = filesize($this->export->export_file); $CSV_file_size = filesize($this->export->export_file);
$this->export->export_file = $this->export->getExportFile('xls'); $this->export->export_file = $this->export->getExportFile('xlsx');
$this->export->export_format_option = 'xls'; $this->export->export_format_option = 'xlsx';
$this->export->process(); $this->export->process();
$XLS_file_size = filesize($this->export->export_file); $XLS_file_size = filesize($this->export->export_file);
expect($CSV_file_size)->greaterThan(0); expect($CSV_file_size)->greaterThan(0);

View File

@ -32,6 +32,10 @@
'admin.js' 'admin.js'
)%> )%>
<script type="text/javascript">
var mailpoet_date_format = "<%= get_option('date_format') %>";
</script>
<script> <script>
HS.beacon.config({ HS.beacon.config({
icon: 'message', icon: 'message',

File diff suppressed because it is too large Load Diff

View File

@ -522,7 +522,7 @@
], ],
fonts: [ fonts: [
'Arial', 'Arial',
'Comic Sans', 'Comic Sans MS',
'Courier New', 'Courier New',
'Georgia', 'Georgia',
'Lucida', 'Lucida',
@ -976,10 +976,10 @@
backgroundColor: '#2ea1cd', backgroundColor: '#2ea1cd',
borderColor: '#0074a2', borderColor: '#0074a2',
borderWidth: '1px', borderWidth: '1px',
borderRadius: '7px', borderRadius: '5px',
borderStyle: 'solid', borderStyle: 'solid',
width: '120px', width: '180px',
lineHeight: '35px', lineHeight: '40px',
fontColor: '#ffffff', fontColor: '#ffffff',
fontFamily: 'Verdana', fontFamily: 'Verdana',
fontSize: '18px', fontSize: '18px',
@ -1241,7 +1241,9 @@
customFieldsWindowTitle: customFieldsWindowTitle:
'<%= __('Select a shortcode') %>', '<%= __('Select a shortcode') %>',
unsubscribeLinkMissing: unsubscribeLinkMissing:
'<%= __('Please include an unsubscribe link to continue.') %>', '<%= __('All newsletter must include an "unsubscribe" link. Add a footer widget to your newsletter to continue.') %>',
newsletterPreviewEmailMissing:
'<%= __('Please enter an email where newsletter preview should be sent to.') %>',
newsletterPreviewSent: newsletterPreviewSent:
'<%= __('Newsletter preview email has been successfully sent!') %>', '<%= __('Newsletter preview email has been successfully sent!') %>',
newsletterPreviewFailedToSend: newsletterPreviewFailedToSend:
@ -1264,7 +1266,7 @@
imageMissing: '<%= image_url( imageMissing: '<%= image_url(
"newsletter_editor/image-missing.svg" "newsletter_editor/image-missing.svg"
) %>', ) %>',
} },
}; };
var editor = null; var editor = null;

View File

@ -4,7 +4,7 @@
<div class="mailpoet_form_field"> <div class="mailpoet_form_field">
<label> <label>
<%= __('Send preview to') %><br /> <%= __('Send preview to') %><br />
<input id="mailpoet_preview_to_email" class="mailpoet_input mailpoet_input_full" type="text" name="to_email" value="{{ from_email }}" /> <input id="mailpoet_preview_to_email" class="mailpoet_input mailpoet_input_full" type="text" name="to_email" value="<%= settings.sender.address %>" />
</label> </label>
</div> </div>

View File

@ -194,14 +194,13 @@
<% endif %> <% endif %>
</td> </td>
</tr> </tr>
<!-- manage subscriptions--> <!-- edit subscription-->
<tr> <tr>
<th scope="row"> <th scope="row">
<label for="settings[subscription_edit]"> <label for="subscription_edit_page">
<%= __('Unsubscribe & Manage Subscription page') %> <%= __('Manage Subscription page') %>
<p class="description"> <p class="description">
<%= __('The page your subscribers see when they click to <%= __('The page your subscribers see when they click on "Manage your subscription" in your emails.') %>
"Unsubscribe" or "Manage your subscription" in your emails.') %>
</p> </p>
</label> </label>
</th> </th>
@ -209,14 +208,15 @@
<p> <p>
<select <select
class="mailpoet_page_selection" class="mailpoet_page_selection"
name="subscription[page]" id="subscription_edit_page"
name="subscription[edit_page]"
> >
<% for page in pages %> <% for page in pages %>
<option <option
value="<%= page.id %>" value="<%= page.id %>"
data-preview-url="<%= page.preview_url|raw %>" data-preview-url="<%= page.preview_url|raw %>&amp;mailpoet_action=edit"
data-edit-url="<%= page.edit_url|raw %>" data-edit-url="<%= page.edit_url|raw %>"
<% if(page.id == settings.subscription.page) %> <% if(page.id == settings.subscription.edit_page) %>
selected="selected" selected="selected"
<% endif %> <% endif %>
><%= page.title %></option> ><%= page.title %></option>
@ -254,6 +254,46 @@
</p> </p>
</td> </td>
</tr> </tr>
<!-- unsubscribe-->
<tr>
<th scope="row">
<label for="subscription_unsubscribe_page">
<%= __('Unsubscribe page') %>
<p class="description">
<%= __('The page your subscribers see when they click on "Unsubscribe" in your emails.') %>
</p>
</label>
</th>
<td>
<p>
<select
class="mailpoet_page_selection"
id="subscription_unsubscribe_page"
name="subscription[unsubscribe_page]"
>
<% for page in pages %>
<option
value="<%= page.id %>"
data-preview-url="<%= page.preview_url|raw %>&amp;mailpoet_action=unsubscribe"
data-edit-url="<%= page.edit_url|raw %>"
<% if(page.id == settings.subscription.unsubscribe_page) %>
selected="selected"
<% endif %>
><%= page.title %></option>
<% endfor %>
</select>
<a
class="mailpoet_page_preview"
href="javascript:;"
title="<%= __('Preview page') %>"
><%= __('Preview') %></a>&nbsp;|&nbsp;<a
class="mailpoet_page_edit"
href="javascript:;"
title="<%= __('Edit page') %>"
><%= __('Edit') %></a>
</p>
</td>
</tr>
<!-- shortcode: archive page --> <!-- shortcode: archive page -->
<tr> <tr>
<th scope="row"> <th scope="row">

View File

@ -112,11 +112,6 @@
name="signup_confirmation[subject]" name="signup_confirmation[subject]"
<% if(settings.signup_confirmation.subject) %> <% if(settings.signup_confirmation.subject) %>
value="<%= settings.signup_confirmation.subject %>" value="<%= settings.signup_confirmation.subject %>"
<% else %>
value="<%=
__('Confirm your subscription to %1$s')
| format(get_option('blogname'))
%>"
<% endif %> <% endif %>
/> />
</td> </td>
@ -139,8 +134,6 @@
name="signup_confirmation[body]" name="signup_confirmation[body]"
><% if(settings.signup_confirmation.body) %> ><% if(settings.signup_confirmation.body) %>
<%=- settings.signup_confirmation.body -%> <%=- settings.signup_confirmation.body -%>
<% else %>
<%=- __("Hello!\n\nHurray! You've subscribed to our site.\nWe need you to activate your subscription to the list(s): [lists_to_confirm] by clicking the link below: \n\n[activation_link]Click here to confirm your subscription.[/activation_link]\n\nThank you,\n\nThe team!") -%>
<% endif %></textarea> <% endif %></textarea>
</td> </td>
</tr> </tr>
@ -163,7 +156,7 @@
<% for page in pages %> <% for page in pages %>
<option <option
value="<%= page.id %>" value="<%= page.id %>"
data-preview-url="<%= page.preview_url|raw %>" data-preview-url="<%= page.preview_url|raw %>&amp;mailpoet_action=confirm"
data-edit-url="<%= page.edit_url|raw %>" data-edit-url="<%= page.edit_url|raw %>"
<% if(page.id == settings.signup_confirmation.page) %> <% if(page.id == settings.signup_confirmation.page) %>
selected="selected" selected="selected"

View File

@ -20,7 +20,5 @@
var mailpoet_segments = <%= json_encode(segments) %>; var mailpoet_segments = <%= json_encode(segments) %>;
var mailpoet_custom_fields = <%= json_encode(custom_fields) %>; var mailpoet_custom_fields = <%= json_encode(custom_fields) %>;
var mailpoet_month_names = <%= json_encode(month_names) %>; var mailpoet_month_names = <%= json_encode(month_names) %>;
var mailpoet_date_format = "<%= get_option('date_format') %>";
var mailpoet_date_offset = "<%= get_option('gmt_offset') %>";
</script> </script>
<% endblock %> <% endblock %>

View File

@ -91,10 +91,14 @@ baseConfig = {
config.push(_.extend({}, baseConfig, { config.push(_.extend({}, baseConfig, {
name: 'admin', name: 'admin',
entry: { entry: {
vendor: ['handlebars', 'handlebars_helpers'], vendor: [
'handlebars',
'handlebars_helpers'
],
mailpoet: [ mailpoet: [
'mailpoet', 'mailpoet',
'ajax', 'ajax',
'date',
'modal', 'modal',
'notice', 'notice',
'jquery.serialize_object', 'jquery.serialize_object',
@ -129,7 +133,6 @@ config.push(_.extend({}, baseConfig, {
'blob', 'blob',
'filesaver', 'filesaver',
'velocity-animate', 'velocity-animate',
'newsletter_editor/communicationsFix.js', 'newsletter_editor/communicationsFix.js',
'newsletter_editor/App', 'newsletter_editor/App',
'newsletter_editor/components/config.js', 'newsletter_editor/components/config.js',
@ -156,11 +159,11 @@ config.push(_.extend({}, baseConfig, {
'newsletter_editor/blocks/header.js', 'newsletter_editor/blocks/header.js',
'newsletter_editor/blocks/automatedLatestContent.js', 'newsletter_editor/blocks/automatedLatestContent.js',
'newsletter_editor/blocks/posts.js', 'newsletter_editor/blocks/posts.js',
'newsletter_editor/blocks/social.js', 'newsletter_editor/blocks/social.js'
], ]
}, },
plugins: [ plugins: [
new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'), new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js')
], ],
externals: { externals: {
'jquery': 'jQuery', 'jquery': 'jQuery',