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->_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->_exec('vendor/bin/codecept build');
$this->_exec(join(' ', array(
$command = join(' ', array(
'vendor/bin/codecept run',
(($file) ? $file : ''),
(($opts['file']) ? $opts['file'] : ''),
'--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->_exec(join(' ', array(
$command = join(' ', array(
'./node_modules/.bin/mocha',
'-r tests/javascript/mochaTestHelper.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() {

View File

@ -35,7 +35,12 @@ define([
Module.ALCSupervisor = SuperModel.extend({
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() {
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.listenTo(this.get('readMoreButton'), 'change', this._scheduleFetchPosts);
this.listenTo(this.get('divider'), 'change', this._scheduleFetchPosts);
this.on('add remove update reset', function(model, collection, options) {
App.getChannel().trigger('automatedLatestContentRefresh');
});
this.on('add remove update reset', this._scheduleFetchPosts);
this.on('refreshPosts', this.updatePosts, this);
},
updatePosts: function(posts) {
@ -120,16 +123,7 @@ define([
* ALC posts on each model change
*/
_scheduleFetchPosts: function() {
var TIMEOUT = 500,
that = this;
if (this._fetchPostsTimer !== undefined) {
clearTimeout(this._fetchPostsTimer);
}
this._fetchPostsTimer = setTimeout(function() {
//that.fetchPosts();
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
memory_limit: 1024M
log: true
strict_xml: true
extensions:
enabled:
- Codeception\Extension\RunFailed

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,73 +1,34 @@
<?php
namespace MailPoet\Newsletter;
use MailPoet\Models\Newsletter;
use MailPoet\Models\SendingQueue;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\Links\Links;
use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Newsletter\Shortcodes\Shortcodes;
class ViewInBrowser {
static function view($data) {
$data = self::preProcessData($data);
if(!self::validateData($data)) self::abort();
$rendered_newsletter =
self::getAndRenderNewsletter(
function view($data) {
$wp_user_preview = ($data->preview && $data->subscriber->isWPUser());
return $this->renderNewsletter(
$data->newsletter,
$data->subscriber,
$data->queue,
$data->preview
$wp_user_preview
);
header('Content-Type: text/html; charset=utf-8');
echo $rendered_newsletter;
exit;
}
static function preProcessData($data) {
$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) {
function renderNewsletter($newsletter, $subscriber, $queue, $wp_user_preview) {
if($queue && $queue->newsletter_rendered_body) {
$newsletter_body = $queue->getRenderedNewsletterBody();
} else {
$renderer = new Renderer($newsletter, $preview);
$renderer = new Renderer($newsletter, $wp_user_preview);
$newsletter_body = $renderer->render();
}
$shortcodes = new Shortcodes(
$newsletter,
$subscriber,
$queue
);
$rendered_newsletter = $shortcodes->replace($newsletter_body['html']);
if($queue && (boolean)Setting::getValue('tracking.enabled')) {
@ -75,14 +36,9 @@ class ViewInBrowser {
$subscriber->id,
$queue->id,
$rendered_newsletter,
$preview
$wp_user_preview
);
}
return $rendered_newsletter;
}
private static function abort() {
status_header(404);
exit;
}
}

View File

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

View File

@ -7,18 +7,31 @@ if(!defined('ABSPATH')) exit;
class 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) {
$subscription = new UserSubscription\Pages('confirm', $data);
function __construct($data) {
$this->data = $data;
}
function confirm() {
$subscription = new UserSubscription\Pages('confirm', $this->data);
$subscription->confirm();
}
static function manage($data) {
$subscription = new UserSubscription\Pages('manage', $data);
function manage() {
$subscription = new UserSubscription\Pages('manage', $this->data);
}
static function unsubscribe($data) {
$subscription = new UserSubscription\Pages('unsubscribe', $data);
function unsubscribe() {
$subscription = new UserSubscription\Pages('unsubscribe', $this->data);
$subscription->unsubscribe();
}
}

View File

@ -14,18 +14,27 @@ class Track {
const ENDPOINT = 'track';
const ACTION_CLICK = 'click';
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();
return $click_event->track(self::_processTrackData($data));
return $click_event->track($this->data);
}
static function open($data) {
function open() {
$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;
if(empty($data->queue_id) ||
empty($data->subscriber_id) ||
@ -41,10 +50,10 @@ class Track {
if(!empty($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;
$subscriber_token_match =
Subscriber::verifyToken($data->subscriber->email, $data->subscriber_token);

View File

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

View File

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

View File

@ -4,7 +4,7 @@ if(!defined('ABSPATH')) exit;
use \MailPoet\Config\Initializer;
/*
* Plugin Name: MailPoet
* Version: 0.0.41
* Version: 0.0.42
* Plugin URI: http://www.mailpoet.com
* Description: MailPoet Newsletters.
* Author: MailPoet
@ -22,7 +22,7 @@ use \MailPoet\Config\Initializer;
require 'vendor/autoload.php';
define('MAILPOET_VERSION', '0.0.41');
define('MAILPOET_VERSION', '0.0.42');
$initializer = new Initializer(array(
'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,
'preview' => false
);
// instantiate class
$this->track = new Track($this->track_data);
}
function testItReturnsFalseWhenTrackDataIsMissing() {
// queue ID is required
$data = $this->track_data;
unset($data['queue_id']);
expect(Track::_processTrackData($data))->false();
expect($this->track->_processTrackData($data))->false();
// subscriber ID is required
$data = $this->track_data;
unset($data['subscriber_id']);
expect(Track::_processTrackData($data))->false();
expect($this->track->_processTrackData($data))->false();
// subscriber token is required
$data = $this->track_data;
unset($data['subscriber_token']);
expect(Track::_processTrackData($data))->false();
expect($this->track->_processTrackData($data))->false();
}
function testItFailsWhenSubscriberTokenDoesNotMatch() {
@ -66,7 +68,7 @@ class TrackTest extends MailPoetTest {
)
);
$data->subscriber->email = 'random@email.com';
expect(Track::_validateTrackData($data))->false();
expect($this->track->_validateTrackData($data))->false();
}
function testItFailsWhenSubscriberIsNotOnProcessedList() {
@ -79,7 +81,7 @@ class TrackTest extends MailPoetTest {
)
);
$data->subscriber->id = 99;
expect(Track::_validateTrackData($data))->false();
expect($this->track->_validateTrackData($data))->false();
}
function testItDoesNotRequireWpUsersToBeOnProcessedListWhenPreviewIsEnabled() {
@ -93,18 +95,26 @@ class TrackTest extends MailPoetTest {
);
$data->subscriber->wp_user_id = 99;
$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['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);
}
function testItCanProcessTrackData() {
$processed_data = Track::_processTrackData($this->track_data);
function testItProcessesTrackData() {
$processed_data = $this->track->_processTrackData($this->track_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);

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(
'http://example.com',
$this->newsletter,

View File

@ -1,16 +1,19 @@
<div class="handlediv" title="Click to toggle"><br></div>
<h3><%= __('Preview') %></h3>
<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">
<label>
<%= __('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>
</div>
<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>
</ofmr>
<hr class="mailpoet_separator" />