Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
7b12affb77 | |||
0150e699a2 | |||
e6943e2638 | |||
ac268c1ec9 | |||
1ef131fa2d | |||
1873007550 | |||
fa2ccb51c9 | |||
8f87d654af | |||
dee6e9fbad | |||
ef90264316 | |||
70bf4be723 | |||
07ef727654 | |||
5ce1eadde7 | |||
8a4d5395b1 | |||
b5feed0f46 | |||
11f9579101 | |||
0f6619e25d | |||
daf747d3be | |||
7393b1f2cf | |||
efe861a9ba | |||
2c358ab179 | |||
ca157fc91d | |||
1fbe5d7bc6 | |||
71c031ccf9 | |||
6449b7ccca | |||
d6af88d667 | |||
b9184a202f | |||
f898746967 | |||
68165b7b78 | |||
bb8591a67b | |||
bda71ae78e | |||
abd4f6cac2 | |||
87e6cc2a4f | |||
dde598eb64 |
@ -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
|
||||
}
|
||||
}
|
||||
|
25
RoboFile.php
25
RoboFile.php
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
})
|
||||
|
@ -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 = {
|
||||
|
@ -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({
|
||||
|
@ -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({
|
||||
|
@ -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({
|
||||
|
@ -1,6 +1,7 @@
|
||||
define([
|
||||
'mailpoet',
|
||||
'jquery'
|
||||
'jquery',
|
||||
'parsleyjs'
|
||||
],
|
||||
function (
|
||||
MailPoet,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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');
|
||||
|
@ -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,',
|
||||
|
@ -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;
|
||||
|
@ -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'),
|
||||
|
8
lib/Models/SubscriberIP.php
Normal file
8
lib/Models/SubscriberIP.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
namespace MailPoet\Models;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class SubscriberIP extends Model {
|
||||
public static $_table = MP_SUBSCRIBER_IPS_TABLE;
|
||||
}
|
@ -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();
|
||||
|
55
lib/Subscription/Throttling.php
Normal file
55
lib/Subscription/Throttling.php
Normal 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();
|
||||
}
|
||||
}
|
@ -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)) {
|
||||
|
@ -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',
|
||||
|
11
readme.txt
11
readme.txt
@ -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;
|
||||
|
||||
|
@ -25,6 +25,7 @@ $models = array(
|
||||
'Subscriber',
|
||||
'SubscriberCustomField',
|
||||
'SubscriberSegment',
|
||||
'SubscriberIP',
|
||||
'StatisticsOpens',
|
||||
'StatisticsClicks',
|
||||
'StatisticsNewsletters',
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
}
|
60
tests/unit/Subscription/ThrottlingTest.php
Normal file
60
tests/unit/Subscription/ThrottlingTest.php
Normal 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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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'),
|
||||
|
@ -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>
|
||||
|
Reference in New Issue
Block a user