Compare commits

...

34 Commits
3.0.5 ... 3.0.7

Author SHA1 Message Date
7b12affb77 Bumps up release version to 3.0.7 and updates changelog 2017-10-17 14:08:16 -04:00
0150e699a2 Merge pull request #1151 from mailpoet/oct17_poll_update
Updates weekly poll [MAILPOET-1170]
2017-10-17 17:06:39 +03:00
e6943e2638 Updates weekly poll 2017-10-17 09:34:04 -04:00
ac268c1ec9 Merge pull request #1150 from mailpoet/beacon_cron_url_update
Uses CronHelper's method to return cron ping URL in beacon [MAILPOET-1164]
2017-10-17 09:54:12 +03:00
1ef131fa2d Merge pull request #1149 from mailpoet/capabilities_fix
Fixes "Call to a member function add_cap() on null" error [MAILPOET-1169]
2017-10-17 09:46:48 +03:00
1873007550 Improve a unit test for non-existent roles' capabilities [MAILPOET-1169] 2017-10-17 09:35:50 +03:00
fa2ccb51c9 Uses CronHelper's method to return cron ping URL in beacon 2017-10-16 23:46:17 -04:00
8f87d654af Merge pull request #1148 from mailpoet/stats_cta
Add CTA links for detailed stats [MAILPOET-1152]
2017-10-16 23:33:10 -04:00
dee6e9fbad Fixes "Call to a member function add_cap() on null" error 2017-10-16 22:55:11 -04:00
ef90264316 Add CTA links for detailed stats [MAILPOET-1152] 2017-10-13 01:11:06 +03:00
70bf4be723 ESLint: Test rules 1
[MAILPOET-1134]
2017-10-12 15:36:40 +01:00
07ef727654 Merge pull request #1139 from mailpoet/throttling
Add progressive throttling of subscriptions from the same IP address [MAILPOET-1128]
2017-10-12 15:56:40 +02:00
5ce1eadde7 Merge pull request #1147 from mailpoet/honeypot-autofill
Subscription form honeypot gets filled by Autofill [MAILPOET-1163]
2017-10-12 16:52:22 +03:00
8a4d5395b1 Add attribute for Chrome
[MAILPOET-1163]
2017-10-12 14:38:45 +01:00
b5feed0f46 Remove WP subscribers with empty emails when syncing [MAILPOET-1158] 2017-10-12 10:31:11 +01:00
11f9579101 Don't synchronize WP users without emails [MAILPOET-1158] 2017-10-12 10:31:11 +01:00
0f6619e25d Merge pull request #1140 from mailpoet/svn_publish
Remove a dependency on WP in svn:publish command [MAILPOET-1156]
2017-10-11 13:21:10 +03:00
daf747d3be Throw an exception if plugin version could not be determined [MAILPOET-1156] 2017-10-11 13:07:53 +03:00
7393b1f2cf Remove a dependency on WP in svn:publish command [MAILPOET-1156] 2017-10-11 10:54:28 +03:00
efe861a9ba Merge pull request #1137 from mailpoet/eslint4
Eslint for tests [MAILPOET-1083]
2017-10-11 10:00:53 +03:00
2c358ab179 Add progressive throttling of subscriptions from the same IP address [MAILPOET-1128] 2017-10-10 19:36:20 +03:00
ca157fc91d Release 3.0.6 2017-10-10 16:46:37 +03:00
1fbe5d7bc6 Merge pull request #1138 from mailpoet/parsley
fixing missing parsley method [MAILPOET-1157]
2017-10-10 16:34:18 +03:00
71c031ccf9 fixing missing parsley method 2017-10-10 13:10:49 +00:00
6449b7ccca fixed minor issue 2017-10-10 09:29:22 +00:00
d6af88d667 Tests object-curly-spacing 2017-10-10 09:09:03 +00:00
b9184a202f Tests func-call-spacing 2017-10-10 09:09:03 +00:00
f898746967 Tests keyword-spacing 2017-10-10 09:09:03 +00:00
68165b7b78 Tests space-before-function-paren 2017-10-10 09:09:03 +00:00
bb8591a67b Tests space-before-blocks 2017-10-10 09:07:29 +00:00
bda71ae78e Tests space-unary-ops 2017-10-10 09:07:29 +00:00
abd4f6cac2 Tests no-spaced-func 2017-10-10 09:07:29 +00:00
87e6cc2a4f Tests no-whitespace-before-property 2017-10-10 09:07:29 +00:00
dde598eb64 rebasing on master 2017-10-10 09:07:29 +00:00
49 changed files with 701 additions and 392 deletions

View File

@ -8,21 +8,13 @@
"ecmaVersion": 6
},
"rules": {
"import/no-amd": 0,
"no-whitespace-before-property": 0,
"global-require": 0,
"keyword-spacing": 0,
// Exceptions
"func-names": 0,
// To add
"no-bitwise": 0,
"no-spaced-func": 0,
"func-call-spacing": 0,
"max-len": 0,
"space-unary-ops": 0,
"no-underscore-dangle": 0,
"no-shadow": 0,
"padded-blocks": 0,
"space-before-blocks": 0,
"object-curly-spacing": 0,
"func-names": 0,
"space-before-function-paren": 0
"padded-blocks": 0
}
}

View File

@ -289,15 +289,16 @@ class RoboFile extends \Robo\Tasks {
}
function svnPublish($opts = ['force' => false]) {
$this->loadWPFunctions();
$this->loadEnv();
$svn_dir = ".mp_svn";
$plugin_data = get_plugin_data('mailpoet.php', false, false);
$plugin_version = $plugin_data['Version'];
$plugin_dist_name = sanitize_title_with_dashes($plugin_data['Name']);
$plugin_dist_name = explode('-', $plugin_dist_name);
$plugin_dist_name = $plugin_dist_name[0];
$plugin_version = $this->getPluginVersion('mailpoet.php');
$plugin_dist_name = 'mailpoet';
$plugin_dist_file = $plugin_dist_name . '.zip';
if(!$plugin_version) {
throw new \Exception('Could not parse plugin version, check the plugin header');
}
$this->say('Publishing version: ' . $plugin_version);
// Sanity checks
@ -416,13 +417,9 @@ class RoboFile extends \Robo\Tasks {
$dotenv->load();
}
protected function loadWPFunctions() {
$this->loadEnv();
define('ABSPATH', getenv('WP_TEST_PATH') . '/');
define('WPINC', 'wp-includes');
require_once(ABSPATH . WPINC . '/functions.php');
require_once(ABSPATH . WPINC . '/formatting.php');
require_once(ABSPATH . WPINC . '/plugin.php');
require_once(ABSPATH . 'wp-admin/includes/plugin.php');
protected function getPluginVersion($file) {
$data = file_get_contents($file);
preg_match('/^[ \t*]*Version:(.*)$/mi', $data, $m);
return !empty($m[1]) ? trim($m[1]) : false;
}
}

View File

@ -51,8 +51,7 @@ define([
.on('resizestart', function () {
that.isBeingResized = true;
that.$el.addClass('mailpoet_resize_active');
})
.on('resizemove', function (event) {
}).on('resizemove', function (event) {
var onResize = that.options.onResize.bind(that);
return onResize(event);
})

View File

@ -143,6 +143,13 @@ const _QueueMixin = {
},
};
const trackStatsCTAClicked = function () {
MailPoet.trackEvent(
'User has clicked a CTA to view detailed stats',
{ 'MailPoet Free version': window.mailpoet_version }
);
};
const _StatisticsMixin = {
renderStatistics: function (newsletter, is_sent, current_time) {
let sent = is_sent;
@ -159,6 +166,7 @@ const _StatisticsMixin = {
}
let params = {};
Hooks.addFilter('mailpoet_newsletters_listing_stats_before', this.addStatsCTALink);
params = Hooks.applyFilters('mailpoet_newsletters_listing_stats_before', params, newsletter);
// welcome emails provide explicit total_sent value
@ -282,6 +290,20 @@ const _StatisticsMixin = {
if (total_sent > 0 && params.link) {
// wrap content in a link
if (params.externalLink) {
return (
<div>
<a
key={`stats-${newsletter.id}`}
href={params.link}
onClick={params.onClick || null}
>
{content}
</a>
{after_content}
</div>
);
} else {
return (
<div>
<Link
@ -295,6 +317,7 @@ const _StatisticsMixin = {
</div>
);
}
}
return (
<div>
@ -303,6 +326,37 @@ const _StatisticsMixin = {
</div>
);
},
addStatsCTAAction: function (actions) {
if (window.mailpoet_premium_active) {
return actions;
}
actions.unshift({
name: 'stats',
link: function () {
return (
<a href={'admin.php?page=mailpoet-premium'} onClick={trackStatsCTAClicked}>
{MailPoet.I18n.t('statsListingActionTitle')}
</a>
);
},
display: function (newsletter) {
// welcome emails provide explicit total_sent value
const count_processed = newsletter.queue && newsletter.queue.count_processed;
return ~~(newsletter.total_sent || count_processed) > 0;
},
});
return actions;
},
addStatsCTALink: function (params) {
if (window.mailpoet_premium_active) {
return params;
}
const newParams = params;
newParams.link = 'admin.php?page=mailpoet-premium';
newParams.externalLink = true;
newParams.onClick = trackStatsCTAClicked;
return newParams;
},
};
const _MailerMixin = {

View File

@ -52,6 +52,7 @@ let newsletter_actions = [
},
];
Hooks.addFilter('mailpoet_newsletters_listings_notification_history_actions', StatisticsMixin.addStatsCTAAction);
newsletter_actions = Hooks.applyFilters('mailpoet_newsletters_listings_notification_history_actions', newsletter_actions);
const NewsletterListNotificationHistory = React.createClass({

View File

@ -157,6 +157,7 @@ let newsletter_actions = [
},
];
Hooks.addFilter('mailpoet_newsletters_listings_standard_actions', StatisticsMixin.addStatsCTAAction);
newsletter_actions = Hooks.applyFilters('mailpoet_newsletters_listings_standard_actions', newsletter_actions);
const NewsletterListStandard = React.createClass({

View File

@ -124,6 +124,7 @@ let newsletter_actions = [
},
];
Hooks.addFilter('mailpoet_newsletters_listings_welcome_notification_actions', StatisticsMixin.addStatsCTAAction);
newsletter_actions = Hooks.applyFilters('mailpoet_newsletters_listings_welcome_notification_actions', newsletter_actions);
const NewsletterListWelcome = React.createClass({

View File

@ -1,6 +1,7 @@
define([
'mailpoet',
'jquery'
'jquery',
'parsleyjs'
],
function (
MailPoet,

View File

@ -10,7 +10,7 @@ use MailPoet\Form\Util\FieldNameObfuscator;
use MailPoet\Models\Form;
use MailPoet\Models\StatisticsForms;
use MailPoet\Models\Subscriber;
use MailPoet\Util\Helpers;
use MailPoet\Subscription\Throttling as SubscriptionThrottling;
if(!defined('ABSPATH')) exit;
@ -98,16 +98,10 @@ class Subscribers extends APIEndpoint {
$data = array_intersect_key($data, array_flip($form_fields));
// make sure we don't allow too many subscriptions with the same ip address
$subscription_count = Subscriber::where(
'subscribed_ip',
Helpers::getIP()
)->whereRaw(
'(TIME_TO_SEC(TIMEDIFF(NOW(), created_at)) < ? OR TIME_TO_SEC(TIMEDIFF(NOW(), updated_at)) < ?)',
array(self::SUBSCRIPTION_LIMIT_COOLDOWN, self::SUBSCRIPTION_LIMIT_COOLDOWN)
)->count();
$timeout = SubscriptionThrottling::throttle();
if($subscription_count > 0) {
throw new \Exception(__('You need to wait before subscribing again.', 'mailpoet'));
if($timeout > 0) {
throw new \Exception(sprintf(__('You need to wait %d seconds before subscribing again.', 'mailpoet'), $timeout));
}
$subscriber = Subscriber::subscribe($data, $segment_ids);

View File

@ -27,6 +27,7 @@ class Capabilities {
if(!isset($role_objects[$role])) {
$role_objects[$role] = get_role($role);
}
if(!is_object($role_objects[$role])) continue;
$role_objects[$role]->add_cap($name);
}
}
@ -40,6 +41,7 @@ class Capabilities {
if(!isset($role_objects[$role])) {
$role_objects[$role] = get_role($role);
}
if(!is_object($role_objects[$role])) continue;
$role_objects[$role]->remove_cap($name);
}
}

View File

@ -68,6 +68,7 @@ class Database {
$subscribers = Env::$db_prefix . 'subscribers';
$subscriber_segment = Env::$db_prefix . 'subscriber_segment';
$subscriber_custom_field = Env::$db_prefix . 'subscriber_custom_field';
$subscriber_ips = Env::$db_prefix . 'subscriber_ips';
$newsletter_segment = Env::$db_prefix . 'newsletter_segment';
$scheduled_tasks = Env::$db_prefix . 'scheduled_tasks';
$scheduled_task_subscribers = Env::$db_prefix . 'scheduled_task_subscribers';
@ -92,6 +93,7 @@ class Database {
define('MP_SUBSCRIBERS_TABLE', $subscribers);
define('MP_SUBSCRIBER_SEGMENT_TABLE', $subscriber_segment);
define('MP_SUBSCRIBER_CUSTOM_FIELD_TABLE', $subscriber_custom_field);
define('MP_SUBSCRIBER_IPS_TABLE', $subscriber_ips);
define('MP_SCHEDULED_TASKS_TABLE', $scheduled_tasks);
define('MP_SCHEDULED_TASK_SUBSCRIBERS_TABLE', $scheduled_task_subscribers);
define('MP_SENDING_QUEUES_TABLE', $sending_queues);

View File

@ -529,6 +529,7 @@ class Menu {
);
$data['tracking_enabled'] = Setting::getValue('tracking.enabled');
$data['premium_plugin_active'] = License::getLicense();
wp_enqueue_script('jquery-ui');
wp_enqueue_script('jquery-ui-datepicker');

View File

@ -23,6 +23,7 @@ class Migrator {
'subscribers',
'subscriber_segment',
'subscriber_custom_field',
'subscriber_ips',
'newsletters',
'newsletter_templates',
'newsletter_option_fields',
@ -207,6 +208,16 @@ class Migrator {
return $this->sqlify(__FUNCTION__, $attributes);
}
function subscriberIps() {
$attributes = array(
'ip varchar(45) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'PRIMARY KEY (created_at, ip),',
'KEY ip (ip)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function newsletters() {
$attributes = array(
'id int(11) unsigned NOT NULL AUTO_INCREMENT,',

View File

@ -41,7 +41,7 @@ class Renderer {
static function renderBlocks($blocks = array(), $honeypot_enabled = true) {
// add honeypot for spambots
$html = ($honeypot_enabled) ?
'<label class="mailpoet_hp_email_label">' . __('Please leave this field empty', 'mailpoet') . '<input type="email" name="data[email]"></label>' :
'<label class="mailpoet_hp_email_label">' . __('Please leave this field empty', 'mailpoet') . '<input autocomplete="not-really-email" type="email" name="data[email]"></label>' :
'';
foreach($blocks as $key => $block) {
$html .= static::renderBlock($block) . PHP_EOL;

View File

@ -1,10 +1,11 @@
<?php
namespace MailPoet\Helpscout;
use MailPoet\Cron\CronHelper;
use MailPoet\Models\Subscriber;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Router\Endpoints\CronDaemon;
use MailPoet\Router\Router;
use MailPoet\Services\Bridge;
if(!defined('ABSPATH')) exit;
@ -16,11 +17,6 @@ class Beacon {
$mta = Setting::getValue('mta');
$current_theme = wp_get_theme();
$current_user = wp_get_current_user();
$cron_ping_url = Router::buildRequest(
CronDaemon::ENDPOINT,
CronDaemon::ACTION_PING
);
$cron_ping_url = str_replace(home_url(), CronHelper::getSiteUrl(), $cron_ping_url);
$premium_key = Setting::getValue(Bridge::PREMIUM_KEY_SETTING_NAME) ?: Setting::getValue(Bridge::API_KEY_SETTING_NAME);
return array(
'name' => $current_user->display_name,
@ -51,7 +47,9 @@ class Beacon {
$mta['frequency']['interval']
),
'Task Scheduler method' => Setting::getValue('cron_trigger.method'),
'Cron ping URL' => $cron_ping_url,
'Cron ping URL' => CronHelper::getCronUrl(
CronDaemon::ACTION_PING
),
'Default FROM address' => Setting::getValue('sender.address'),
'Default Reply-To address' => Setting::getValue('reply_to.address'),
'Bounce Email Address' => Setting::getValue('bounce.address'),

View File

@ -0,0 +1,8 @@
<?php
namespace MailPoet\Models;
if(!defined('ABSPATH')) exit;
class SubscriberIP extends Model {
public static $_table = MP_SUBSCRIBER_IPS_TABLE;
}

View File

@ -99,7 +99,7 @@ class WP {
UPDATE IGNORE %s
JOIN %s as wu ON %s.wp_user_id = wu.id
SET email = user_email
WHERE %s.wp_user_id IS NOT NULL
WHERE %s.wp_user_id IS NOT NULL AND wu.user_email != ""
', $subscribers_table, $wpdb->users, $subscribers_table, $subscribers_table));
}
@ -110,7 +110,7 @@ class WP {
INSERT IGNORE INTO %s(wp_user_id, email, status, created_at)
SELECT wu.id, wu.user_email, "subscribed", CURRENT_TIMESTAMP() FROM %s wu
LEFT JOIN %s mps ON wu.id = mps.wp_user_id
WHERE mps.wp_user_id IS NULL
WHERE mps.wp_user_id IS NULL AND wu.user_email != ""
ON DUPLICATE KEY UPDATE wp_user_id = wu.id
', $subscribers_table, $wpdb->users, $subscribers_table));
}
@ -180,7 +180,7 @@ class WP {
$wp_segment->subscribers()
->leftOuterJoin($wpdb->users, array(MP_SUBSCRIBERS_TABLE . '.wp_user_id', '=', 'wu.id'), 'wu')
->whereNull('wu.id')
->whereRaw('(wu.id IS NULL OR ' . MP_SUBSCRIBERS_TABLE . '.email = "")')
->findResultSet()
->set('wp_user_id', null)
->delete();

View File

@ -0,0 +1,55 @@
<?php
namespace MailPoet\Subscription;
use MailPoet\Models\SubscriberIP;
use MailPoet\Util\Helpers;
use MailPoet\WP\Hooks;
class Throttling {
static function throttle() {
$subscription_limit_enabled = Hooks::applyFilters('mailpoet_subscription_limit_enabled', true);
$subscription_limit_window = Hooks::applyFilters('mailpoet_subscription_limit_window', DAY_IN_SECONDS);
$subscription_limit_base = Hooks::applyFilters('mailpoet_subscription_limit_base', MINUTE_IN_SECONDS);
$subscriber_ip = Helpers::getIP();
if($subscription_limit_enabled && !is_user_logged_in()) {
if(!empty($subscriber_ip)) {
$subscription_count = SubscriberIP::where('ip', $subscriber_ip)
->whereRaw(
'(`created_at` >= NOW() - INTERVAL ? SECOND)',
array((int)$subscription_limit_window)
)->count();
if($subscription_count > 0) {
$timeout = $subscription_limit_base * pow(2, $subscription_count - 1);
$existing_user = SubscriberIP::where('ip', $subscriber_ip)
->whereRaw(
'(`created_at` >= NOW() - INTERVAL ? SECOND)',
array((int)$timeout)
)->findOne();
if(!empty($existing_user)) {
return $timeout;
}
}
}
}
$ip = SubscriberIP::create();
$ip->ip = $subscriber_ip;
$ip->save();
self::purge($subscription_limit_window);
return false;
}
static function purge($interval) {
return SubscriberIP::whereRaw(
'(`created_at` < NOW() - INTERVAL ? SECOND)',
array($interval)
)->deleteMany();
}
}

View File

@ -10,6 +10,10 @@ class Hooks {
return self::callWithFallback('apply_filters', func_get_args());
}
static function removeFilter() {
return self::callWithFallback('remove_filter', func_get_args());
}
static function addAction() {
return self::callWithFallback('add_action', func_get_args());
}
@ -18,6 +22,10 @@ class Hooks {
return self::callWithFallback('do_action', func_get_args());
}
static function removeAction() {
return self::callWithFallback('remove_action', func_get_args());
}
private static function callWithFallback($func, $args) {
$local_func = __NAMESPACE__ . '\\' . $func;
if(function_exists($local_func)) {

View File

@ -4,7 +4,7 @@ if(!defined('ABSPATH')) exit;
/*
* Plugin Name: MailPoet 3 (new)
* Version: 3.0.5
* Version: 3.0.7
* Plugin URI: http://www.mailpoet.com
* Description: Create and send newsletters, post notifications and welcome emails from your WordPress.
* Author: MailPoet
@ -20,7 +20,7 @@ if(!defined('ABSPATH')) exit;
*/
$mailpoet_plugin = array(
'version' => '3.0.5',
'version' => '3.0.7',
'filename' => __FILE__,
'path' => dirname(__FILE__),
'autoloader' => dirname(__FILE__) . '/vendor/autoload.php',

View File

@ -4,7 +4,7 @@ Tags: newsletter, email, welcome email, post notification, autoresponder, signup
Requires at least: 4.6
Tested up to: 4.8
Requires PHP: 5.3
Stable tag: 3.0.5
Stable tag: 3.0.7
License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html
@ -114,6 +114,15 @@ Stop by our [support site](https://www.mailpoet.com/support).
== Changelog ==
= 3.0.7 - 2017-10-17 =
* Improved: subscribing from the same IP address is progressively throttled. Thanks Suyog Palav, Piyush Kumar and Bits of Freedom!
* Fixed: WordPress users without an email address will not be added as subscribers;
* Fixed: bug asking subscribers to leave the first field empty in MailPoet subscription forms;
* Fixed: plugin no longer fails to activate on sites when certain user roles do not exist. Thanks to all who reported this!
= 3.0.6 - 2017-10-10 =
* Fixed: subscription forms to not throw form validation engine errors;
= 3.0.5 - 2017-10-10 =
* Added: images can now be aligned left, center or right in email designer;

View File

@ -25,6 +25,7 @@ $models = array(
'Subscriber',
'SubscriberCustomField',
'SubscriberSegment',
'SubscriberIP',
'StatisticsOpens',
'StatisticsClicks',
'StatisticsNewsletters',

View File

@ -8,6 +8,7 @@ use MailPoet\API\JSON\Response as APIResponse;
use MailPoet\Form\Util\FieldNameObfuscator;
use MailPoet\Models\Form;
use MailPoet\Models\Subscriber;
use MailPoet\Models\SubscriberIP;
use MailPoet\Models\Segment;
use MailPoet\Models\Setting;
@ -516,7 +517,7 @@ class SubscribersTest extends \MailPoetTest {
));
$this->fail('It should not be possible to subscribe a second time so soon');
} catch(\Exception $e) {
expect($e->getMessage())->equals('You need to wait before subscribing again.');
expect($e->getMessage())->equals('You need to wait 60 seconds before subscribing again.');
}
}
@ -544,12 +545,13 @@ class SubscribersTest extends \MailPoetTest {
));
$this->fail('It should not be possible to resubscribe a second time so soon');
} catch(\Exception $e) {
expect($e->getMessage())->equals('You need to wait before subscribing again.');
expect($e->getMessage())->equals('You need to wait 60 seconds before subscribing again.');
}
}
function _after() {
Segment::deleteMany();
Subscriber::deleteMany();
SubscriberIP::deleteMany();
}
}

View File

@ -7,6 +7,7 @@ use Helper\WordPressHooks as WPHooksHelper;
use MailPoet\Config\AccessControl;
use MailPoet\Config\Capabilities;
use MailPoet\Config\Renderer;
use MailPoet\WP\Hooks;
class CapabilitiesTest extends \MailPoetTest {
function _before() {
@ -52,6 +53,32 @@ class CapabilitiesTest extends \MailPoetTest {
$this->caps->setupWPCapabilities();
}
function testItDoesNotSetupCapabilitiesForNonexistentRoles() {
$this->caps->removeWPCapabilities();
$filter = function() {
return array('nonexistent_role');
};
Hooks::addFilter('mailpoet_permission_access_plugin_admin', $filter);
$this->caps->setupWPCapabilities();
// role does not exist
expect(get_role('nonexistent_role'))->null();
// other MailPoet capabilities were successfully configured
$editor_role = get_role('editor');
expect($editor_role->has_cap(AccessControl::PERMISSION_ACCESS_PLUGIN_ADMIN))->false();
expect($editor_role->has_cap(AccessControl::PERMISSION_MANAGE_EMAILS))->true();
// Restore capabilities
Hooks::removeFilter('mailpoet_permission_access_plugin_admin', $filter);
$this->caps->setupWPCapabilities();
$editor_role = get_role('editor');
expect($editor_role->has_cap(AccessControl::PERMISSION_ACCESS_PLUGIN_ADMIN))->true();
expect($editor_role->has_cap(AccessControl::PERMISSION_MANAGE_EMAILS))->true();
}
function testItSetsUpMembersCapabilities() {
WPHooksHelper::interceptAddAction();

View File

@ -5,6 +5,7 @@ use MailPoet\Helpscout\Beacon;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Services\Bridge;
use MailPoet\WP\Hooks;
class BeaconTest extends \MailPoetTest {
function _before() {
@ -122,8 +123,16 @@ class BeaconTest extends \MailPoetTest {
expect($this->beacon_data['Server OS'])->equals(utf8_encode(php_uname()));
}
function testItReturnsCronPingResponse() {
function testItReturnsCronPingUrl() {
expect($this->beacon_data['Cron ping URL'])->contains('&action=ping');
// cron ping URL should react to custom filters
$filter = function($url) {
return str_replace(home_url(), 'http://custom_url/', $url);
};
Hooks::addFilter('mailpoet_cron_request_url', $filter);
$beacon_data = Beacon::getData();
expect($beacon_data['Cron ping URL'])->regExp('!^http:\/\/custom_url\/!');
Hooks::removeFilter('mailpoet_cron_request_url', $filter);
}
function testItReturnsPremiumVersion() {

View File

@ -54,6 +54,25 @@ class WPTest extends \MailPoetTest {
expect($subscriber->email)->equals('user-sync-test-xx@email.com');
}
function testItDoesNotSynchronizeEmptyEmailsForExistingUsers() {
$id = $this->insertUser();
WP::synchronizeUsers();
$this->updateWPUserEmail($id, '');
WP::synchronizeUsers();
$subscriber = Subscriber::where('wp_user_id', $id)->findOne();
expect($subscriber->email)->notEmpty();
$this->deleteWPUser($id);
}
function testItDoesNotSynchronizeEmptyEmailsForNewUsers() {
$id = $this->insertUser();
$this->updateWPUserEmail($id, '');
WP::synchronizeUsers();
$subscriber = Subscriber::where('wp_user_id', $id)->findOne();
expect($subscriber)->isEmpty();
$this->deleteWPUser($id);
}
function testItSynchronizeFirstNames() {
$id = $this->insertUser();
WP::synchronizeUsers();
@ -152,9 +171,22 @@ class WPTest extends \MailPoetTest {
));
$subscriber2->status = Subscriber::STATUS_SUBSCRIBED;
$subscriber2->save();
// email is empty
$subscriber3 = Subscriber::create();
$subscriber3->hydrate(array(
'first_name' => 'Dave',
'last_name' => 'Dave',
'email' => 'user-sync-test3' . rand() . '@example.com', // need to pass validation
));
$subscriber3->status = Subscriber::STATUS_SUBSCRIBED;
$subscriber3->save();
$this->clearEmail($subscriber3);
WP::synchronizeUsers();
$subscribersCount = $this->getSubscribersCount();
expect($subscribersCount)->equals(3);
$db_subscriber = Subscriber::findOne($subscriber3->id);
expect($db_subscriber)->notEmpty();
$subscriber3->delete();
}
function testItRemovesSubscribersInWPSegmentWithoutWPId() {
@ -179,6 +211,31 @@ class WPTest extends \MailPoetTest {
expect($subscribersCount)->equals(0);
}
function testItRemovesSubscribersInWPSegmentWithoutEmail() {
$id = $this->insertUser();
$this->updateWPUserEmail($id, '');
$subscriber = Subscriber::create();
$subscriber->hydrate(array(
'first_name' => 'Mike',
'last_name' => 'Mike',
'email' => 'user-sync-test' . rand() . '@example.com', // need to pass validation
'wp_user_id' => $id,
));
$subscriber->status = Subscriber::STATUS_SUBSCRIBED;
$subscriber->save();
$this->clearEmail($subscriber);
$wp_segment = Segment::getWPSegment();
$association = SubscriberSegment::create();
$association->subscriber_id = $subscriber->id;
$association->segment_id = $wp_segment->id;
$association->save();
$db_subscriber = Subscriber::findOne($subscriber->id);
expect($db_subscriber)->notEmpty();
WP::synchronizeUsers();
$db_subscriber = Subscriber::findOne($subscriber->id);
expect($db_subscriber)->isEmpty();
}
function _before() {
$this->cleanData();
}
@ -282,4 +339,11 @@ class WPTest extends \MailPoetTest {
', $wpdb->users, $id));
}
private function clearEmail($subscriber) {
\ORM::raw_execute('
UPDATE ' . MP_SUBSCRIBERS_TABLE . '
SET `email` = "" WHERE `id` = ' . $subscriber->id
);
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace MailPoet\Test\Subscription;
use Carbon\Carbon;
use MailPoet\Models\SubscriberIP;
use MailPoet\Subscription\Throttling;
use MailPoet\WP\Hooks;
class ThrottlingTest extends \MailPoetTest {
function testItProgressivelyThrottlesSubscriptions() {
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
expect(Throttling::throttle())->equals(false);
expect(Throttling::throttle())->equals(60);
for($i = 0; $i < 11; $i++) {
$ip = SubscriberIP::create();
$ip->ip = '127.0.0.1';
$ip->created_at = Carbon::now()->subMinutes($i);
$ip->save();
}
expect(Throttling::throttle())->equals(MINUTE_IN_SECONDS * pow(2, 10));
}
function testItDoesNotThrottleIfDisabledByAHook() {
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
Hooks::addFilter('mailpoet_subscription_limit_enabled', '__return_false');
expect(Throttling::throttle())->equals(false);
expect(Throttling::throttle())->equals(false);
Hooks::removeFilter('mailpoet_subscription_limit_enabled', '__return_false');
expect(Throttling::throttle())->greaterThan(0);
}
function testItDoesNotThrottleForLoggedInUsers() {
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
$wp_users = get_users();
wp_set_current_user($wp_users[0]->ID);
expect(Throttling::throttle())->equals(false);
expect(Throttling::throttle())->equals(false);
wp_set_current_user(0);
expect(Throttling::throttle())->greaterThan(0);
}
function testItPurgesOldSubscriberIps() {
$ip = SubscriberIP::create();
$ip->ip = '127.0.0.1';
$ip->save();
$ip2 = SubscriberIP::create();
$ip2->ip = '127.0.0.1';
$ip2->created_at = Carbon::now()->subDays(1)->subSeconds(1);
$ip2->save();
expect(SubscriberIP::count())->equals(2);
Throttling::throttle();
expect(SubscriberIP::count())->equals(1);
}
function _after() {
SubscriberIP::deleteMany();
}
}

View File

@ -24,6 +24,11 @@ class HooksTest extends \MailPoetTest {
Hooks::doAction($this->action, $test_value, $test_value2);
expect($called)->true();
$called = false;
Hooks::removeAction($this->action, $callback);
Hooks::doAction($this->action);
expect($called)->false();
}
function testItCanProcessFilters() {
@ -41,5 +46,10 @@ class HooksTest extends \MailPoetTest {
expect($called)->true();
expect($result)->equals($test_value);
$called = false;
Hooks::removeFilter($this->filter, $callback);
Hooks::applyFilters($this->filter, $test_value);
expect($called)->false();
}
}

View File

@ -15,6 +15,7 @@
var mailpoet_date_display_format = "<%= wp_date_format() %>";
var mailpoet_date_storage_format = "Y-m-d";
var mailpoet_tracking_enabled = <%= json_encode(tracking_enabled) %>;
var mailpoet_premium_active = <%= json_encode(premium_plugin_active) %>;
</script>
<% endblock %>
@ -60,6 +61,7 @@
'subject': __('Subject'),
'status': __('Status'),
'statsListingActionTitle': __('Statistics'),
'statistics': __('Opened, Clicked'),
'lists': __('Lists'),
'settings': __('Settings'),

View File

@ -60,8 +60,8 @@
<div class="feature-section one-col mailpoet_centered">
<h2><%= __('Care to Give Your Opinion?') %></h2>
<script type="text/javascript" charset="utf-8" src="https://static.polldaddy.com/p/9840615.js"></script>
<noscript><a href="https://polldaddy.com/poll/9840615/">How did you find out about our plugin?</a></noscript>
<script type="text/javascript" charset="utf-8" src="https://secure.polldaddy.com/p/9853743.js"></script>
<noscript><a href="https://polldaddy.com/poll/9853743/">How many people do you know that have their own WordPress website and also write a newsletter?</a></noscript>
</div>
<hr>