Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
a03891895c | |||
3368e84a99 | |||
e90df2f08d | |||
2391ae1cad | |||
83114a8be4 | |||
d08d5a3b6c | |||
8330bfc884 | |||
ef21a8cca7 | |||
e32c46a755 | |||
092f69538a | |||
7a75367d75 | |||
0b2701ade2 | |||
1ac288d286 | |||
516bc73092 | |||
4088abef68 | |||
f6cefc3f5c | |||
202e4b90e1 | |||
ee89bf0722 | |||
876d21300a | |||
0ca5b7a79f | |||
5d0ee43921 | |||
cc523a3c0b | |||
2787998d32 | |||
38f6c95059 | |||
cc03b631ff | |||
a3c77fb685 | |||
3817e28960 | |||
c3a78b1ea3 | |||
42877236c8 | |||
6e87f3539c | |||
7704ea4b68 |
@ -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('');
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
2
build.sh
2
build.sh
@ -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
|
||||
|
144
lib/API/API.php
144
lib/API/API.php
@ -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
12
lib/API/Access.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace MailPoet\API;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
final class Access {
|
||||
const ALL = 'all';
|
||||
|
||||
private function __construct() {
|
||||
|
||||
}
|
||||
}
|
@ -5,6 +5,8 @@ if(!defined('ABSPATH')) exit;
|
||||
|
||||
abstract class Endpoint {
|
||||
|
||||
public $permissions = array();
|
||||
|
||||
function successResponse(
|
||||
$data = array(), $meta = array(), $status = Response::STATUS_OK
|
||||
) {
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -383,6 +383,7 @@ class Subscriber extends Model {
|
||||
'subscribers.id = relation.subscriber_id',
|
||||
'subscribers'
|
||||
)
|
||||
->whereNull('subscribers.deleted_at')
|
||||
->where('subscribers.status', 'subscribed');
|
||||
return $subscribers;
|
||||
}
|
||||
|
@ -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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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/
|
@ -4,7 +4,7 @@ if(!defined('ABSPATH')) exit;
|
||||
use \MailPoet\Config\Initializer;
|
||||
/*
|
||||
* Plugin Name: MailPoet
|
||||
* Version: 0.0.48
|
||||
* Version: 0.0.50
|
||||
* 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', '0.0.50');
|
||||
|
||||
$initializer = new Initializer(array(
|
||||
'file' => __FILE__,
|
||||
|
130
readme.txt
130
readme.txt
@ -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
|
||||
Stable tag: 3.0.0
|
||||
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 - 2016-09 =
|
||||
|
||||
* Hello world.
|
||||
|
||||
|
@ -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');
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
|
38
tests/unit/API/APITest.php
Normal file
38
tests/unit/API/APITest.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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 -->
|
||||
|
@ -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 }
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -23,25 +23,31 @@
|
||||
|
||||
<div id="mailpoet-changelog" clas="feature-section one-col">
|
||||
<h2><%= __("List of Changes") %></h2>
|
||||
<h3>0.0.48 - 2016-10-11</h3>
|
||||
<h3>0.0.50 - 2016-10-25</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>
|
||||
<li>Renamed "LICENSE" to "license.txt" in preparation for WP plugin repo;</li>
|
||||
<li>Updated "readme.txt" with newly prepared README text;</li>
|
||||
<li>Updated "View All Changes" button on plugin update page to link to MailPoet plugin repo page;</li>
|
||||
<li>Fixed date formatting to allow using escaped symbols;</li>
|
||||
<li>Fixed saving existing newsletters in newsletter editor and last newsletter creation step;</li>
|
||||
<li>Changed "Newsletter not found" error on newsletter editor page to be displayed permanently;</li>
|
||||
<li>Changed "newsletters.save" endpoint to require segment objects when saving newsletters;</li>
|
||||
<li>Fixed security issue with public token reuse on admin panel;</li>
|
||||
<li>Added endpoint specific access limits;</li>
|
||||
<li>Increased subscriber export filename length and complexity to make it more difficult to guess file names;</li>
|
||||
<li>Fixed sending queue to not send newsletters to trashed subscribers;</li>
|
||||
<li>Fixed post notifications to work properly with multiple notification newsletters sent at the same time;</li>
|
||||
<li>Fixed post notification newsletters to correctly set newsletter status once sending is completed;</li>
|
||||
<li>Fixed post notification newsletters to not send newsletters without any ALC posts;</li>
|
||||
<li>Fixed selecting multiple data fields in subscriber export.</li>
|
||||
</ul>
|
||||
<br>
|
||||
|
||||
<h3>0.0.47 - 2016-10-04</h3>
|
||||
<h3>0.0.49 - 2016-10-18</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>Fixed security issues in Front Router, subscriber import, newsletter preview, admin listings, Idiorm demo code and subscriber verification;</li>
|
||||
<li>Added unit tests for newsletter scheduler;</li>
|
||||
<li>Added "Read more" documentation URL describing "WordPress Users" list in admin listings.</li>
|
||||
</ul>
|
||||
<br>
|
||||
|
||||
@ -51,7 +57,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") %> →</a></p>
|
||||
<p style="text-align: center"><a class="button button-primary" href="https://wordpress.org/plugins/mailpoet/changelog/" target="_blank"><%= __("View all changes") %> →</a></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user