Compare commits

...

46 Commits

Author SHA1 Message Date
c09bcd51ad Merge pull request #675 from mailpoet/amazon_ses_fix
Fixes const value declaration for PHP <5.6
2016-10-28 17:29:57 +03:00
01af4d3401 - Fixes const value declaration for PHP <5.6 2016-10-28 10:13:56 -04:00
2ba9d95a2e Update plugin repo icon to "beta" 2016-10-28 16:03:14 +03:00
b2d4bfc760 Initial MailPoet 3.0.0-beta.1 release 2016-10-28 13:52:40 +03:00
57f5f16bb6 Merge pull request #674 from mailpoet/premium_hook
Bypasses subscriber count enforcement for premium users
2016-10-27 20:47:14 +03:00
7d2e13b9a3 - Updates license check logic
- Updates subscriber limit check logic
- Updates unit tests
- Updates Menu's check for subscriber limit
2016-10-27 12:35:57 -04:00
6d39f9fa78 Merge pull request #671 from mailpoet/plugin_repository_assets
Preparation for plugin repository
2016-10-27 11:36:57 -04:00
a4395f2350 - Adds unit tests 2016-10-27 11:16:30 -04:00
411969b3eb - Adds check for premium plugin status
- Bypasses subscriber count enforcement if premium is enabled
2016-10-27 10:20:05 -04:00
1868ca3155 Merge pull request #673 from mailpoet/scheduler_update
Scheduler update
2016-10-27 13:17:03 +03:00
e765471f5d - Changes month days count to start from 1 instead of 0
- Closes #672
2016-10-26 21:18:07 -04:00
bdce7c5e5a - Remove unused dependency 2016-10-26 11:43:32 -04:00
773be9f5c8 Add assets for plugin repository 2016-10-26 13:59:02 +03:00
6ae46b05e5 Merge pull request #669 from mailpoet/string_updates
String updates
2016-10-25 18:00:44 +03:00
217894745d - Updates text strings
- Closes #655
2016-10-25 10:21:23 -04:00
a03891895c Bump up release version to 0.0.50 and update changelog 2016-10-25 13:04:55 +03:00
3368e84a99 Merge pull request #668 from mailpoet/export_confirmed_subscribers_option_update
Fixes minor export UI issues
2016-10-25 12:46:13 +03:00
e90df2f08d - Fixes Select2 not dislaying multiple options in the list of export
fields
- Sets default "export confirmed subscriber" option to "no"
2016-10-24 13:09:48 -04:00
2391ae1cad Merge pull request #665 from mailpoet/post_notification_fix
Fixes post notification issues
2016-10-24 16:02:19 +03:00
83114a8be4 - Removes unused class declarations 2016-10-24 08:55:22 -04:00
d08d5a3b6c - Updates unit tests 2016-10-24 08:55:22 -04:00
8330bfc884 - Fixes "completed" status update of notification history
newsletters
- Fixes detection of post notification newsletters that do not contain any posts (i.e., blank ALC blocks)
- Updates unit test
2016-10-24 08:55:22 -04:00
ef21a8cca7 - Enables post notification schedule update upon newsletter saving during
step 3
2016-10-24 08:55:22 -04:00
e32c46a755 - Detaches posts_where action after posts are pulled from the database 2016-10-24 08:55:22 -04:00
092f69538a Merge pull request #667 from mailpoet/sending_to_trashed_subscribers_fix
Prevents newsletters from being sent to trashed subscribers
2016-10-24 15:26:55 +03:00
7a75367d75 Merge pull request #666 from mailpoet/export_filename_update
Increases export filename length and randomness
2016-10-24 13:36:36 +03:00
0b2701ade2 Merge pull request #656 from mailpoet/security_issue_636
API Token
2016-10-24 13:26:44 +03:00
1ac288d286 - Prevents newsletters from being sent to trashed subscribers
- Updates unit tests
- Addresses #629
2016-10-21 14:36:44 -04:00
516bc73092 - Increases export filename length and randomness 2016-10-21 11:42:13 -04:00
4088abef68 removed useless 'use' in unit test 2016-10-21 13:42:19 +02:00
f6cefc3f5c wrong email address in unit test 2016-10-21 13:38:23 +02:00
202e4b90e1 added unit test for API::checkPermissions 2016-10-21 13:36:41 +02:00
ee89bf0722 refactored API class 2016-10-21 13:36:41 +02:00
876d21300a fixed duplicated lines due to faulty rebase 2016-10-21 13:36:41 +02:00
0ca5b7a79f API Security
- added APIAccess class to define access levels of API Endpoints (permissions)
- use "mailpoet_token" for all nonce (just as before)
- merged setupPublic/setupAdmin methods in API in order to avoid duplication
- check permission if access level is not all
- fixed ABSPATH check in some classes
2016-10-21 13:36:41 +02:00
5d0ee43921 removed checkToken for admin ajax 2016-10-21 13:36:41 +02:00
cc523a3c0b ability to specify action for generateToken() method 2016-10-21 13:36:41 +02:00
2787998d32 Merge pull request #664 from mailpoet/editor_fixes
Editor fixes
2016-10-20 17:29:55 +02:00
38f6c95059 Update newsletter saving to reflect code review comments
- Switch to using full segment objects when saving newsletters
- Fix stale comment in newsletter editor's Newsletter model
- Fix typo in newsletter editor tests
2016-10-20 17:52:05 +03:00
cc03b631ff Allow newsletters.save endpoint to accept segments as list of objects 2016-10-20 16:08:41 +03:00
a3c77fb685 Fix PHP to JS date format converter to handle escaped symbols 2016-10-20 15:19:04 +03:00
3817e28960 Change newsletter not found error to a static one in editor 2016-10-20 13:38:07 +03:00
c3a78b1ea3 Fix newsletter editor to only save properties it changes 2016-10-20 13:37:32 +03:00
42877236c8 Merge pull request #663 from mailpoet/wp_repo_files
Preparation for plugin repo
2016-10-19 09:08:54 -04:00
6e87f3539c Update license.txt, readme.txt and link to plugin's repo page 2016-10-19 13:46:14 +03:00
7704ea4b68 Bump up release version to 0.0.49 2016-10-19 13:23:00 +03:00
70 changed files with 722 additions and 273 deletions

View File

@ -71,30 +71,43 @@ define('date',
convertFormat: function(format) {
var format_mappings = {
date: {
D: 'ddd',
l: 'dddd',
d: 'DD',
D: 'ddd',
j: 'D',
z: 'DDDD',
l: 'dddd',
N: 'E',
S: '',
M: 'MMM',
S: 'o',
w: 'e',
z: 'DDD',
W: 'W',
F: 'MMMM',
m: 'MM',
n: '',
t: '',
y: 'YY',
M: 'MMM',
n: 'M',
t: '', // no equivalent
L: '', // no equivalent
o: 'YYYY',
Y: 'YYYY',
H: 'HH',
h: 'hh',
g: 'h',
y: 'YY',
a: 'a',
A: 'A',
B: '', // no equivalent
g: 'h',
G: 'H',
h: 'hh',
H: 'HH',
i: 'mm',
s: 'ss',
T: 'z',
O: 'ZZ',
w: 'd',
W: 'WW'
u: 'SSS',
e: 'zz', // deprecated since version 1.6.0 of moment.js
I: '', // no equivalent
O: '', // no equivalent
P: '', // no equivalent
T: '', // no equivalent
Z: '', // no equivalent
c: '', // no equivalent
r: '', // no equivalent
U: 'X'
},
strftime: {
a: 'ddd',
@ -127,20 +140,29 @@ define('date',
var replacements = format_mappings['date'];
var outputFormat = '';
var convertedFormat = [];
var escapeToken = false;
Object.keys(replacements).forEach(function(key) {
if (format.indexOf(key) !== -1) {
format = format.replace(key, '%'+key);
for (var index in format) {
var token = format[index];
if (escapeToken === true) {
convertedFormat.push('['+token+']');
escapeToken = false;
} else {
if (token === '\\') {
// Slash escapes the next symbol to be treated as literal
escapeToken = true;
continue;
} else if (replacements[token] !== undefined) {
convertedFormat.push(replacements[token]);
} else {
convertedFormat.push('['+token+']');
}
}
});
outputFormat = format;
Object.keys(replacements).forEach(function(key) {
if (outputFormat.indexOf('%'+key) !== -1) {
outputFormat = outputFormat.replace('%'+key, replacements[key]);
}
});
return outputFormat;
}
return convertedFormat.join('');
}
};
});

View File

@ -122,9 +122,10 @@ function(
} else {
value = e.target.value;
}
var transformedValue = this.transformChangedValue(value);
this.props.onValueChange({
target: {
value: value,
value: transformedValue,
name: this.props.field.name
}
});
@ -148,6 +149,16 @@ function(
}
return item.id;
},
// When it's impossible to represent the desired value in DOM,
// this function may be used to transform the placeholder value into
// desired value.
transformChangedValue: function(value) {
if(typeof this.props.field['transformChangedValue'] === 'function') {
return this.props.field.transformChangedValue.call(this, value);
} else {
return value;
}
},
render: function() {
const options = this.state.items.map((item, index) => {
let label = this.getLabel(item);

View File

@ -11,15 +11,16 @@ define([
// Does not hold newsletter content nor newsletter styles, those are
// handled by other components.
Module.NewsletterModel = SuperModel.extend({
stale: ['body', 'created_at', 'deleted_at', 'updated_at'],
whitelisted: ['id', 'subject', 'preheader'],
initialize: function(options) {
this.on('change', function() {
App.getChannel().trigger('autoSave');
});
},
toJSON: function() {
// Remove stale attributes from resulting JSON object
return _.omit(SuperModel.prototype.toJSON.call(this), this.stale);
// Use only whitelisted properties to ensure properties editor
// doesn't control don't change.
return _.pick(SuperModel.prototype.toJSON.call(this), this.whitelisted);
},
});

View File

@ -62,7 +62,7 @@ const _monthDayValues = _.object(
} else {
label = MailPoet.I18n.t('nth').replace("%$1d", day + 1);
}
return [day, label];
return [day + 1, label];
}
)
);

View File

@ -1,11 +1,13 @@
define(
[
'mailpoet',
'newsletters/types/notification/scheduling.jsx'
'newsletters/types/notification/scheduling.jsx',
'underscore'
],
function(
MailPoet,
Scheduling
Scheduling,
_
) {
var settings = window.mailpoet_settings || {};
@ -42,6 +44,14 @@ define(
getLabel: function(segment) {
return segment.name + ' (' + parseInt(segment.subscribers).toLocaleString() + ')';
},
transformChangedValue: function(segment_ids) {
var all_segments = this.state.items;
return _.map(segment_ids, function(id) {
return _.find(all_segments, function(segment) {
return segment.id === id;
});
});
},
validation: {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('noSegmentsSelectedError')

View File

@ -348,6 +348,14 @@ define(
getLabel: function(segment) {
return segment.name + ' (' + parseInt(segment.subscribers).toLocaleString() + ')';
},
transformChangedValue: function(segment_ids) {
var all_segments = this.state.items;
return _.map(segment_ids, function(id) {
return _.find(all_segments, function(segment) {
return segment.id === id;
});
});
},
validation: {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('noSegmentsSelectedError')

View File

@ -1,6 +1,5 @@
import _ from 'underscore'
import React from 'react'
import MailPoet from 'mailpoet'
import Select from 'form/fields/select.jsx'
import {
intervalValues,

View File

@ -40,7 +40,7 @@ function(
// ajax request
MailPoet.Ajax.post({
url: MailPoetForm.ajax_url,
token: MailPoetForm.token,
token: data.token,
endpoint: 'subscribers',
action: 'subscribe',
data: data

View File

@ -95,15 +95,18 @@ define(
});
};
// set confirmed subscribers export option to false
exportData.exportConfirmedOption = false;
renderSegmentsAndFields(subscriberFieldsContainerElement, subscriberFieldsSelect2);
renderSegmentsAndFields(segmentsContainerElement, segments);
subscriberFieldsContainerElement.select2('val', [
'status',
subscriberFieldsContainerElement.val([
'email',
'first_name',
'last_name'
]);
'last_name',
'status'
]).trigger("change");
exportConfirmedOptionElement.change(function () {
var selectedSegments = segmentsContainerElement.val();

View File

@ -871,7 +871,7 @@ define(
'<span class="mailpoet_data_match mailpoet_import_error" title="'
+ MailPoet.I18n.t('noDateFieldMatch') + '">'
+ MailPoet.I18n.t('emptyFirstRowDate')
+ '</span>';
+ '</span> ';
preventNextStep = true;
}
else {
@ -901,7 +901,7 @@ define(
+ '<span class="mailpoet_data_match" title="'
+ MailPoet.I18n.t('verifyDateMatch') + '">'
+ MailPoet.Date.format(date)
+ '</span>'
+ '</span> '
);
}
else {
@ -910,7 +910,7 @@ define(
+ '<span class="mailpoet_data_match mailpoet_import_error" title="'
+ MailPoet.I18n.t('noDateFieldMatch') + '">'
+ (new Handlebars.SafeString(MailPoet.I18n.t('dateMatchError')))
+ '</span>'
+ '</span> '
);
preventNextStep = true;
};

View File

@ -44,7 +44,7 @@ rm -rf $plugin_name/vendor/swiftmailer/swiftmailer/tests
rm -rf $plugin_name/vendor/cerdic/css-tidy/testing
# Copy release files.
cp LICENSE $plugin_name
cp license.txt $plugin_name
cp index.php $plugin_name
cp $plugin_name.php $plugin_name
cp readme.txt $plugin_name

View File

@ -5,31 +5,40 @@ use \MailPoet\Util\Security;
if(!defined('ABSPATH')) exit;
class API {
private $_endpoint;
private $_method;
private $_token;
private $_endpoint_class;
private $_data = array();
function init() {
// security token
// Admin Security token
add_action(
'admin_head',
array($this, 'setToken')
);
// Admin API (Ajax only)
// ajax (logged in users)
add_action(
'wp_ajax_mailpoet',
array($this, 'setupAdmin')
array($this, 'setupAjax')
);
// Public API (Ajax)
// ajax (logged out users)
add_action(
'wp_ajax_nopriv_mailpoet',
array($this, 'setupPublic')
array($this, 'setupAjax')
);
}
function setupAdmin() {
function setupAjax() {
$this->getRequestData();
if($this->checkToken() === false) {
$error_response = new ErrorResponse(
array(
Error::UNAUTHORIZED => __('You need to specify a valid API token.', 'mailpoet')
Error::UNAUTHORIZED => __('Invalid request.', 'mailpoet')
),
array(),
Response::STATUS_UNAUTHORIZED
@ -37,55 +46,84 @@ class API {
$error_response->send();
}
if($this->checkPermissions() === false) {
$error_response = new ErrorResponse(
array(
Error::FORBIDDEN => __('You do not have the required permissions.', 'mailpoet')
),
array(),
Response::STATUS_FORBIDDEN
);
$error_response->send();
}
$this->processRoute();
}
function setupPublic() {
if($this->checkToken() === false) {
function getRequestData() {
$this->_endpoint = isset($_POST['endpoint'])
? trim($_POST['endpoint'])
: null;
$this->_method = isset($_POST['method'])
? trim($_POST['method'])
: null;
$this->_token = isset($_POST['token'])
? trim($_POST['token'])
: null;
if(!$this->_endpoint || !$this->_method) {
// throw exception bad request
$error_response = new ErrorResponse(
array(
Error::UNAUTHORIZED => __('You need to specify a valid API token.', 'mailpoet')
Error::BAD_REQUEST => __('Invalid request.', 'mailpoet')
),
array(),
Response::STATUS_UNAUTHORIZED
Response::STATUS_BAD_REQUEST
);
$error_response->send();
}
} else {
$this->_endpoint_class = (
__NAMESPACE__."\\Endpoints\\".ucfirst($this->_endpoint)
);
$this->processRoute();
$this->_data = isset($_POST['data'])
? stripslashes_deep($_POST['data'])
: array();
// remove reserved keywords from data
if(is_array($this->_data) && !empty($this->_data)) {
// filter out reserved keywords from data
$reserved_keywords = array(
'token',
'endpoint',
'method',
'mailpoet_redirect'
);
$this->_data = array_diff_key(
$this->_data,
array_flip($reserved_keywords)
);
}
}
}
function processRoute() {
$class = ucfirst($_POST['endpoint']);
$endpoint = __NAMESPACE__ . "\\Endpoints\\" . $class;
$method = $_POST['method'];
$data = isset($_POST['data']) ? stripslashes_deep($_POST['data']) : array();
if(is_array($data) && !empty($data)) {
// filter out reserved keywords from data
$reserved_keywords = array(
'token',
'endpoint',
'method',
'mailpoet_redirect'
);
$data = array_diff_key($data, array_flip($reserved_keywords));
}
try {
$endpoint = new $endpoint();
$response = $endpoint->$method($data);
$endpoint = new $this->_endpoint_class();
// check the accessibility of the requested endpoint's action
// by default, an endpoint's action is considered "private"
$permissions = $endpoint->permissions;
if(
array_key_exists($this->_method, $permissions) === false
||
$permissions[$this->_method] !== Access::ALL
) {
if($this->checkPermissions() === false) {
$error_response = new ErrorResponse(
array(
Error::FORBIDDEN => __(
'You do not have the required permissions.',
'mailpoet'
)
),
array(),
Response::STATUS_FORBIDDEN
);
$error_response->send();
}
}
$response = $endpoint->{$this->_method}($this->_data);
$response->send();
} catch(\Exception $e) {
$error_response = new ErrorResponse(
@ -95,22 +133,20 @@ class API {
}
}
function setToken() {
$global = '<script type="text/javascript">';
$global .= 'var mailpoet_token = "'.Security::generateToken().'";';
$global .= '</script>';
echo $global;
}
function checkPermissions() {
return current_user_can('manage_options');
}
function checkToken() {
return (
isset($_POST['token'])
&&
wp_verify_nonce($_POST['token'], 'mailpoet_token')
);
return wp_verify_nonce($this->_token, 'mailpoet_token');
}
function setToken() {
$global = '<script type="text/javascript">';
$global .= 'var mailpoet_token = "';
$global .= Security::generateToken();
$global .= '";';
$global .= '</script>';
echo $global;
}
}

12
lib/API/Access.php Normal file
View File

@ -0,0 +1,12 @@
<?php
namespace MailPoet\API;
if(!defined('ABSPATH')) exit;
final class Access {
const ALL = 'all';
private function __construct() {
}
}

View File

@ -5,6 +5,8 @@ if(!defined('ABSPATH')) exit;
abstract class Endpoint {
public $permissions = array();
function successResponse(
$data = array(), $meta = array(), $status = Response::STATUS_OK
) {

View File

@ -40,9 +40,9 @@ class Newsletters extends APIEndpoint {
}
function save($data = array()) {
$segment_ids = array();
$segments = array();
if(isset($data['segments'])) {
$segment_ids = $data['segments'];
$segments = $data['segments'];
unset($data['segments']);
}
@ -58,13 +58,14 @@ class Newsletters extends APIEndpoint {
if(!empty($errors)) {
return $this->badRequest($errors);
} else {
if(!empty($segment_ids)) {
if(!empty($segments)) {
NewsletterSegment::where('newsletter_id', $newsletter->id)
->deleteMany();
foreach($segment_ids as $segment_id) {
foreach($segments as $segment) {
if(!is_array($segment)) continue;
$relation = NewsletterSegment::create();
$relation->segment_id = $segment_id;
$relation->segment_id = (int)$segment['id'];
$relation->newsletter_id = $newsletter->id;
$relation->save();
}
@ -90,9 +91,14 @@ class Newsletters extends APIEndpoint {
}
}
return $this->successResponse(
Newsletter::findOne($newsletter->id)->asArray()
);
$newsletter = Newsletter::filter('filterWithOptions')->findOne($newsletter->id);
// if this is a post notification, process options and update its schedule
if($newsletter->type === Newsletter::TYPE_NOTIFICATION) {
Scheduler::processPostNotificationSchedule($newsletter);
}
return $this->successResponse($newsletter->asArray());
}
}

View File

@ -2,6 +2,7 @@
namespace MailPoet\API\Endpoints;
use \MailPoet\API\Endpoint as APIEndpoint;
use \MailPoet\API\Error as APIError;
use \MailPoet\API\Access as APIAccess;
use MailPoet\Listing;
use MailPoet\Models\Subscriber;
@ -15,6 +16,11 @@ use MailPoet\Models\StatisticsForms;
if(!defined('ABSPATH')) exit;
class Subscribers extends APIEndpoint {
public $permissions = array(
'subscribe' => APIAccess::ALL
);
function get($data = array()) {
$id = (isset($data['id']) ? (int)$data['id'] : false);
$subscriber = Subscriber::findOne($id);

View File

@ -27,7 +27,6 @@ class Env {
static $db_password;
static $db_charset;
static $db_timezone_offset;
static $subscribers_limit = 2000;
static function init($file, $version) {
global $wpdb;
@ -39,11 +38,11 @@ class Env {
self::$assets_path = self::$path . '/assets';
self::$assets_url = plugins_url('/assets', $file);
$wp_upload_dir = wp_upload_dir();
self::$temp_path = $wp_upload_dir['basedir'].'/'.self::$plugin_name;
self::$temp_path = $wp_upload_dir['basedir'] . '/' . self::$plugin_name;
if(!is_dir(self::$temp_path)) {
mkdir(self::$temp_path);
}
self::$temp_url = $wp_upload_dir['baseurl'].'/'.self::$plugin_name;
self::$temp_url = $wp_upload_dir['baseurl'] . '/' . self::$plugin_name;
self::$languages_path = self::$path . '/lang';
self::$lib_path = self::$path . '/lib';
self::$plugin_prefix = 'mailpoet_';

View File

@ -4,6 +4,7 @@ namespace MailPoet\Config;
use MailPoet\Cron\CronTrigger;
use MailPoet\Router;
use MailPoet\API;
use MailPoet\Util\License\License as License;
use MailPoet\WP\Notice as WPNotice;
if(!defined('ABSPATH')) exit;
@ -116,6 +117,7 @@ class Initializer {
function onInit() {
if(!$this->plugin_initialized) {
define('MAILPOET_INITIALIZED', false);
return;
}
@ -126,6 +128,8 @@ class Initializer {
} catch(\Exception $e) {
$this->handleFailedInitialization($e);
}
define('MAILPOET_INITIALIZED', true);
}
function setupWidget() {
@ -206,4 +210,4 @@ class Initializer {
function handleFailedInitialization($message) {
return WPNotice::displayError($message);
}
}
}

View File

@ -14,6 +14,7 @@ use MailPoet\Settings\Hosts;
use MailPoet\Settings\Pages;
use MailPoet\Subscribers\ImportExport\ImportExportFactory;
use MailPoet\Listing;
use MailPoet\Util\License\Features\Subscribers as SubscribersFeature;
use MailPoet\WP\DateTime;
if(!defined('ABSPATH')) exit;
@ -22,6 +23,8 @@ class Menu {
function __construct($renderer, $assets_url) {
$this->renderer = $renderer;
$this->assets_url = $assets_url;
$subscribers_feature = new SubscribersFeature();
$this->subscribers_over_limit = $subscribers_feature->check();
}
function init() {
@ -34,16 +37,6 @@ class Menu {
);
}
function checkSubscribersLimit() {
$subscribers_count = Subscriber::getTotalSubscribers();
if($subscribers_count > Env::$subscribers_limit) {
echo $this->renderer->render('limit.html', array(
'limit' => Env::$subscribers_limit
));
exit;
}
}
function setup() {
$main_page_slug = 'mailpoet-newsletters';
@ -59,8 +52,8 @@ class Menu {
$newsletters_page = add_submenu_page(
$main_page_slug,
$this->setPageTitle(__('Newsletters', 'mailpoet')),
__('Newsletters', 'mailpoet'),
$this->setPageTitle(__('Emails', 'mailpoet')),
__('Emails', 'mailpoet'),
'manage_options',
$main_page_slug,
array($this, 'newsletters')
@ -252,7 +245,7 @@ class Menu {
}
function settings() {
$this->checkSubscribersLimit();
if ($this->subscribers_over_limit) return $this->displaySubscriberLimitExceededTemplate();
$settings = Setting::getAll();
$flags = $this->_getFlags();
@ -326,7 +319,7 @@ class Menu {
}
function segments() {
$this->checkSubscribersLimit();
if ($this->subscribers_over_limit) return $this->displaySubscriberLimitExceededTemplate();
$data = array();
$data['items_per_page'] = $this->getLimitPerPage('segments');
@ -334,7 +327,7 @@ class Menu {
}
function forms() {
$this->checkSubscribersLimit();
if ($this->subscribers_over_limit) return $this->displaySubscriberLimitExceededTemplate();
$data = array();
@ -345,7 +338,7 @@ class Menu {
}
function newsletters() {
$this->checkSubscribersLimit();
if ($this->subscribers_over_limit) return $this->displaySubscriberLimitExceededTemplate();
global $wp_roles;
@ -447,4 +440,11 @@ class Menu {
? (int)$listing_per_page
: Listing\Handler::DEFAULT_LIMIT_PER_PAGE;
}
}
function displaySubscriberLimitExceededTemplate() {
echo $this->renderer->render('limit.html', array(
'limit' => SubscribersFeature::SUBSCRIBERS_LIMIT
));
exit;
}
}

View File

@ -69,8 +69,7 @@ class Widget {
'form' => $form_html,
'mailpoet_form' => array(
'ajax_url' => admin_url('admin-ajax.php', 'absolute'),
'is_rtl' => $is_rtl,
'token' => Security::generateToken()
'is_rtl' => $is_rtl
)
);
@ -103,8 +102,7 @@ class Widget {
wp_localize_script('mailpoet_public', 'MailPoetForm', array(
'ajax_url' => admin_url('admin-ajax.php'),
'is_rtl' => (function_exists('is_rtl') ? (bool)is_rtl() : false),
'token' => Security::generateToken()
'is_rtl' => (function_exists('is_rtl') ? (bool)is_rtl() : false)
));
}

View File

@ -47,6 +47,7 @@ class SendingQueue {
// abort if execution limit is reached
CronHelper::enforceExecutionLimit($this->timer);
$found_subscribers = SubscriberModel::whereIn('id', $subscribers_to_process_ids)
->whereNull('deleted_at')
->findMany();
$found_subscribers_ids = array_map(function($subscriber) {
return $subscriber->id;

View File

@ -8,7 +8,6 @@ use MailPoet\Models\Newsletter as NewsletterModel;
use MailPoet\Models\Setting;
use MailPoet\Newsletter\Links\Links as NewsletterLinks;
use MailPoet\Newsletter\Renderer\PostProcess\OpenTracking;
use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
@ -45,7 +44,9 @@ class Newsletter {
}
// check if this is a post notification and if it contains posts
$newsletter_contains_posts = strpos($rendered_newsletter['html'], 'data-post-id');
if($newsletter->type === 'notification' && !$newsletter_contains_posts) {
if($newsletter->type === NewsletterModel::TYPE_NOTIFICATION_HISTORY &&
!$newsletter_contains_posts
) {
return false;
}
// extract and save newsletter posts
@ -91,8 +92,10 @@ class Newsletter {
}
function markNewsletterAsSent($newsletter) {
// if it's a standard newsletter, update its status
if($newsletter->type === NewsletterModel::TYPE_STANDARD) {
// if it's a standard or notification history newsletter, update its status
if($newsletter->type === NewsletterModel::TYPE_STANDARD ||
$newsletter->type === NewsletterModel::TYPE_NOTIFICATION_HISTORY
) {
$newsletter->setStatus(NewsletterModel::STATUS_SENT);
}
}

View File

@ -17,7 +17,7 @@ class AmazonSES {
public $reply_to;
public $date;
public $date_without_time;
const SES_REGIONS = array(
private $available_regions = array(
'US East (N. Virginia)' => 'us-east-1',
'US West (Oregon)' => 'us-west-2',
'EU (Ireland)' => 'eu-west-1'
@ -26,7 +26,7 @@ class AmazonSES {
function __construct($region, $access_key, $secret_key, $sender, $reply_to) {
$this->aws_access_key = $access_key;
$this->aws_secret_key = $secret_key;
$this->aws_region = (in_array($region, self::SES_REGIONS)) ? $region : false;
$this->aws_region = (in_array($region, $this->available_regions)) ? $region : false;
if(!$this->aws_region) {
throw new \Exception(__('Unsupported Amazon SES region.', 'mailpoet'));
}
@ -42,6 +42,7 @@ class AmazonSES {
$this->date_without_time = gmdate('Ymd');
}
function send($newsletter, $subscriber) {
$result = wp_remote_post(
$this->url,

View File

@ -383,6 +383,7 @@ class Subscriber extends Model {
'subscribers.id = relation.subscriber_id',
'subscribers'
)
->whereNull('subscribers.deleted_at')
->where('subscribers.status', 'subscribed');
return $subscribers;
}

View File

@ -15,12 +15,6 @@ class AutomatedLatestContent {
function __construct($newsletter_id = false, $newer_than_timestamp = false) {
$this->newsletter_id = $newsletter_id;
$this->newer_than_timestamp = $newer_than_timestamp;
$this->_attachSentPostsFilter();
}
function __destruct() {
$this->_detachSentPostsFilter();
}
function filterOutSentPosts($where) {
@ -72,7 +66,10 @@ class AutomatedLatestContent {
);
}
return get_posts($parameters);
$this->_attachSentPostsFilter();
$posts = get_posts($parameters);
$this->_detachSentPostsFilter();
return $posts;
}
function transformPosts($args, $posts) {
@ -129,4 +126,4 @@ class AutomatedLatestContent {
remove_action('posts_where', array($this, 'filterOutSentPosts'));
}
}
}
}

View File

@ -1,8 +1,6 @@
<?php
namespace MailPoet\Settings;
use MailPoet\Mailer\Methods\AmazonSES;
class Hosts {
private static $_smtp = array(
'AmazonSES' => array(
@ -14,7 +12,11 @@ class Hosts {
'access_key',
'secret_key'
),
'regions' => AmazonSES::SES_REGIONS
'regions' => array(
'US East (N. Virginia)' => 'us-east-1',
'US West (Oregon)' => 'us-west-2',
'EU (Ireland)' => 'eu-west-1'
)
),
'SendGrid' => array(
'name' => 'SendGrid',

View File

@ -8,6 +8,7 @@ use MailPoet\Models\Subscriber;
use MailPoet\Models\SubscriberSegment;
use MailPoet\Subscribers\ImportExport\ImportExportFactory;
use MailPoet\Util\Helpers;
use MailPoet\Util\Security;
use MailPoet\Util\XLSXWriter;
class Export {
@ -240,7 +241,7 @@ class Export {
function getExportFile($format) {
return sprintf(
$this->export_path . '/MailPoet_export_%s.%s',
substr(md5(time()), 0, 4),
Security::generateRandomString(15),
$format
);
}

View File

@ -0,0 +1,19 @@
<?php
namespace MailPoet\Util\License\Features;
use MailPoet\Models\Subscriber as SubscriberModel;
use MailPoet\Util\License\License;
class Subscribers {
public $license;
const SUBSCRIBERS_LIMIT = 2000;
function __construct($license = false) {
$this->license = ($license) ? $license : License::getLicense();
}
function check($subscribers_limit = self::SUBSCRIBERS_LIMIT) {
if($this->license) return false;
return SubscriberModel::getTotalSubscribers() > $subscribers_limit;
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace MailPoet\Util\License;
class License {
static function getLicense($license = false) {
if(!$license) {
$license = defined('MAILPOET_PREMIUM_LICENSE') ?
MAILPOET_PREMIUM_LICENSE :
false;
}
return $license;
}
}

View File

@ -5,8 +5,8 @@ if(!defined('ABSPATH')) exit;
require_once(ABSPATH . 'wp-includes/pluggable.php');
class Security {
static function generateToken() {
return wp_create_nonce('mailpoet_token');
static function generateToken($action = 'mailpoet_token') {
return wp_create_nonce($action);
}
static function generateRandomString($length = 5) {

View File

@ -1,3 +1,42 @@
WordPress - Web publishing software
Copyright 2011-2016 by the contributors
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
This program incorporates work covered by the following copyright and
permission notices:
b2 is (c) 2001, 2002 Michel Valdrighi - m@tidakada.com -
http://tidakada.com
Wherever third party code has been used, credit has been given in the code's
comments.
b2 is released under the GPL
and
WordPress - Web publishing software
Copyright 2003-2010 by the contributors
WordPress is released under the GPL
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
@ -336,4 +375,11 @@ This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
Public License instead of this License.
WRITTEN OFFER
The source code for any program binaries or compressed scripts that are
included with WordPress can be freely obtained at the following URL:
https://wordpress.org/download/source/

View File

@ -4,7 +4,7 @@ if(!defined('ABSPATH')) exit;
use \MailPoet\Config\Initializer;
/*
* Plugin Name: MailPoet
* Version: 0.0.48
* Version: 3.0.0-beta.1
* 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.48');
define('MAILPOET_VERSION', '3.0.0-beta.1');
$initializer = new Initializer(array(
'file' => __FILE__,

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -1,41 +1,89 @@
=== MailPoet ===
Contributors: mailpoet
Donate link: http://mailpoet.com
Tags: wordpress, plugin
Requires at least: 4.0
Tested up to: 4.0
Stable tag: 1.0
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
MailPoet newsletters.
== Description ==
Long description.
== Installation ==
Installation instructions.
== Screenshots ==
Screenshots.
== Frequently Asked Questions ==
= Question? =
Answer.
== Changelog ==
= 1.0 =
* 2020-01-01
* Initial release
== Upgrade Notice ==
= 1.0 =
* 2020-01-01
* Initial release
=== MailPoet 3 - Beta Version ===
Contributors: mailpoet
Tags: newsletter, email, welcome email, post notification, autoresponder, mailchimp, signup, smtp
Requires at least: 4.6
Tested up to: 4.6.1
Stable tag: 3.0.0-beta.1
Create and send beautiful emails and newsletters from WordPress.
== Description ==
Try the new MailPoet! This is a beta version of our completely new email newsletter plugin.(https://wordpress.org/plugins/wysija-newsletters/).
= What's new? =
* New email designer
* Responsive templates
* Send with MailPoet's sending service
* Fast user interface
* Easier initial configuration
[Try the demo.](http://demo3.mailpoet.com/launch/)
= Check out this 2 minute video. =
[vimeo https://vimeo.com/183339372]
= Use at your own risk! =
Use [the current stable MailPoet](https://wordpress.org/plugins/wysija-newsletters/) instead of this version if you are not a power user.
* This beta version is for testing purposes only!
* Not RTL compatible
* We expect bug reports from you!
* Multisite not supported
* No migration script from MailPoet 2.X to this version
* Weekly releases
= Premium version =
Not available yet. Limited stats in free version.
= Translations in your language =
We accept translations in the repository.
== Installation ==
There are 3 ways to install this plugin:
= 1. The super easy way =
1. In your Admin, go to menu Plugins > Add
1. Search for `mailpoet`
1. Click to install
1. Activate the plugin
1. A new menu `mailpoet` will appear in your Admin
= 2. The easy way =
1. Download the plugin (.zip file) on the right column of this page
1. In your Admin, go to menu Plugins > Add
1. Select the tab "Upload"
1. Upload the .zip file you just downloaded
1. Activate the plugin
1. A new menu `MailPoet` will appear in your Admin
= 3. The old and reliable way (FTP) =
1. Upload `mailpoet` folder to the `/wp-content/plugins/` directory
1. Activate the plugin through the 'Plugins' menu in WordPress
1. A new menu `MailPoet` will appear in your Admin
== Frequently Asked Questions ==
= Need help? =
Our [support site](https://docs.mailpoet.com/) has plenty of articles. You can write to us on the forums too.
== Screenshots ==
1. Sample newsletters.
2. The drag & drop editor.
3. Subscriber management.
4. Sending method configuration in Settings.
5. Importing subscribers with a CSV or from MailChimp.
== Changelog ==
= 3.0.0-beta.1 - 2016-10 =
* Initial public beta release.

View File

@ -19,7 +19,7 @@ define([
data2: 'data2Value',
},
},
someField: 'someValue'
subject: 'my test subject'
});
});
@ -28,13 +28,32 @@ define([
global.stubChannel(EditorApplication, {
trigger: mock,
});
model.set('someField', 'anotherValue');
model.set('subject', 'another test subject');
mock.verify();
});
it('does not include styles and content attributes in its JSON', function() {
it('does not include styles and content properties in its JSON', function() {
var json = model.toJSON();
expect(json).to.deep.equal({someField: 'someValue'});
expect(json).to.deep.equal({subject: 'my test subject'});
});
describe('toJSON()', function() {
it('will only contain properties modifiable by the editor', function() {
var model = new (ContentComponent.NewsletterModel)({
id: 19,
subject: 'some subject',
preheader: 'some preheader',
segments: [1, 2, 3],
modified_at: '2000-01-01 12:01:02',
someField: 'someValue'
});
var json = model.toJSON();
expect(json.id).to.equal(19);
expect(json.subject).to.equal('some subject');
expect(json.preheader).to.equal('some preheader');
expect(json).to.not.include.keys('segments', 'modified_at', 'someField');
})
});
});

View File

@ -0,0 +1,38 @@
<?php
use \MailPoet\API\API;
// required to be able to use wp_delete_user()
require_once(ABSPATH.'wp-admin/includes/user.php');
class APITest extends MailPoetTest {
function _before() {
// create WP user
$this->wp_user_id = null;
$wp_user_id = wp_create_user('WP User', 'pass', 'wp_user@mailpoet.com');
if(is_wp_error($wp_user_id)) {
// user already exists
$this->wp_user_id = email_exists('wp_user@mailpoet.com');
} else {
$this->wp_user_id = $wp_user_id;
}
$this->api = new API();
}
function testItChecksPermissions() {
// logged out user
expect($this->api->checkPermissions())->false();
// give administrator role to wp user
$wp_user = get_user_by('id', $this->wp_user_id);
$wp_user->add_role('administrator');
wp_set_current_user($wp_user->ID, $wp_user->user_login);
// administrator should have permission
expect($this->api->checkPermissions())->true();
}
function _after() {
wp_delete_user($this->wp_user_id);
}
}

View File

@ -1,11 +1,11 @@
<?php
use \MailPoet\API\Response as APIResponse;
use \MailPoet\API\Error as APIError;
use \MailPoet\API\Endpoints\Newsletters;
use \MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterOptionField;
use \MailPoet\Models\NewsletterSegment;
use \MailPoet\Models\NewsletterTemplate;
use \MailPoet\Models\Segment;
use MailPoet\Newsletter\Scheduler\Scheduler;
class NewslettersTest extends MailPoetTest {
function _before() {
@ -44,17 +44,26 @@ class NewslettersTest extends MailPoetTest {
}
function testItCanSaveANewNewsletter() {
$newsletter_option_field = NewsletterOptionField::create();
$newsletter_option_field->name = 'some_option';
$newsletter_option_field->newsletter_type = Newsletter::TYPE_STANDARD;
$newsletter_option_field->save();
$valid_data = array(
'subject' => 'My First Newsletter',
'type' => Newsletter::TYPE_STANDARD
'type' => Newsletter::TYPE_STANDARD,
'options' => array(
$newsletter_option_field->name => 'some_option_value',
)
);
$router = new Newsletters();
$response = $router->save($valid_data);
$saved_newsletter = Newsletter::filter('filterWithOptions')->findOne($response->data['id']);
expect($response->status)->equals(APIResponse::STATUS_OK);
expect($response->data)->equals(
Newsletter::findOne($response->data['id'])->asArray()
);
expect($response->data)->equals($saved_newsletter->asArray());
// newsletter option should be saved
expect($saved_newsletter->some_option)->equals('some_option_value');
$invalid_data = array(
'subject' => 'Missing newsletter type'
@ -73,13 +82,75 @@ class NewslettersTest extends MailPoetTest {
);
$response = $router->save($newsletter_data);
$updated_newsletter = Newsletter::findOne($this->newsletter->id);
expect($response->status)->equals(APIResponse::STATUS_OK);
expect($response->data)->equals(
Newsletter::findOne($this->newsletter->id)->asArray()
expect($response->data)->equals($updated_newsletter->asArray());
expect($updated_newsletter->subject)->equals('My Updated Newsletter');
}
function testItCanUpdatePostNotificationScheduleUponSave() {
$newsletter_options = array(
'intervalType',
'timeOfDay',
'weekDay',
'monthDay',
'nthWeekDay',
'schedule'
);
foreach($newsletter_options as $option) {
$newsletter_option_field = NewsletterOptionField::create();
$newsletter_option_field->name = $option;
$newsletter_option_field->newsletter_type = Newsletter::TYPE_NOTIFICATION;
$newsletter_option_field->save();
}
$router = new Newsletters();
$newsletter_data = array(
'id' => $this->newsletter->id,
'type' => Newsletter::TYPE_NOTIFICATION,
'subject' => 'Newsletter',
'options' => array(
'intervalType' => Scheduler::INTERVAL_WEEKLY,
'timeOfDay' => '50400',
'weekDay' => '1',
'monthDay' => '0',
'nthWeekDay' => '1',
'schedule' => '0 14 * * 1'
)
);
$response = $router->save($newsletter_data);
$saved_newsletter = Newsletter::filter('filterWithOptions')
->findOne($response->data['id']);
expect($response->status)->equals(APIResponse::STATUS_OK);
expect($response->data)->equals($saved_newsletter->asArray());
// schedule should be recalculated when options change
$newsletter_data['options']['intervalType'] = Scheduler::INTERVAL_IMMEDIATELY;
$response = $router->save($newsletter_data);
$saved_newsletter = Newsletter::filter('filterWithOptions')->findOne($response->data['id']);
expect($response->status)->equals(APIResponse::STATUS_OK);
expect($saved_newsletter->schedule)->equals('* * * * *');
}
function testItCanModifySegmentsOfExistingNewsletter() {
$segment_1 = Segment::createOrUpdate(array('name' => 'Segment 1'));
$fake_segment_id = 1;
$router = new Newsletters();
$newsletter_data = array(
'id' => $this->newsletter->id,
'subject' => 'My Updated Newsletter',
'segments' => array($segment_1->asArray(), $fake_segment_id)
);
$updated_newsletter = Newsletter::findOne($this->newsletter->id);
expect($updated_newsletter->subject)->equals('My Updated Newsletter');
$response = $router->save($newsletter_data);
expect($response->status)->equals(APIResponse::STATUS_OK);
$updated_newsletter =
Newsletter::findOne($this->newsletter->id)->withSegments();
expect(count($updated_newsletter->segments))->equals(1);
expect($updated_newsletter->segments[0]['name'])->equals('Segment 1');
}
function testItCanSetANewsletterStatus() {
@ -377,6 +448,7 @@ class NewslettersTest extends MailPoetTest {
function _after() {
Newsletter::deleteMany();
NewsletterSegment::deleteMany();
NewsletterOptionField::deleteMany();
Segment::deleteMany();
}
}

View File

@ -1,5 +1,6 @@
<?php
use Carbon\Carbon;
use Codeception\Util\Fixtures;
use Codeception\Util\Stub;
use MailPoet\API\Endpoints\Cron;
@ -298,6 +299,29 @@ class SendingQueueTest extends MailPoetTest {
expect($statistics)->false();
}
function testItDoesNotSendToTrashedSubscribers() {
$sending_queue_worker = $this->sending_queue_worker;
$sending_queue_worker->mailer_task = Stub::make(
new MailerTask(),
array('send' => function($newsletter, $subscriber) { return true; })
);
// newsletter is sent to existing subscriber
$sending_queue_worker->process();
$updated_queue = SendingQueue::findOne($this->queue->id);
expect((int)$updated_queue->count_total)->equals(1);
// newsletter is not sent to trashed subscriber
$this->_after();
$this->_before();
$subscriber = $this->subscriber;
$subscriber->deleted_at = Carbon::now();
$subscriber->save();
$sending_queue_worker->process();
$updated_queue = SendingQueue::findOne($this->queue->id);
expect((int)$updated_queue->count_total)->equals(0);
}
function _after() {
ORM::raw_execute('TRUNCATE ' . Subscriber::$_table);
ORM::raw_execute('TRUNCATE ' . SendingQueue::$_table);

View File

@ -76,7 +76,8 @@ class NewsletterTaskTest extends MailPoetTest {
function testReturnsFalseWhenNewsletterIsANotificationWithoutPosts() {
$newsletter = $this->newsletter;
$newsletter->type = Newsletter::TYPE_NOTIFICATION;
$newsletter->type = Newsletter::TYPE_NOTIFICATION_HISTORY;
// replace post id data tag with something else
$newsletter->body = str_replace('data-post-id', 'id', $newsletter->body);
$newsletter->save();
@ -92,17 +93,27 @@ class NewsletterTaskTest extends MailPoetTest {
expect($newsletter_post->post_id)->equals('10');
}
function testItUpdatesStatusToSentOnlyForStandardNewsletters() {
// newsletter type is 'standard'
function testItUpdatesStatusToSentOnlyForStandardAndPostNotificationNewsletters() {
$newsletter = $this->newsletter;
expect($newsletter->type)->equals(Newsletter::TYPE_STANDARD);
expect($newsletter->status)->notEquals(Newsletter::STATUS_SENT);
// newsletter type is 'standard'
$newsletter->type = Newsletter::TYPE_STANDARD;
$newsletter->status = 'not_sent';
$newsletter->save();
$this->newsletter_task->markNewsletterAsSent($newsletter);
$updated_newsletter = Newsletter::findOne($newsletter->id);
expect($updated_newsletter->status)->equals(Newsletter::STATUS_SENT);
// newsletter type is NOT 'standard'
$newsletter->type = Newsletter::TYPE_NOTIFICATION;
// newsletter type is 'notification history'
$newsletter->type = Newsletter::TYPE_NOTIFICATION_HISTORY;
$newsletter->status = 'not_sent';
$newsletter->save();
$this->newsletter_task->markNewsletterAsSent($newsletter);
$updated_newsletter = Newsletter::findOne($newsletter->id);
expect($updated_newsletter->status)->equals(Newsletter::STATUS_SENT);
// all other newsletter types
$newsletter->type = Newsletter::TYPE_WELCOME;
$newsletter->status = 'not_sent';
$newsletter->save();
$this->newsletter_task->markNewsletterAsSent($newsletter);

View File

@ -1,5 +1,6 @@
<?php
use Carbon\Carbon;
use MailPoet\Models\CustomField;
use MailPoet\Models\Segment;
use MailPoet\Models\Subscriber;
@ -369,7 +370,7 @@ class SubscriberTest extends MailPoetTest {
->equals('non_default_value');
}
function testItCanGetOnlySubscribedSubscribersInSegments() {
function testItCanGetOnlySubscribedAndNonTrashedSubscribersInSegments() {
$subscriber_1 = Subscriber::createOrUpdate(array(
'first_name' => 'Adam',
'last_name' => 'Smith',
@ -384,13 +385,20 @@ class SubscriberTest extends MailPoetTest {
'status' => Subscriber::STATUS_SUBSCRIBED
));
$subscriber_3 = Subscriber::createOrUpdate(array(
'first_name' => 'Bob',
'last_name' => 'Smith',
'email' => 'bob@smith.com',
'status' => Subscriber::STATUS_SUBSCRIBED,
'deleted_at' => Carbon::now()
));
$segment = Segment::createOrUpdate(array(
'name' => 'Only Subscribed Subscribers Segment'
));
//Subscriber::createMultiple($columns, $values);
$result = SubscriberSegment::subscribeManyToSegments(
array($subscriber_1->id, $subscriber_2->id),
array($subscriber_1->id, $subscriber_2->id, $subscriber_3->id),
array($segment->id)
);
expect($result)->true();

View File

@ -134,7 +134,7 @@ class ExportTest extends MailPoetTest {
expect(
preg_match(
'|' .
Env::$temp_path . '/MailPoet_export_[a-f0-9]{4}.' .
Env::$temp_path . '/MailPoet_export_[a-f0-9]{15}.' .
$this->export->export_format_option .
'|', $this->export->export_file)
)->equals(1);

View File

@ -0,0 +1,34 @@
<?php
use Codeception\Util\Fixtures;
use Codeception\Util\Stub;
use MailPoet\Models\Subscriber;
use MailPoet\Util\License\Features\Subscribers as SubscribersFeature;
class SubscribersFeaturesTest extends MailPoetTest {
function testChecksIfSubscribersWithinLimitWhenPremiumLicenseDoesNotExist() {
$subscribers_feature = new SubscribersFeature();
expect($subscribers_feature->check(0))->false();
$subscriber = Subscriber::create();
$subscriber->hydrate(Fixtures::get('subscriber_template'));
$subscriber->save();
expect($subscribers_feature->check(0))->true();
}
function testChecksIfSubscribersWithinLimitWhenPremiumLicenseExists() {
$subscribers_feature = Stub::construct(
new SubscribersFeature(),
array(
'license' => true
)
);
$subscriber = Subscriber::create();
$subscriber->hydrate(Fixtures::get('subscriber_template'));
$subscriber->save();
expect($subscribers_feature->check(0))->false();
}
function _after() {
ORM::raw_execute('TRUNCATE ' . Subscriber::$_table);
}
}

View File

@ -0,0 +1,10 @@
<?php
use MailPoet\Util\License\License;
class LicenseTest extends MailPoetTest {
function testItGetsLicense() {
expect(License::getLicense())->false();
expect(License::getLicense('valid'))->equals('valid');
}
}

View File

@ -39,8 +39,8 @@ jQuery('.toplevel_page_mailpoet-newsletters.menu-top-last')
<% block after_css %><% endblock %>
<script type="text/javascript">
var mailpoet_date_format = "<%= wp_datetime_format() %>";
var mailpoet_time_format = "<%= wp_time_format() %>";
var mailpoet_date_format = "<%= wp_datetime_format()|escape('js') %>";
var mailpoet_time_format = "<%= wp_time_format()|escape('js') %>";
</script>
<!-- javascripts -->

View File

@ -322,8 +322,8 @@
'failedToFetchAvailablePosts': __('Failed to fetch available posts'),
'failedToFetchRenderedPosts': __('Failed to fetch rendered posts'),
'shortcodesWindowTitle': __('Select a shortcode'),
'unsubscribeLinkMissing': __('All newsletters must include an "Unsubscribe" link. Add a footer widget to your newsletter to continue'),
'newsletterPreviewEmailMissing': __('Enter an email address to send the preview newsletter to'),
'unsubscribeLinkMissing': __('All emails must include an "Unsubscribe" link. Add a footer widget to your email to continue.'),
'newsletterPreviewEmailMissing': __('Enter an email address to send the preview newsletter to.'),
'newsletterPreviewSent': __('Your test email has been sent!'),
'templateNameMissing': __('Please add a template name'),
'templateDescriptionMissing': __('Please add a template description'),
@ -1209,7 +1209,7 @@
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
{ scroll: true, static: true }
);
}
});

View File

@ -1,2 +1,2 @@
<div class="mailpoet_container_empty">{{#ifCond emptyContainerMessage '!==' ''}}{{emptyContainerMessage}}{{else}}{{#if isRoot}}<%= __('Drop layout here!') %>{{else}}<%= __('Drop content here!') %>{{/if}}{{/ifCond}}</div>
<div class="mailpoet_container_empty">{{#ifCond emptyContainerMessage '!==' ''}}{{emptyContainerMessage}}{{else}}{{#if isRoot}}<%= __('Add a column block here.') %>{{else}}<%= __('Add a content block here.') %>{{/if}}{{/ifCond}}</div>
{{debug}}

View File

@ -1,4 +1,4 @@
<h3><%= __('Layout') %></h3>
<h3><%= __('Columns') %></h3>
<div class="mailpoet_form_field">
<label>
<div class="mailpoet_form_field_input_option">

View File

@ -1,4 +1,4 @@
<div class="handlediv" title="Click to toggle"><br></div>
<h3><%= __('Layout') %></h3>
<h3><%= __('Columns') %></h3>
<div class="mailpoet_region_content clearfix">
</div>

View File

@ -20,9 +20,9 @@
<% block translations %>
<%= localize({
'pageTitle': __('Newsletters'),
'pageTitle': __('Emails'),
'tabStandardTitle': __('Newsletters'),
'tabStandardTitle': __('Emails'),
'tabWelcomeTitle': __('Welcome Emails'),
'tabNotificationTitle': __('Post Notifications'),
@ -75,7 +75,7 @@
'trash': __('Trash'),
'edit': __('Edit'),
'duplicate': __('Duplicate'),
'newsletterDuplicated': __('Newsletter "%$1s" has been duplicated'),
'newsletterDuplicated': __('Email "%$1s" has been duplicated.'),
'notSentYet': __('Not sent yet'),
'scheduledFor': __('Scheduled for'),
'scheduleIt': __('Schedule it'),
@ -97,18 +97,18 @@
'delete': __('Delete'),
'select': __('Select'),
'preview': __('Preview'),
'selectTemplateTitle': __('Select a template'),
'selectTemplateTitle': __('Select a responsive template'),
'draftNewsletterTitle': __('Subject'),
'pickCampaignType': __('Pick a type of campaign'),
'pickCampaignType': __('Select type of email'),
'regularNewsletterTypeTitle': __('Newsletter'),
'regularNewsletterTypeDescription': __('Send a newsletter with images, buttons, dividers, and social bookmarks. Or, just send a basic text email.'),
'create': __('Create'),
'welcomeNewsletterTypeTitle': __('Welcome Email'),
'welcomeNewsletterTypeDescription': __('Automatically send an email (or series of emails) to new subscribers or WordPress users. Send a day, a week, or a month after they sign up.'),
'setUp': __('Set up'),
'postNotificationNewsletterTypeTitle': __('Post Notifications'),
'postNotificationsNewsletterTypeDescription': __('Automatically send posts immediately, daily, weekly or monthly. Filter by categories, if you like.'),
'postNotificationNewsletterTypeTitle': __('Latest Post Notifications'),
'postNotificationsNewsletterTypeDescription': __('Let MailPoet email your subscribers with your latest content. You can send daily, weekly, monthly, or even immediately after publication.'),
'selectFrequency': __('Select a frequency'),
'postNotificationSubjectLineTip': __("Insert [newsletter:total] to show number of posts, [newsletter:post_title] to show the latest post's title & [newsletter:number] to display the issue number."),
'activate': __('Activate'),
@ -146,7 +146,7 @@
'subjectLineTip': __("Be creative! It's the first thing that your subscribers see. Tempt them to open your email."),
'emptySubjectLineError': __('Please specify a subject'),
'segments': __('Lists'),
'segmentsTip': __('Your email newsletter(s) will be sent to this list.'),
'segmentsTip': __('This subscriber segment will be used for this email.'),
'selectSegmentPlaceholder': __('Select a list'),
'noSegmentsSelectedError': __('Please select a list'),
'sender': __('Sender'),
@ -160,7 +160,7 @@
'newsletterUpdated': __('Newsletter was updated successfully!'),
'newsletterAdded': __('Newsletter was added successfully!'),
'newsletterSendingError': __('An error occurred while trying to send. <a href="%$1s">Please check your settings</a>'),
'finalNewsletterStep': __('Final step: last details'),
'finalNewsletterStep': __('Final Step: Last Details'),
'saveDraftAndClose': __('Save as draft and close'),
'orSimply': __('or simply'),
'goBackToDesign': __('go back to the Design page'),

View File

@ -25,7 +25,7 @@
<tr>
<th scope="row">
<label>
<%= __('Newsletter task scheduler') %>
<%= __('Newsletter task scheduler (cron)') %>
</label>
<p class="description">
<%= __('Select what will activate your newsletter queue.') %>

View File

@ -148,7 +148,7 @@
<%= __('Subscribe in registration form') %>
</label>
<p class="description">
<%= __('Allow users who register as a WordPress user on your website to subscribe to a MailPoet list (in addition to the "WordPress Users" list') %>
<%= __('Allow users who register as a WordPress user on your website to subscribe to a MailPoet list (in addition to the "WordPress Users" list)') %>
</p>
</th>
<td>
@ -203,7 +203,7 @@
</div>
<% else %>
<p>
<em><%= __('Registration is disabled on this site') %></em>
<em><%= __('Registration is disabled on this site.') %></em>
</p>
<% endif %>
</td>
@ -270,9 +270,9 @@
<%= __('Unsubscribe page') %>
</label>
<p class="description">
<%= __('When your subscribers click the "Unsubscribe" link, they will be directed to this page') %>
<%= __('When your subscribers click the "Unsubscribe" link, they will be directed to this page.') %>
<br />
<%= __('Use this shortcode on your website\'s WordPress pages: [mailpoet_manage text="Manage your subscription"]') %>
<%= __('If you want to use a custom Unsubscribe page, simply paste this shortcode on to a WordPress page: [mailpoet_manage_text="Manage your subscription"]') %>
</p>
</th>
<td>
@ -340,7 +340,7 @@
<tr>
<th scope="row">
<label>
<%= __('Shortcode to Display Total Number of Subscribers') %>
<%= __('Shortcode to display total number of subscribers') %>
</label>
<p class="description">
<%= __('Paste this shortcode on a post or page to display the total number of confirmed subscribers') %>

View File

@ -194,7 +194,7 @@
if(~~($(this).val()) === 1) {
result = confirm("<%= __('Subscribers will need to activate their subscription via email in order to receive your newsletters. This is highly recommended!') %>");
} else {
result = confirm("<%= __('Unconfirmed subscribers will receive your newsletters from without needing to activate their subscriptions. This is not recommended!') %>");
result = confirm("<%= __('New subscribers will be automatically confirmed, without having to confirm their subscription. This is not recommended!') %>");
}
// if the user confirmed changing the signup confirmation (yes/no)
if(result === true) {

View File

@ -52,7 +52,7 @@
'userColumns': __('User fields'),
'selectedValueAlreadyMatched': __('The selected value is already matched to another field'),
'confirmCorrespondingColumn': __('Confirm that this field corresponds to the selected field'),
'columnContainInvalidElement': __('One of the fields contains an invalid email. Please fix it before continuing'),
'columnContainInvalidElement': __('One of the fields contains an invalid email. Please fix it before continuing.'),
'january': __('January'),
'february': __('February'),
'march': __('March'),
@ -65,15 +65,15 @@
'october': __('October'),
'november': __('November'),
'december': __('December'),
'noDateFieldMatch': __("Do not match as a 'date field' if most of the rows for that field return the same error"),
'emptyFirstRowDate': __('First row date cannot be empty'),
'noDateFieldMatch': __("Do not match as a 'date field' if most of the rows for that field return the same error."),
'emptyFirstRowDate': __('First row date cannot be empty.'),
'verifyDateMatch': __('Verify that the date in blue matches the original date'),
'pm': __('PM'),
'am': __('AM'),
'dateMatchError': __('Error matching date'),
'columnContainsInvalidDate': __('One of the fields contains an invalid date. Please fix it before continuing'),
'columnContainsInvalidDate': __('One of the fields contains an invalid date. Please fix before continuing.'),
'listCreateError': __('Error adding a new list:'),
'columnContainsInvalidElement': __('One of the fields contains an invalid email. Please fix before continuing'),
'columnContainsInvalidElement': __('One of the fields contains an invalid email. Please fix before continuing.'),
'customFieldCreateError': __('Custom field could not be created'),
'subscribersCreated': __('%1$s subscribers added to %2$s.'),
'subscribersUpdated': __('%1$s existing subscribers were updated and added to %2$s')

View File

@ -17,7 +17,7 @@
'pageTitle': __('Subscribers'),
'searchLabel': __('Search'),
'loadingItems': __('Loading subscribers...'),
'noItemsFound': __('No subscribers were found'),
'noItemsFound': __('No subscribers were found.'),
'selectAllLabel': __('All subscribers on this page are selected.'),
'selectedAllLabel': __('All %d subscribers are selected'),
'selectAllLink': __('Select all subscribers on all pages.'),

View File

@ -17,31 +17,15 @@
<div style="position: absolute; top: .2em; right: 0;"><img src="<%= image_url('welcome_template/mailpoet-logo.png') %>" alt="MailPoet Logo" /></div>
<h2 class="nav-tab-wrapper wp-clearfix">
<a href="admin.php?page=mailpoet-welcome" class="nav-tab"><%= __('Whats New') %></a>
<a href="admin.php?page=mailpoet-update" class="nav-tab nav-tab-active"><%= __('Changelog') %></a>
<a href="admin.php?page=mailpoet-welcome" class="nav-tab"><%= __('Welcome') %></a>
<a href="admin.php?page=mailpoet-update" class="nav-tab nav-tab-active"><%= __("What's New") %></a>
</h2>
<div id="mailpoet-changelog" clas="feature-section one-col">
<h2><%= __("List of Changes") %></h2>
<h3>0.0.48 - 2016-10-11</h3>
<h3>3.0.0-beta.1 - 2016-10-28</h3>
<ul>
<li>Added `mailpoet` text domain to gettext translation functions;</li>
<li>Added `.pot` translation template file generation to build process;</li>
<li>Fixed SQL injection via listings in admin panel;</li>
<li>Fixed stored XSS in Idiorm library demo code;</li>
<li>Fixed constant usage before initialization errors;</li>
<li>Fixed subscriber token leak via timing attacks;</li>
<li>Added a "Read More" link to "WordPress Users" list in "Lists" admin listing;</li>
<li>Removed test code and docs from vendor code in our distributable zip.</li>
</ul>
<br>
<h3>0.0.47 - 2016-10-04</h3>
<ul>
<li>Fixed subscription form to not send confirmation email when sending one is disabled in settings;</li>
<li>Fixed segment selection field to preselect previously used segments on last newsletter creation step;</li>
<li>Fixed segment subscriber count to be always displayed;</li>
<li>Changed segment subscriber count to not include unsubscribed or unconfirmed subscribers.</li>
<li>Initial public beta release;</li>
</ul>
<br>
@ -51,7 +35,7 @@
<div clas="feature-section one-col">
<br>
<p style="text-align: center"><a class="button button-primary" href="https://wordpress.org/plugins/wysija-newsletters/changelog/" target="_blank"><%= __("View all changes") %> &rarr;</a></p>
<p style="text-align: center"><a class="button button-primary" href="https://wordpress.org/plugins/mailpoet/changelog/" target="_blank"><%= __("View all changes") %> &rarr;</a></p>
</div>
</div>

View File

@ -26,8 +26,8 @@
<div style="position: absolute; top: .2em; right: 0;"><img src="<%= image_url('welcome_template/mailpoet-logo.png') %>" alt="<%= __('MailPoet Logo') %>" /></div>
<h2 class="nav-tab-wrapper wp-clearfix">
<a href="admin.php?page=mailpoet-welcome" class="nav-tab nav-tab-active"><%= __('Whats New') %></a>
<a href="admin.php?page=mailpoet-update" class="nav-tab"><%= __('Changelog') %></a>
<a href="admin.php?page=mailpoet-welcome" class="nav-tab nav-tab-active"><%= __('Welcome') %></a>
<a href="admin.php?page=mailpoet-update" class="nav-tab"><%= __("What's new") %></a>
</h2>
<div class="headline-feature feature-video">