Compare commits

...

39 Commits

Author SHA1 Message Date
fb51765d3f Bump up release version to 0.0.42 2016-08-30 13:04:08 +03:00
088ad5fb42 Merge pull request #597 from mailpoet/editor_fixes
Editor fixes
2016-08-29 15:43:47 -04:00
2f5b3c0c0a Merge pull request #600 from mailpoet/circle_ci
Adds CircleCI support
2016-08-29 15:15:39 -04:00
04ac4d896c Remove the build testing error 2016-08-29 21:38:26 +03:00
f3b96af863 Attempt to fix command status codes for Robo commands 2016-08-29 21:32:57 +03:00
eb42b0b98d Merge branch 'circle_ci' of github.com:mailpoet/mailpoet into circle_ci 2016-08-29 21:32:14 +03:00
304667eb49 - Testing commit e-mail 2016-08-29 14:27:40 -04:00
dad1082cd7 - Fixes JS unit test 2016-08-29 14:26:44 -04:00
37cf0f3d29 - Fixes JS unit test
:
2016-08-29 13:58:20 -04:00
d61c6dff58 - Fixes PHP unit test
- Fails JS unit test to check if CircleCI will detect it
2016-08-29 13:52:17 -04:00
3734ac578d - Fails test to check if CircleCI will detect it 2016-08-29 13:48:33 -04:00
b3f56c9d8e Merge pull request #599 from mailpoet/router_unit_tests
Router refactoring and unit tests
2016-08-25 17:33:44 +03:00
3603eeee77 - Updates remaining router endpoints to use constructor and new constants 2016-08-25 10:03:52 -04:00
59d30cc139 - Renames router URL query parameter and router class
- Updates other classes to use the new name
- Updates unit tests
2016-08-25 09:57:14 -04:00
6ff3bbbb72 - Fixes type in method name 2016-08-24 23:35:45 -04:00
a561e10156 - Updates tests for view in browser and statistics tracking 2016-08-24 23:35:34 -04:00
99f2cf6702 - Adds unit tests for front router 2016-08-24 23:27:12 -04:00
c6b72e729b - Refactors front router and endpoints to use dynamic methods 2016-08-24 23:26:13 -04:00
c5bc0f36a4 Disable running PHP coverage reports 2016-08-25 00:46:29 +03:00
efc5c34bf9 Add running PHP unit test coverage reports in CircleCI 2016-08-25 00:34:58 +03:00
3929efbdd9 Enable running JS tests 2016-08-25 00:09:34 +03:00
0e0c41882e Generate XML report for unit tests and add it to CircleCI tracking 2016-08-25 00:05:41 +03:00
79cc708fc6 Set UTC timezone for CircleCI PHP version 2016-08-24 23:50:59 +03:00
8fa98879b8 Enable debugging when running tests 2016-08-24 23:46:22 +03:00
331ba385e9 Switch MySQL host to 127.0.0.1 instead of localhost 2016-08-24 23:30:19 +03:00
71ce46d78d Add running PHP tests 2016-08-24 23:13:06 +03:00
c493de6569 Try to output JS test results in jUnit format for CircleCI 2016-08-24 21:44:22 +03:00
ff2c2ace86 Add running JS tests in CircleCI 2016-08-24 21:29:58 +03:00
7fa789cfd1 Merge pull request #598 from mailpoet/view_in_browser_update
View in browser update
2016-08-24 19:02:15 +03:00
ae6269eb63 - Restricts router access to explicitly defined endpoint actions 2016-08-24 11:23:12 -04:00
a8f4779bfe - Updates code formatting 2016-08-24 10:22:10 -04:00
6868142e35 - Extracts view in browser response to the endpoint
- Updates unit tests
2016-08-24 10:20:35 -04:00
133d123919 - Updates front router and endpoints to use dynamic methods 2016-08-24 10:20:10 -04:00
05c128d12d - Fixes errors thrown when there are no shortcodes in the newsletter body 2016-08-24 09:29:17 -04:00
bdab0c12fa Fix debouncing for ALC refresh to not update multiple times immediately 2016-08-24 16:15:53 +03:00
75b94690e2 - Adds unit tests 2016-08-23 23:42:56 -04:00
80fddd6c58 - Refactors view in browser 2016-08-23 23:42:26 -04:00
c807ead5fd - Prepares newsletter renderer for conversion to using modal objects
instead of arrays
2016-08-23 12:48:38 -04:00
f004bb5368 - Set default preview email to be current user's email;
- Change "Preview in browser" form to autocomplete used emails.
2016-08-23 19:32:10 +03:00
25 changed files with 714 additions and 166 deletions

View File

@ -102,31 +102,51 @@ class RoboFile extends \Robo\Tasks {
); );
} }
function testUnit($file = null) { function testUnit($opts=['file' => null, 'xml' => false]) {
$this->loadEnv(); $this->loadEnv();
$this->_exec('vendor/bin/codecept build'); $this->_exec('vendor/bin/codecept build');
$this->_exec('vendor/bin/codecept run unit -f '.(($file) ? $file : ''));
$command = 'vendor/bin/codecept run unit -f '.(($opts['file']) ? $opts['file'] : '');
if($opts['xml']) {
$command .= ' --xml';
}
return $this->_exec($command);
} }
function testCoverage($file = null) { function testCoverage($opts=['file' => null, 'xml' => false]) {
$this->loadEnv(); $this->loadEnv();
$this->_exec('vendor/bin/codecept build'); $this->_exec('vendor/bin/codecept build');
$this->_exec(join(' ', array( $command = join(' ', array(
'vendor/bin/codecept run', 'vendor/bin/codecept run',
(($file) ? $file : ''), (($opts['file']) ? $opts['file'] : ''),
'--coverage', '--coverage',
'--coverage-html' ($opts['xml']) ? '--coverage-xml' : '--coverage-html'
))); ));
if($opts['xml']) {
$command .= ' --xml';
}
return $this->_exec($command);
} }
function testJavascript() { function testJavascript($xml_output_file = null) {
$this->compileJs(); $this->compileJs();
$this->_exec(join(' ', array( $command = join(' ', array(
'./node_modules/.bin/mocha', './node_modules/.bin/mocha',
'-r tests/javascript/mochaTestHelper.js', '-r tests/javascript/mochaTestHelper.js',
'tests/javascript/testBundles/**/*.js' 'tests/javascript/testBundles/**/*.js'
))); ));
if(!empty($xml_output_file)) {
$command .= sprintf(
' --reporter xunit --reporter-options output="%s"',
$xml_output_file
);
}
return $this->_exec($command);
} }
function testDebug() { function testDebug() {

View File

@ -35,7 +35,12 @@ define([
Module.ALCSupervisor = SuperModel.extend({ Module.ALCSupervisor = SuperModel.extend({
initialize: function() { initialize: function() {
this.listenTo(App.getChannel(), 'automatedLatestContentRefresh', this.refresh); var DELAY_REFRESH_FOR_MS = 500;
this.listenTo(
App.getChannel(),
'automatedLatestContentRefresh',
_.debounce(this.refresh, DELAY_REFRESH_FOR_MS)
);
}, },
refresh: function() { refresh: function() {
var models = App.findModels(function(model) { var models = App.findModels(function(model) {
@ -107,9 +112,7 @@ define([
this.on('change:amount change:contentType change:terms change:inclusionType change:displayType change:titleFormat change:featuredImagePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:sortBy change:showDivider', this._scheduleFetchPosts, this); this.on('change:amount change:contentType change:terms change:inclusionType change:displayType change:titleFormat change:featuredImagePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:sortBy change:showDivider', this._scheduleFetchPosts, this);
this.listenTo(this.get('readMoreButton'), 'change', this._scheduleFetchPosts); this.listenTo(this.get('readMoreButton'), 'change', this._scheduleFetchPosts);
this.listenTo(this.get('divider'), 'change', this._scheduleFetchPosts); this.listenTo(this.get('divider'), 'change', this._scheduleFetchPosts);
this.on('add remove update reset', function(model, collection, options) { this.on('add remove update reset', this._scheduleFetchPosts);
App.getChannel().trigger('automatedLatestContentRefresh');
});
this.on('refreshPosts', this.updatePosts, this); this.on('refreshPosts', this.updatePosts, this);
}, },
updatePosts: function(posts) { updatePosts: function(posts) {
@ -120,16 +123,7 @@ define([
* ALC posts on each model change * ALC posts on each model change
*/ */
_scheduleFetchPosts: function() { _scheduleFetchPosts: function() {
var TIMEOUT = 500,
that = this;
if (this._fetchPostsTimer !== undefined) {
clearTimeout(this._fetchPostsTimer);
}
this._fetchPostsTimer = setTimeout(function() {
//that.fetchPosts();
App.getChannel().trigger('automatedLatestContentRefresh'); App.getChannel().trigger('automatedLatestContentRefresh');
that._fetchPostsTimer = undefined;
}, TIMEOUT);
}, },
}); });

53
circle.yml Normal file
View File

@ -0,0 +1,53 @@
machine:
timezone:
UTC
hosts:
mailpoet.loc: 127.0.0.1
## Customize dependencies
dependencies:
pre:
- composer install
- ./do install
# Set up Wordpress
# No password is required for the MySQL user `ubuntu`
- mysql -u ubuntu -e "create database wordpress"
# Use cURL to fetch WP-CLI
- curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
# Make sure WP-CLI is executable
- chmod +x wp-cli.phar
# Download WordPress into `wordpress` directory
- ./wp-cli.phar core download --allow-root --path=wordpress
# Generate `wp-config.php` file
- echo "define(\"WP_DEBUG\", true);" | ./wp-cli.phar core config --allow-root --dbname=wordpress --dbuser=ubuntu --dbhost=127.0.0.1:3306 --path=wordpress --extra-php
# Install WordPress
- ./wp-cli.phar core install --allow-root --admin_name=admin --admin_password=admin --admin_email=admin@mailpoet.loc --url=http://mailpoet.loc:8080 --title=WordPress --path=wordpress
# Softlink MailPoet to plugin path
- ln -s ../../.. wordpress/wp-content/plugins/mailpoet
# Activate MailPoet
- ./wp-cli.phar plugin activate mailpoet --path=wordpress
# Create .env file with correct path to WP installation
- echo "WP_TEST_PATH=\"/home/ubuntu/mailpoet/wordpress\"" > .env
# Enable XDebug for coverage reports.
# Comment out if not running PHP coverage reports, for performance
#- sed -i 's/^;//' /opt/circleci/php/$(phpenv global)/etc/conf.d/xdebug.ini
## tests override
test:
override:
# Run JS tests
- mkdir $CIRCLE_TEST_REPORTS/mocha
- ./do t:j $CIRCLE_TEST_REPORTS/mocha/junit.xml
# Run PHP tests
- ./do t:u --xml
# Uncomment to run coverage tests instead
#- ./do t:c --xml
# Copy the report
- mkdir $CIRCLE_TEST_REPORTS/codeception
- cp tests/_output/report.xml $CIRCLE_TEST_REPORTS/codeception/report.xml
# Uncomment to copy PHP coverage report
#- cp tests/_output/coverage.xml $CIRCLE_TEST_REPORTS/codeception/coverage.xml

View File

@ -10,6 +10,7 @@ settings:
colors: true colors: true
memory_limit: 1024M memory_limit: 1024M
log: true log: true
strict_xml: true
extensions: extensions:
enabled: enabled:
- Codeception\Extension\RunFailed - Codeception\Extension\RunFailed

View File

@ -1,7 +1,6 @@
<?php <?php
namespace MailPoet\Config; namespace MailPoet\Config;
use MailPoet\Models;
use MailPoet\Cron\CronTrigger; use MailPoet\Cron\CronTrigger;
use MailPoet\Router; use MailPoet\Router;
use MailPoet\API; use MailPoet\API;
@ -122,7 +121,7 @@ class Initializer {
try { try {
$this->setupAPI(); $this->setupAPI();
$this->setupFrontRouter(); $this->setupRouter();
$this->setupPages(); $this->setupPages();
} catch(\Exception $e) { } catch(\Exception $e) {
$this->handleFailedInitialization($e); $this->handleFailedInitialization($e);
@ -187,8 +186,8 @@ class Initializer {
$api->init(); $api->init();
} }
function setupFrontRouter() { function setupRouter() {
$router = new Router\Front(); $router = new Router\Router();
$router->init(); $router->init();
} }

View File

@ -8,6 +8,7 @@ use MailPoet\Models\CustomField;
use MailPoet\Models\Form; use MailPoet\Models\Form;
use MailPoet\Models\Segment; use MailPoet\Models\Segment;
use MailPoet\Models\Setting; use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\Shortcodes\ShortcodesHelper; use MailPoet\Newsletter\Shortcodes\ShortcodesHelper;
use MailPoet\Settings\Hosts; use MailPoet\Settings\Hosts;
use MailPoet\Settings\Pages; use MailPoet\Settings\Pages;
@ -368,6 +369,7 @@ class Menu {
$data = array( $data = array(
'shortcodes' => ShortcodesHelper::getShortcodes(), 'shortcodes' => ShortcodesHelper::getShortcodes(),
'settings' => Setting::getAll(), 'settings' => Setting::getAll(),
'current_wp_user' => Subscriber::getCurrentWPUser(),
'sub_menu' => 'mailpoet-newsletters' 'sub_menu' => 'mailpoet-newsletters'
); );
wp_enqueue_media(); wp_enqueue_media();

View File

@ -3,7 +3,7 @@ namespace MailPoet\Cron;
use MailPoet\Models\Setting; use MailPoet\Models\Setting;
use MailPoet\Router\Endpoints\Queue as QueueEndpoint; use MailPoet\Router\Endpoints\Queue as QueueEndpoint;
use MailPoet\Router\Front as FrontRouter; use MailPoet\Router\Router;
use MailPoet\Util\Security; use MailPoet\Util\Security;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
@ -55,7 +55,7 @@ class CronHelper {
static function accessDaemon($token, $timeout = self::DAEMON_REQUEST_TIMEOUT) { static function accessDaemon($token, $timeout = self::DAEMON_REQUEST_TIMEOUT) {
$data = array('token' => $token); $data = array('token' => $token);
$url = FrontRouter::buildRequest( $url = Router::buildRequest(
QueueEndpoint::ENDPOINT, QueueEndpoint::ENDPOINT,
QueueEndpoint::ACTION_RUN, QueueEndpoint::ACTION_RUN,
$data $data

View File

@ -2,7 +2,7 @@
namespace MailPoet\Newsletter\Links; namespace MailPoet\Newsletter\Links;
use MailPoet\Models\Subscriber; use MailPoet\Models\Subscriber;
use MailPoet\Router\Front as FrontRouter; use MailPoet\Router\Router;
use MailPoet\Router\Endpoints\Track as TrackEndpoint; use MailPoet\Router\Endpoints\Track as TrackEndpoint;
use MailPoet\Models\NewsletterLink; use MailPoet\Models\NewsletterLink;
use MailPoet\Newsletter\Shortcodes\Shortcodes; use MailPoet\Newsletter\Shortcodes\Shortcodes;
@ -35,12 +35,14 @@ class Links {
// extract shortcodes with [link:*] format // extract shortcodes with [link:*] format
$shortcodes = new Shortcodes(); $shortcodes = new Shortcodes();
$shortcodes = $shortcodes->extract($content, $categories = array('link')); $shortcodes = $shortcodes->extract($content, $categories = array('link'));
$extracted_links = array_map(function ($shortcode) { if($shortcodes) {
$extracted_links = array_map(function($shortcode) {
return array( return array(
'html' => $shortcode, 'html' => $shortcode,
'link' => $shortcode 'link' => $shortcode
); );
}, $shortcodes); }, $shortcodes);
}
// extract urls with href="url" format // extract urls with href="url" format
preg_match_all($regex, $content, $matched_urls); preg_match_all($regex, $content, $matched_urls);
$matched_urls_count = count($matched_urls[0]); $matched_urls_count = count($matched_urls[0]);
@ -121,7 +123,7 @@ class Links {
$router_action = ($matches[2][$index] === self::DATA_TAG_CLICK) ? $router_action = ($matches[2][$index] === self::DATA_TAG_CLICK) ?
TrackEndpoint::ACTION_CLICK : TrackEndpoint::ACTION_CLICK :
TrackEndpoint::ACTION_OPEN; TrackEndpoint::ACTION_OPEN;
$link = FrontRouter::buildRequest( $link = Router::buildRequest(
TrackEndpoint::ENDPOINT, TrackEndpoint::ENDPOINT,
$router_action, $router_action,
$data $data

View File

@ -13,8 +13,9 @@ class Renderer {
const NEWSLETTER_TEMPLATE = 'Template.html'; const NEWSLETTER_TEMPLATE = 'Template.html';
const POST_PROCESS_FILTER = 'mailpoet_rendering_post_process'; const POST_PROCESS_FILTER = 'mailpoet_rendering_post_process';
function __construct(array $newsletter, $preview = false) { function __construct($newsletter, $preview = false) {
$this->newsletter = $newsletter; // TODO: remove ternary condition, refactor to use model objects
$this->newsletter = (is_object($newsletter)) ? $newsletter->asArray() : $newsletter;
$this->preview = $preview; $this->preview = $preview;
$this->blocks_renderer = new Blocks\Renderer($this->newsletter, $this->preview); $this->blocks_renderer = new Blocks\Renderer($this->newsletter, $this->preview);
$this->columns_renderer = new Columns\Renderer(); $this->columns_renderer = new Columns\Renderer();

View File

@ -1,9 +1,8 @@
<?php <?php
namespace MailPoet\Newsletter; namespace MailPoet\Newsletter;
use MailPoet\Models\Newsletter;
use MailPoet\Models\SendingQueue; use MailPoet\Models\SendingQueue;
use MailPoet\Router\Front as FrontRouter; use MailPoet\Router\Router;
use MailPoet\Router\Endpoints\ViewInBrowser as ViewInBrowserEndpoint; use MailPoet\Router\Endpoints\ViewInBrowser as ViewInBrowserEndpoint;
use MailPoet\Models\Subscriber; use MailPoet\Models\Subscriber;
@ -44,7 +43,7 @@ class Url {
$queue, $queue,
'preview' => $preview 'preview' => $preview
); );
return FrontRouter::buildRequest( return Router::buildRequest(
ViewInBrowserEndpoint::ENDPOINT, ViewInBrowserEndpoint::ENDPOINT,
ViewInBrowserEndpoint::ACTION_VIEW, ViewInBrowserEndpoint::ACTION_VIEW,
$data $data

View File

@ -1,73 +1,34 @@
<?php <?php
namespace MailPoet\Newsletter; namespace MailPoet\Newsletter;
use MailPoet\Models\Newsletter;
use MailPoet\Models\SendingQueue;
use MailPoet\Models\Setting; use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\Links\Links; use MailPoet\Newsletter\Links\Links;
use MailPoet\Newsletter\Renderer\Renderer; use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Newsletter\Shortcodes\Shortcodes; use MailPoet\Newsletter\Shortcodes\Shortcodes;
class ViewInBrowser { class ViewInBrowser {
static function view($data) { function view($data) {
$data = self::preProcessData($data); $wp_user_preview = ($data->preview && $data->subscriber->isWPUser());
if(!self::validateData($data)) self::abort(); return $this->renderNewsletter(
$rendered_newsletter =
self::getAndRenderNewsletter(
$data->newsletter, $data->newsletter,
$data->subscriber, $data->subscriber,
$data->queue, $data->queue,
$data->preview $wp_user_preview
); );
header('Content-Type: text/html; charset=utf-8');
echo $rendered_newsletter;
exit;
} }
static function preProcessData($data) { function renderNewsletter($newsletter, $subscriber, $queue, $wp_user_preview) {
$data = (object)$data;
if(empty($data->subscriber_id) ||
empty($data->subscriber_token) ||
empty($data->newsletter_id)
) {
return false;
}
$data->newsletter = Newsletter::findOne($data->newsletter_id);
$data->subscriber = Subscriber::findOne($data->subscriber_id);
$data->queue = ($data->queue_id) ?
SendingQueue::findOne($data->queue_id) :
false;
return $data;
}
static function validateData($data) {
if(!$data || !$data->subscriber || !$data->newsletter) return false;
$subscriber_token_match =
Subscriber::verifyToken($data->subscriber->email, $data->subscriber_token);
if(!$subscriber_token_match) return false;
// return if this is a WP user previewing the newsletter
if($data->subscriber->isWPUser() && $data->preview) {
return $data;
}
// if queue exists, check if the newsletter was sent to the subscriber
if($data->queue && !$data->queue->isSubscriberProcessed($data->subscriber->id)) {
$data = false;
}
return $data;
}
static function getAndRenderNewsletter($newsletter, $subscriber, $queue, $preview) {
if($queue && $queue->newsletter_rendered_body) { if($queue && $queue->newsletter_rendered_body) {
$newsletter_body = $queue->getRenderedNewsletterBody(); $newsletter_body = $queue->getRenderedNewsletterBody();
} else { } else {
$renderer = new Renderer($newsletter, $preview); $renderer = new Renderer($newsletter, $wp_user_preview);
$newsletter_body = $renderer->render(); $newsletter_body = $renderer->render();
} }
$shortcodes = new Shortcodes( $shortcodes = new Shortcodes(
$newsletter, $newsletter,
$subscriber, $subscriber,
$queue $queue
); );
$rendered_newsletter = $shortcodes->replace($newsletter_body['html']); $rendered_newsletter = $shortcodes->replace($newsletter_body['html']);
if($queue && (boolean)Setting::getValue('tracking.enabled')) { if($queue && (boolean)Setting::getValue('tracking.enabled')) {
@ -75,14 +36,9 @@ class ViewInBrowser {
$subscriber->id, $subscriber->id,
$queue->id, $queue->id,
$rendered_newsletter, $rendered_newsletter,
$preview $wp_user_preview
); );
} }
return $rendered_newsletter; return $rendered_newsletter;
} }
private static function abort() {
status_header(404);
exit;
}
} }

View File

@ -8,9 +8,15 @@ if(!defined('ABSPATH')) exit;
class Queue { class Queue {
const ENDPOINT = 'queue'; const ENDPOINT = 'queue';
const ACTION_RUN = 'run'; const ACTION_RUN = 'run';
public $allowed_actions = array(self::ACTION_RUN);
public $data;
static function run($data) { function __construct($data) {
$queue = new Daemon($data); $this->data = $data;
}
function run() {
$queue = new Daemon($this->data);
$queue->run(); $queue->run();
} }
} }

View File

@ -7,18 +7,31 @@ if(!defined('ABSPATH')) exit;
class Subscription { class Subscription {
const ENDPOINT = 'subscription'; const ENDPOINT = 'subscription';
const ACTION_CONFIRM = 'confirm';
const ACTION_MANAGE = 'manage';
const ACTION_UNSUBSCRIBE = 'unsubscribe';
public $allowed_actions = array(
self::ACTION_CONFIRM,
self::ACTION_MANAGE,
self::ACTION_UNSUBSCRIBE
);
public $data;
static function confirm($data) { function __construct($data) {
$subscription = new UserSubscription\Pages('confirm', $data); $this->data = $data;
}
function confirm() {
$subscription = new UserSubscription\Pages('confirm', $this->data);
$subscription->confirm(); $subscription->confirm();
} }
static function manage($data) { function manage() {
$subscription = new UserSubscription\Pages('manage', $data); $subscription = new UserSubscription\Pages('manage', $this->data);
} }
static function unsubscribe($data) { function unsubscribe() {
$subscription = new UserSubscription\Pages('unsubscribe', $data); $subscription = new UserSubscription\Pages('unsubscribe', $this->data);
$subscription->unsubscribe(); $subscription->unsubscribe();
} }
} }

View File

@ -14,18 +14,27 @@ class Track {
const ENDPOINT = 'track'; const ENDPOINT = 'track';
const ACTION_CLICK = 'click'; const ACTION_CLICK = 'click';
const ACTION_OPEN = 'open'; const ACTION_OPEN = 'open';
public $allowed_actions = array(
self::ACTION_CLICK,
self::ACTION_OPEN
);
public $data;
static function click($data) { function __construct($data) {
$this->data = $this->_processTrackData($data);
}
function click() {
$click_event = new Clicks(); $click_event = new Clicks();
return $click_event->track(self::_processTrackData($data)); return $click_event->track($this->data);
} }
static function open($data) { function open() {
$open_event = new Opens(); $open_event = new Opens();
return $open_event->track(self::_processTrackData($data)); return $open_event->track($this->data);
} }
static function _processTrackData($data) { function _processTrackData($data) {
$data = (object)$data; $data = (object)$data;
if(empty($data->queue_id) || if(empty($data->queue_id) ||
empty($data->subscriber_id) || empty($data->subscriber_id) ||
@ -41,10 +50,10 @@ class Track {
if(!empty($data->link_hash)) { if(!empty($data->link_hash)) {
$data->link = NewsletterLink::getByHash($data->link_hash); $data->link = NewsletterLink::getByHash($data->link_hash);
} }
return self::_validateTrackData($data); return $this->_validateTrackData($data);
} }
static function _validateTrackData($data) { function _validateTrackData($data) {
if(!$data->subscriber || !$data->queue || !$data->newsletter) return false; if(!$data->subscriber || !$data->queue || !$data->newsletter) return false;
$subscriber_token_match = $subscriber_token_match =
Subscriber::verifyToken($data->subscriber->email, $data->subscriber_token); Subscriber::verifyToken($data->subscriber->email, $data->subscriber_token);

View File

@ -1,6 +1,9 @@
<?php <?php
namespace MailPoet\Router\Endpoints; namespace MailPoet\Router\Endpoints;
use MailPoet\Models\Newsletter;
use MailPoet\Models\SendingQueue;
use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\ViewInBrowser as NewsletterViewInBrowser; use MailPoet\Newsletter\ViewInBrowser as NewsletterViewInBrowser;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
@ -8,8 +11,61 @@ if(!defined('ABSPATH')) exit;
class ViewInBrowser { class ViewInBrowser {
const ENDPOINT = 'view_in_browser'; const ENDPOINT = 'view_in_browser';
const ACTION_VIEW = 'view'; const ACTION_VIEW = 'view';
public $allowed_actions = array(self::ACTION_VIEW);
public $data;
static function view($data) { function __construct($data) {
NewsletterViewInBrowser::view($data); $this->data = $this->_processBrowserPreviewData($data);
}
function view() {
$view_in_browser = new NewsletterViewInBrowser();
return $this->_displayNewsletter($view_in_browser->view($this->data));
}
function _processBrowserPreviewData($data) {
$data = (object)$data;
if(empty($data->subscriber_id) ||
empty($data->subscriber_token) ||
empty($data->newsletter_id)
) {
$this->_abort();
} else {
$data->newsletter = Newsletter::findOne($data->newsletter_id);
$data->subscriber = Subscriber::findOne($data->subscriber_id);
$data->queue = ($data->queue_id) ?
SendingQueue::findOne($data->queue_id) :
false;
return ($this->_validateBrowserPreviewData($data)) ?
$data :
$this->_abort();
}
}
function _validateBrowserPreviewData($data) {
if(!$data || !$data->subscriber || !$data->newsletter) return false;
$subscriber_token_match =
Subscriber::verifyToken($data->subscriber->email, $data->subscriber_token);
if(!$subscriber_token_match) return false;
// return if this is a WP user previewing the newsletter
if($data->subscriber->isWPUser() && $data->preview) {
return $data;
}
// if queue exists, check if the newsletter was sent to the subscriber
if($data->queue && !$data->queue->isSubscriberProcessed($data->subscriber->id)) {
$data = false;
}
return $data;
}
function _displayNewsletter($result) {
header('Content-Type: text/html; charset=utf-8');
echo $result;
exit;
}
function _abort() {
status_header(404);
exit;
} }
} }

View File

@ -1,15 +1,16 @@
<?php <?php
namespace MailPoet\Router; namespace MailPoet\Router;
use MailPoet\Util\Helpers; use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class Front { class Router {
public $api_request; public $api_request;
public $endpoint; public $endpoint;
public $action; public $action;
public $data; public $data;
const NAME = 'mailpoet_api'; const NAME = 'mailpoet_router';
const RESPONSE_ERROR = 404; const RESPONSE_ERROR = 404;
function __construct($api_data = false) { function __construct($api_data = false) {
@ -27,43 +28,31 @@ class Front {
} }
function init() { function init() {
$class = __NAMESPACE__ . "\\Endpoints\\" . ucfirst($this->endpoint); $endpoint_class = __NAMESPACE__ . "\\Endpoints\\" . ucfirst($this->endpoint);
if(!$this->api_request) return; if(!$this->api_request) return;
if(!$this->endpoint || !class_exists($class)) { if(!$this->endpoint || !class_exists($endpoint_class)) {
self::terminateRequest(self::RESPONSE_ERROR, __('Invalid Router endpoint.')); return $this->terminateRequest(self::RESPONSE_ERROR, __('Invalid router endpoint.'));
} }
$this->callEndpoint( $endpoint = new $endpoint_class($this->data);
$class, if(!method_exists($endpoint, $this->action) || !in_array($this->action, $endpoint->allowed_actions)) {
$this->action, return $this->terminateRequest(self::RESPONSE_ERROR, __('Invalid router endpoint action.'));
$this->data
);
} }
return call_user_func(
function callEndpoint($endpoint, $action, $data) {
if(!method_exists($endpoint, $action)) {
self::terminateRequest(self::RESPONSE_ERROR, __('Invalid Router action.'));
}
call_user_func(
array( array(
$endpoint, $endpoint,
$action $this->action
), )
$data
); );
} }
static function decodeRequestData($data) { static function decodeRequestData($data) {
$data = base64_decode($data); $data = base64_decode($data);
if(is_serialized($data)) { if(is_serialized($data)) {
$data = unserialize($data); $data = unserialize($data);
} }
if(!is_array($data)) { if(!is_array($data)) {
$data = array(); $data = array();
} }
return $data; return $data;
} }
@ -82,7 +71,7 @@ class Front {
return add_query_arg($params, home_url()); return add_query_arg($params, home_url());
} }
static function terminateRequest($code, $message) { function terminateRequest($code, $message) {
status_header($code, $message); status_header($code, $message);
exit; exit;
} }

View File

@ -1,7 +1,7 @@
<?php <?php
namespace MailPoet\Subscription; namespace MailPoet\Subscription;
use MailPoet\Router\Front as FrontRouter; use MailPoet\Router\Router;
use MailPoet\Router\Endpoints\Subscription as SubscriptionEndpoint; use MailPoet\Router\Endpoints\Subscription as SubscriptionEndpoint;
use MailPoet\Models\Subscriber; use MailPoet\Models\Subscriber;
use MailPoet\Models\Setting; use MailPoet\Models\Setting;
@ -45,10 +45,10 @@ class Url {
} }
$params = array( $params = array(
FrontRouter::NAME, Router::NAME,
'endpoint='.SubscriptionEndpoint::ENDPOINT, 'endpoint='.SubscriptionEndpoint::ENDPOINT,
'action='.$action, 'action='.$action,
'data='.FrontRouter::encodeRequestData($data) 'data='.Router::encodeRequestData($data)
); );
// add parameters // add parameters

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.41 * Version: 0.0.42
* 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.41'); define('MAILPOET_VERSION', '0.0.42');
$initializer = new Initializer(array( $initializer = new Initializer(array(
'file' => __FILE__, 'file' => __FILE__,

View File

@ -0,0 +1,152 @@
<?php
use Codeception\Util\Stub;
use MailPoet\Models\Newsletter;
use MailPoet\Models\SendingQueue;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\ViewInBrowser;
use MailPoet\Router\Router;
class ViewInBrowserTest extends MailPoetTest {
function __construct() {
$this->newsletter = array(
'body' => json_decode(
'{
"content": {
"type": "container",
"orientation": "vertical",
"styles": {
"block": {
"backgroundColor": "transparent"
}
},
"blocks": [
{
"type": "container",
"orientation": "horizontal",
"styles": {
"block": {
"backgroundColor": "transparent"
}
},
"blocks": [
{
"type": "container",
"orientation": "vertical",
"styles": {
"block": {
"backgroundColor": "transparent"
}
},
"blocks": [
{
"type": "text",
"text": "<p>Rendered newsletter. Hello,&nbsp;[subscriber:firstname | default:reader] & [link:newsletter_view_in_browser_url]</p>"
}
]
}
]
}
]
}
}', true),
'id' => 1,
'subject' => 'Some subject',
'preheader' => 'Some preheader',
'type' => 'standard',
'status' => 'active'
);
$this->queue_rendered_newsletter_without_tracking = json_encode(
array(
'html' => 'Newsletter from queue. Hello, [subscriber:firstname] &
[link:newsletter_view_in_browser_url]'
)
);
$this->queue_rendered_newsletter_with_tracking = json_encode(
array(
'html' => 'Newsletter from queue. Hello, [subscriber:firstname] &
[mailpoet_click_data]-90e56'
)
);
// instantiate class
$this->view_in_browser = new ViewInBrowser();
}
function _before() {
// create newsletter
$newsletter = Newsletter::create();
$newsletter->hydrate($this->newsletter);
$this->newsletter = $newsletter->save();
// create subscriber
$subscriber = Subscriber::create();
$subscriber->email = 'test@example.com';
$subscriber->first_name = 'First';
$subscriber->last_name = 'Last';
$this->subscriber = $subscriber->save();
// create queue
$queue = SendingQueue::create();
$queue->newsletter_id = $newsletter->id;
$queue->newsletter_rendered_body = $this->queue_rendered_newsletter_without_tracking;
$queue->subscribers = array('processed' => array($subscriber->id));
$this->queue = $queue->save();
// build browser preview data
$this->browser_preview_data = (object)array(
'queue' => $this->queue,
'subscriber' => $this->subscriber,
'newsletter' => $this->newsletter,
'preview' => false
);
}
function testItRendersNewsletter() {
$rendered_body = ViewInBrowser::renderNewsletter(
$this->newsletter,
$this->subscriber,
$queue = false,
$preview = true
);
expect($rendered_body)->regExp('/Rendered newsletter/');
}
function testItReusesRenderedNewsletterBodyWhenQueueExists() {
$rendered_body = ViewInBrowser::renderNewsletter(
$this->newsletter,
$this->subscriber,
$this->queue,
$preview = true
);
expect($rendered_body)->regExp('/Newsletter from queue/');
}
function testItConvertsShortcodes() {
Setting::setValue('tracking.enabled', false);
$rendered_body = ViewInBrowser::renderNewsletter(
$this->newsletter,
$this->subscriber,
$this->queue,
$preview = true
);
expect($rendered_body)->contains('Hello, First');
expect($rendered_body)->contains(Router::NAME . '&endpoint=view_in_browser');
}
function testItProcessesLinksWhenTrackingIsEnabled() {
Setting::setValue('tracking.enabled', true);
$queue = $this->queue;
$queue->newsletter_rendered_body = $this->queue_rendered_newsletter_with_tracking;
$rendered_body = ViewInBrowser::renderNewsletter(
$this->newsletter,
$this->subscriber,
$queue,
$preview = true
);
expect($rendered_body)->contains(Router::NAME . '&endpoint=track');
}
function _after() {
ORM::raw_execute('TRUNCATE ' . Newsletter::$_table);
ORM::raw_execute('TRUNCATE ' . Subscriber::$_table);
ORM::raw_execute('TRUNCATE ' . SendingQueue::$_table);
}
}

View File

@ -39,21 +39,23 @@ class TrackTest extends MailPoetTest {
'link_hash' => $link->hash, 'link_hash' => $link->hash,
'preview' => false 'preview' => false
); );
// instantiate class
$this->track = new Track($this->track_data);
} }
function testItReturnsFalseWhenTrackDataIsMissing() { function testItReturnsFalseWhenTrackDataIsMissing() {
// queue ID is required // queue ID is required
$data = $this->track_data; $data = $this->track_data;
unset($data['queue_id']); unset($data['queue_id']);
expect(Track::_processTrackData($data))->false(); expect($this->track->_processTrackData($data))->false();
// subscriber ID is required // subscriber ID is required
$data = $this->track_data; $data = $this->track_data;
unset($data['subscriber_id']); unset($data['subscriber_id']);
expect(Track::_processTrackData($data))->false(); expect($this->track->_processTrackData($data))->false();
// subscriber token is required // subscriber token is required
$data = $this->track_data; $data = $this->track_data;
unset($data['subscriber_token']); unset($data['subscriber_token']);
expect(Track::_processTrackData($data))->false(); expect($this->track->_processTrackData($data))->false();
} }
function testItFailsWhenSubscriberTokenDoesNotMatch() { function testItFailsWhenSubscriberTokenDoesNotMatch() {
@ -66,7 +68,7 @@ class TrackTest extends MailPoetTest {
) )
); );
$data->subscriber->email = 'random@email.com'; $data->subscriber->email = 'random@email.com';
expect(Track::_validateTrackData($data))->false(); expect($this->track->_validateTrackData($data))->false();
} }
function testItFailsWhenSubscriberIsNotOnProcessedList() { function testItFailsWhenSubscriberIsNotOnProcessedList() {
@ -79,7 +81,7 @@ class TrackTest extends MailPoetTest {
) )
); );
$data->subscriber->id = 99; $data->subscriber->id = 99;
expect(Track::_validateTrackData($data))->false(); expect($this->track->_validateTrackData($data))->false();
} }
function testItDoesNotRequireWpUsersToBeOnProcessedListWhenPreviewIsEnabled() { function testItDoesNotRequireWpUsersToBeOnProcessedListWhenPreviewIsEnabled() {
@ -93,18 +95,26 @@ class TrackTest extends MailPoetTest {
); );
$data->subscriber->wp_user_id = 99; $data->subscriber->wp_user_id = 99;
$data->preview = true; $data->preview = true;
expect(Track::_validateTrackData($data))->equals($data); expect($this->track->_validateTrackData($data))->equals($data);
} }
function testItCanGetNewsletterFromQueue() { function testItRequiresValidQueueToGetNewsletter() {
$data = $this->track_data; $data = $this->track_data;
$data['newsletter_id'] = false; $data['newsletter_id'] = false;
$processed_data = Track::_processTrackData($this->track_data); $data['queue_id'] = 99;
$processed_data = $this->track->_processTrackData($data);
expect($processed_data)->false();
}
function testItGetsNewsletterFromQueue() {
$data = $this->track_data;
$data['newsletter_id'] = false;
$processed_data = $this->track->_processTrackData($data);
expect($processed_data->newsletter->id)->equals($this->newsletter->id); expect($processed_data->newsletter->id)->equals($this->newsletter->id);
} }
function testItCanProcessTrackData() { function testItProcessesTrackData() {
$processed_data = Track::_processTrackData($this->track_data); $processed_data = $this->track->_processTrackData($this->track_data);
expect($processed_data->queue->id)->equals($this->queue->id); expect($processed_data->queue->id)->equals($this->queue->id);
expect($processed_data->subscriber->id)->equals($this->subscriber->id); expect($processed_data->subscriber->id)->equals($this->subscriber->id);
expect($processed_data->newsletter->id)->equals($this->newsletter->id); expect($processed_data->newsletter->id)->equals($this->newsletter->id);

View File

@ -0,0 +1,134 @@
<?php
use Codeception\Util\Stub;
use MailPoet\Models\Newsletter;
use MailPoet\Models\SendingQueue;
use MailPoet\Models\Subscriber;
use MailPoet\Router\Endpoints\ViewInBrowser;
class ViewInBrowserRouterTest extends MailPoetTest {
function _before() {
// create newsletter
$newsletter = Newsletter::create();
$newsletter->type = 'type';
$this->newsletter = $newsletter->save();
// create subscriber
$subscriber = Subscriber::create();
$subscriber->email = 'test@example.com';
$subscriber->first_name = 'First';
$subscriber->last_name = 'Last';
$this->subscriber = $subscriber->save();
// create queue
$queue = SendingQueue::create();
$queue->newsletter_id = $newsletter->id;
$queue->subscribers = array('processed' => array($subscriber->id));
$this->queue = $queue->save();
// build browser preview data
$this->browser_preview_data = array(
'queue_id' => $queue->id,
'subscriber_id' => $subscriber->id,
'newsletter_id' => $newsletter->id,
'subscriber_token' => Subscriber::generateToken($subscriber->email),
'preview' => false
);
// instantiate class
$this->view_in_browser = new ViewInBrowser($this->browser_preview_data);
}
function testItAbortsWhenBrowserPreviewDataIsMissing() {
$view_in_browser = Stub::make($this->view_in_browser, array(
'_abort' => Stub::exactly(3, function() { })
), $this);
// newsletter ID is required
$data = $this->browser_preview_data;
unset($data['newsletter_id']);
$view_in_browser->_processBrowserPreviewData($data);
// subscriber ID is required
$data = $this->browser_preview_data;
unset($data['subscriber_id']);
$view_in_browser->_processBrowserPreviewData($data);
// subscriber token is required
$data = $this->browser_preview_data;
unset($data['subscriber_token']);
$view_in_browser->_processBrowserPreviewData($data);
}
function testItAbortsWhenBrowserPreviewDataIsInvalid() {
$view_in_browser = Stub::make($this->view_in_browser, array(
'_abort' => Stub::exactly(3, function() { })
), $this);
// newsletter ID is invalid
$data = $this->browser_preview_data;
$data['newsletter_id'] = 99;
$view_in_browser->_processBrowserPreviewData($data);
// subscriber ID is invalid
$data = $this->browser_preview_data;
$data['subscriber_id'] = 99;
$view_in_browser->_processBrowserPreviewData($data);
// subscriber token is invalid
$data = $this->browser_preview_data;
$data['subscriber_token'] = 'invalid';
$view_in_browser->_processBrowserPreviewData($data);
}
function testItFailsValidationWhenSubscriberTokenDoesNotMatch() {
$data = (object)array_merge(
$this->browser_preview_data,
array(
'queue' => $this->queue,
'subscriber' => $this->subscriber,
'newsletter' => $this->newsletter
)
);
$data->subscriber->email = 'random@email.com';
expect($this->view_in_browser->_validateBrowserPreviewData($data))->false();
}
function testItFailsValidationWhenSubscriberIsNotOnProcessedList() {
$data = (object)array_merge(
$this->browser_preview_data,
array(
'queue' => $this->queue,
'subscriber' => $this->subscriber,
'newsletter' => $this->newsletter
)
);
$data->subscriber->id = 99;
expect($this->view_in_browser->_validateBrowserPreviewData($data))->false();
}
function testItDoesNotRequireWpUsersToBeOnProcessedListWhenPreviewIsEnabled() {
$data = (object)array_merge(
$this->browser_preview_data,
array(
'queue' => $this->queue,
'subscriber' => $this->subscriber,
'newsletter' => $this->newsletter
)
);
$data->subscriber->wp_user_id = 99;
$data->preview = true;
expect($this->view_in_browser->_validateBrowserPreviewData($data))->equals($data);
}
function testItProcessesBrowserPreviewData() {
$processed_data = $this->view_in_browser->_processBrowserPreviewData($this->browser_preview_data);
expect($processed_data->queue->id)->equals($this->queue->id);
expect($processed_data->subscriber->id)->equals($this->subscriber->id);
expect($processed_data->newsletter->id)->equals($this->newsletter->id);
}
function testItReturnsViewActionResult() {
$view_in_browser = Stub::make($this->view_in_browser, array(
'_displayNewsletter' => Stub::exactly(1, function() { })
), $this);
$view_in_browser->data = $view_in_browser->_processBrowserPreviewData($this->browser_preview_data);
$view_in_browser->view();
}
function _after() {
ORM::raw_execute('TRUNCATE ' . Newsletter::$_table);
ORM::raw_execute('TRUNCATE ' . Subscriber::$_table);
ORM::raw_execute('TRUNCATE ' . SendingQueue::$_table);
}
}

View File

@ -0,0 +1,130 @@
<?php
use Codeception\Util\Stub;
use MailPoet\Router\Router;
require_once('RouterTestMockEndpoint.php');
class FrontRouterTest extends MailPoetTest {
public $router_data;
public $router;
function __construct() {
$this->router_data = array(
Router::NAME => '',
'endpoint' => 'mock_endpoint',
'action' => 'test',
'data' => base64_encode(serialize(array('data' => 'dummy data')))
);
$this->router = new Router($this->router_data);
}
function testItCanGetAPIDataFromGetRequest() {
$data = array('data' => 'dummy data');
$url = 'http://example.com/?' . Router::NAME . '&endpoint=view_in_browser&action=view&data='
. base64_encode(serialize($data));
parse_str(parse_url($url, PHP_URL_QUERY), $_GET);
$router = new Router();
expect($router->api_request)->equals(true);
expect($router->endpoint)->equals('viewInBrowser');
expect($router->action)->equals('view');
expect($router->data)->equals($data);
}
function testItContinuesExecutionWhenAPIRequestNotDetected() {
$router_data = $this->router_data;
unset($router_data[Router::NAME]);
$router = Stub::construct(
new Router(),
array($router_data)
);
$result = $router->init();
expect($result)->null();
}
function testItTerminatesRequestWhenEndpointNotFound() {
$router_data = $this->router_data;
$router_data['endpoint'] = 'invalid_endpoint';
$router = Stub::construct(
new Router(),
array($router_data),
array(
'terminateRequest' => function($code, $error) {
return array(
$code,
$error
);
}
)
);
$result = $router->init();
expect($result)->equals(
array(
404,
'Invalid router endpoint.'
)
);
}
function testItTerminatesRequestWhenEndpointActionNotFound() {
$router_data = $this->router_data;
$router_data['action'] = 'invalid_action';
$router = Stub::construct(
new Router(),
array($router_data),
array(
'terminateRequest' => function($code, $error) {
return array(
$code,
$error
);
}
)
);
$result = $router->init();
expect($result)->equals(
array(
404,
'Invalid router endpoint action.'
)
);
}
function testItCallsEndpointAction() {
$data = array('data' => 'dummy data');
$result = $this->router->init();
expect($result)->equals($data);
}
function testItCanEncodeRequestData() {
$data = array('data' => 'dummy data');
$result = Router::encodeRequestData($data);
expect($result)->equals(
rtrim(base64_encode(serialize($data)), '=')
);
}
function testItReturnsEmptyArrayWhenRequestDataIsAString() {
$encoded_data = 'test';
$result = Router::decodeRequestData($encoded_data);
expect($result)->equals(array());
}
function testItCanDecodeRequestData() {
$data = array('data' => 'dummy data');
$encoded_data = rtrim(base64_encode(serialize($data)), '=');
$result = Router::decodeRequestData($encoded_data);
expect($result)->equals($data);
}
function testItCanBuildRequest() {
$data = array('data' => 'dummy data');
$encoded_data = rtrim(base64_encode(serialize($data)), '=');
$result = Router::buildRequest(
'mock_endpoint',
'test',
$data
);
expect($result)->contains(Router::NAME . '&endpoint=mock_endpoint&action=test&data=' . $encoded_data);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace MailPoet\Router\Endpoints;
class MockEndpoint {
const ACTION_TEST = 'test';
public $allowed_actions = array(
self::ACTION_TEST
);
public $data;
function __construct($data) {
$this->data = $data;
}
function test() {
return $this->data;
}
}

View File

@ -135,7 +135,7 @@ class ClicksTest extends MailPoetTest {
} }
function testItDoesNotConvertRegulaUrls() { function testItDoesNotConvertRegularUrls() {
$link = $this->clicks->processUrl( $link = $this->clicks->processUrl(
'http://example.com', 'http://example.com',
$this->newsletter, $this->newsletter,

View File

@ -1,16 +1,19 @@
<div class="handlediv" title="Click to toggle"><br></div> <div class="handlediv" title="Click to toggle"><br></div>
<h3><%= __('Preview') %></h3> <h3><%= __('Preview') %></h3>
<div class="mailpoet_region_content"> <div class="mailpoet_region_content">
<iframe name="mailpoet_save_preview_email_for_autocomplete" style="display:none" src="about:blank"></iframe>
<form target="mailpoet_save_preview_email_for_autocomplete" action="about:blank">
<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="<%= settings.sender.address %>" /> <input id="mailpoet_preview_to_email" class="mailpoet_input mailpoet_input_full" type="text" name="to_email" value="<%= current_wp_user.email %>" autocomplete="email" />
</label> </label>
</div> </div>
<div class="mailpoet_form_field"> <div class="mailpoet_form_field">
<input type="button" id="mailpoet_send_preview" class="button button-primary mailpoet_button_full" value="<%= __('Send preview') %>" /> <input type="submit" id="mailpoet_send_preview" class="button button-primary mailpoet_button_full" value="<%= __('Send preview') %>" />
</div> </div>
</ofmr>
<hr class="mailpoet_separator" /> <hr class="mailpoet_separator" />