Compare commits
151 Commits
3.0.0-beta
...
3.0.0-beta
Author | SHA1 | Date | |
---|---|---|---|
8ae939080b | |||
610f74343c | |||
4de03db125 | |||
f734d47b26 | |||
9e879d0539 | |||
ff6dffd626 | |||
efe187953c | |||
ab8a503a48 | |||
81d91188ac | |||
d3353b793f | |||
625f828cce | |||
c90dd94cf3 | |||
a851527ff5 | |||
76e292f494 | |||
ed886280de | |||
f3b2db48c4 | |||
b60c758281 | |||
b758284df7 | |||
c6d43099a1 | |||
fde283dd56 | |||
5800883416 | |||
39a6318105 | |||
4196d35e35 | |||
c0653ce26c | |||
131f12d19e | |||
3e734b5c44 | |||
76a41d53d6 | |||
43cb03105b | |||
3647f70daa | |||
5956a200ea | |||
449eb28b2a | |||
912fca0e73 | |||
a3f2c32a6d | |||
e175e63a7c | |||
bfe898aae8 | |||
099c2a390d | |||
8fe57ff57f | |||
6dc9f9d2ab | |||
5463dd10e2 | |||
b8c56623a6 | |||
a6b00e1ba7 | |||
aa0078e5e4 | |||
714f81d936 | |||
497c2fb595 | |||
c291fada74 | |||
e4166cecba | |||
3a1568a31d | |||
02a5de0cd6 | |||
f5dce907ff | |||
2692c7c7dd | |||
4cfe3c534c | |||
f8d78d87af | |||
3b6e06eccb | |||
09b2246186 | |||
af83b02129 | |||
093fd2f082 | |||
c24d8135aa | |||
41fdc3af13 | |||
ec9417d293 | |||
6eb78b5bf7 | |||
877e110188 | |||
e6d909da21 | |||
c26a3f704b | |||
9425e390c5 | |||
698cdecbfe | |||
d5a1d94bca | |||
4f37a44363 | |||
95f8f130ea | |||
fb29800c7d | |||
d2b41a5b90 | |||
71db3e569d | |||
9a8f028a01 | |||
2c5e73305a | |||
90e7026355 | |||
d63ab6a927 | |||
e5cf57e4f8 | |||
6beada63de | |||
6699b52184 | |||
c651a8bbe8 | |||
4a171dca2d | |||
f821a60a2c | |||
24a3866e2a | |||
fc54f31d3d | |||
616883ed63 | |||
b6ce513927 | |||
456152b5cb | |||
0be790971a | |||
e255484bc8 | |||
fc53aca31d | |||
31116a7cf6 | |||
ee0c824126 | |||
9ee66160ec | |||
bc91b12cf3 | |||
457c43cd77 | |||
949d6033d7 | |||
cad6391fc6 | |||
e5e5e7b426 | |||
9095482af2 | |||
9698cf2d2e | |||
707afc2ae0 | |||
3b795a3e58 | |||
062f849fc8 | |||
98c6c29716 | |||
b4da3ecfb3 | |||
709f45941a | |||
9ac4c3de72 | |||
eee22227b3 | |||
edcce542c3 | |||
a354a380ba | |||
54eb667654 | |||
10207112bc | |||
892eea238f | |||
e6bb1666ee | |||
2be9985d20 | |||
f4b7acca1e | |||
de9d3655f0 | |||
dfa13726e7 | |||
61ab583030 | |||
6954501915 | |||
45a8103322 | |||
c4896f4662 | |||
71711b4a0d | |||
2634b606f6 | |||
0aa48b9121 | |||
1157cc8b9a | |||
88599963e0 | |||
57706dc1b3 | |||
694402e9f2 | |||
891239bf4e | |||
232494e1a2 | |||
4ae55230da | |||
eda4a9edcc | |||
5fb699fd5b | |||
7c5e0212ad | |||
44a77e097b | |||
22fd9e31f7 | |||
bbe3d48ec1 | |||
449978d7c1 | |||
0535e1eaeb | |||
4dfe4e4997 | |||
33a184fc4a | |||
19b34ed838 | |||
ca17e0c4da | |||
347e491865 | |||
59b6877675 | |||
6728203672 | |||
058ba53622 | |||
8a1450d7d6 | |||
49f2b147be | |||
8f3b2e6c0b | |||
baf0d374ae |
@ -8,8 +8,7 @@ Listen 8080
|
||||
LogLevel notice
|
||||
|
||||
<Directory /home/ubuntu/mailpoet/wordpress>
|
||||
AllowOverride All
|
||||
Allow from All
|
||||
Require all granted
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
|
||||
|
8
.circle_ci/fake-sendmail.rb
Executable file
8
.circle_ci/fake-sendmail.rb
Executable file
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/ruby
|
||||
path = "/tmp"
|
||||
Dir.mkdir(path) if !File.exists?(path)
|
||||
File.open("#{path}/mailpoet-#{Time.now.to_f}.txt", "w") do |f|
|
||||
sleep 5
|
||||
f.puts ARGV.inspect
|
||||
$stdin.each_line { |line| f.puts line }
|
||||
end
|
3
.circle_ci/mailpoet_php.ini
Normal file
3
.circle_ci/mailpoet_php.ini
Normal file
@ -0,0 +1,3 @@
|
||||
; For Unix only. You may supply arguments as well (default: "sendmail -t -i").
|
||||
; http://php.net/sendmail-path
|
||||
sendmail_path = /home/ubuntu/mailpoet/.circle_ci/fake-sendmail.rb
|
@ -13,4 +13,6 @@ WP_TEST_MAILER_MAILPOET_API=""
|
||||
WP_TEST_MAILER_SENDGRID_API=""
|
||||
WP_TEST_MAILER_SMTP_HOST=""
|
||||
WP_TEST_MAILER_SMTP_LOGIN=""
|
||||
WP_TEST_MAILER_SMTP_PASSWORD=""
|
||||
WP_TEST_MAILER_SMTP_PASSWORD=""
|
||||
WP_SVN_USERNAME=""
|
||||
WP_SVN_PASSWORD=""
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -17,4 +17,5 @@ tests/javascript/testBundles
|
||||
assets/css/*.css
|
||||
assets/js/*.js
|
||||
.vagrant
|
||||
lang
|
||||
lang
|
||||
.mp_svn
|
||||
|
17
README.md
17
README.md
@ -128,3 +128,20 @@ _n()
|
||||
- Handlebars.
|
||||
|
||||
You can use Twig i18n functions in Handlebars, just load your template from a Twig view.
|
||||
|
||||
# Publish
|
||||
|
||||
Before you run a publishing command, you need to:
|
||||
1. Ensure there is an up-to-date local copy of MailPoet SVN repository in `.mp_svn` directory by running `./do svn:checkout`.
|
||||
2. Have all your features merged in Git `master`, your `mailpoet.php` and `readme.txt` tagged with a new version.
|
||||
3. Run `./build.sh` to produce a `mailpoet.zip` distributable archive.
|
||||
|
||||
Everything's ready? Then run `./do svn:publish`.
|
||||
If the job goes fine, you'll get a message like this:
|
||||
```
|
||||
Go to '.mp_svn' and run 'svn ci -m "Release 3.0.0-beta.9"' to publish the
|
||||
release
|
||||
```
|
||||
It's quite literal: you can review the changes to be pushed and if you're satisfied, run the suggested command to finish the release publishing process.
|
||||
|
||||
If you're confident, execute `./do svn:publish --force` and your release will be published to the remote SVN repository without manual intervention (automatically). For easier authentication you might want to set `WP_SVN_USERNAME` and `WP_SVN_PASSWORD` environment variables.
|
||||
|
123
RoboFile.php
123
RoboFile.php
@ -99,15 +99,8 @@ class RoboFile extends \Robo\Tasks {
|
||||
|
||||
function makepot() {
|
||||
return $this->_exec('./node_modules/.bin/grunt makepot'.
|
||||
' --gruntfile '.__DIR__.'/tasks/makepot/makepot.js'.
|
||||
' --base_path '.__DIR__
|
||||
);
|
||||
}
|
||||
|
||||
function pushpot() {
|
||||
return $this->_exec('./node_modules/.bin/grunt pushpot'.
|
||||
' --gruntfile '.__DIR__.'/tasks/makepot/makepot.js'.
|
||||
' --base_path '.__DIR__
|
||||
' --gruntfile='.__DIR__.'/tasks/makepot/makepot.js'.
|
||||
' --base_path='.__DIR__
|
||||
);
|
||||
}
|
||||
|
||||
@ -199,8 +192,120 @@ class RoboFile extends \Robo\Tasks {
|
||||
);
|
||||
}
|
||||
|
||||
function svnCheckout() {
|
||||
return $this->_exec('svn co https://plugins.svn.wordpress.org/mailpoet/ .mp_svn');
|
||||
}
|
||||
|
||||
function svnPublish($opts = ['force' => false]) {
|
||||
$this->loadWPFunctions();
|
||||
|
||||
$svn_dir = ".mp_svn";
|
||||
$plugin_data = get_plugin_data('mailpoet.php', false, false);
|
||||
$plugin_version = $plugin_data['Version'];
|
||||
$plugin_dist_name = sanitize_title_with_dashes($plugin_data['Name']);
|
||||
$plugin_dist_file = $plugin_dist_name . '.zip';
|
||||
|
||||
$this->say('Publishing version: ' . $plugin_version);
|
||||
|
||||
// Sanity checks
|
||||
if(!is_readable($plugin_dist_file)) {
|
||||
$this->say("Failed to access " . $plugin_dist_file);
|
||||
return;
|
||||
} elseif(!file_exists($svn_dir . "/.svn/")) {
|
||||
$this->say("$svn_dir/.svn/ dir not found, is it a SVN repository?");
|
||||
return;
|
||||
} elseif(file_exists($svn_dir . "/tags/" . $plugin_version)) {
|
||||
$this->say("A SVN tag already exists: " . $plugin_version);
|
||||
return;
|
||||
}
|
||||
|
||||
$collection = $this->collection();
|
||||
|
||||
// Clean up tmp dirs if the previous run was halted
|
||||
if(file_exists("$svn_dir/trunk_new") || file_exists("$svn_dir/trunk_old")) {
|
||||
$this->taskFileSystemStack()
|
||||
->stopOnFail()
|
||||
->remove(array("$svn_dir/trunk_new", "$svn_dir/trunk_old"))
|
||||
->addToCollection($collection);
|
||||
}
|
||||
|
||||
// Extract the distributable zip to tmp trunk dir
|
||||
$this->taskExtract($plugin_dist_file)
|
||||
->to("$svn_dir/trunk_new")
|
||||
->preserveTopDirectory(false)
|
||||
->addToCollection($collection);
|
||||
|
||||
// Rename current trunk
|
||||
if(file_exists("$svn_dir/trunk")) {
|
||||
$this->taskFileSystemStack()
|
||||
->rename("$svn_dir/trunk", "$svn_dir/trunk_old")
|
||||
->addToCollection($collection);
|
||||
}
|
||||
|
||||
// Replace old trunk with a new one
|
||||
$this->taskFileSystemStack()
|
||||
->stopOnFail()
|
||||
->rename("$svn_dir/trunk_new", "$svn_dir/trunk")
|
||||
->remove("$svn_dir/trunk_old")
|
||||
->addToCollection($collection);
|
||||
|
||||
// Windows compatibility
|
||||
$awkCmd = '{print " --force \""$2"\""}';
|
||||
// Mac OS X compatibility
|
||||
$xargsFlag = (stripos(PHP_OS, 'Darwin') !== false) ? '' : '-r';
|
||||
|
||||
$this->taskExecStack()
|
||||
->stopOnFail()
|
||||
// Set SVN repo as working directory
|
||||
->dir($svn_dir)
|
||||
// Remove files from SVN repo that have already been removed locally
|
||||
->exec("svn st | grep ^! | awk '$awkCmd' | xargs $xargsFlag svn rm")
|
||||
// Recursively add files to SVN that haven't been added yet
|
||||
->exec("svn add --force * --auto-props --parents --depth infinity -q")
|
||||
// Tag the release
|
||||
->exec("svn cp trunk tags/$plugin_version")
|
||||
->addToCollection($collection);
|
||||
|
||||
$result = $collection->run();
|
||||
|
||||
if($result->wasSuccessful()) {
|
||||
// Run or suggest release command depending on a flag
|
||||
$release_cmd = "svn ci -m \"Release $plugin_version\"";
|
||||
if(!empty($opts['force'])) {
|
||||
$svn_login = getenv('WP_SVN_USERNAME');
|
||||
$svn_password = getenv('WP_SVN_PASSWORD');
|
||||
if ($svn_login && $svn_password) {
|
||||
$release_cmd .= " --username $svn_login --password $svn_password";
|
||||
} else {
|
||||
$release_cmd .= ' --force-interactive';
|
||||
}
|
||||
$result = $this->taskExecStack()
|
||||
->stopOnFail()
|
||||
->dir($svn_dir)
|
||||
->exec($release_cmd)
|
||||
->run();
|
||||
} else {
|
||||
$this->yell(
|
||||
"Go to '$svn_dir' and run '$release_cmd' to publish the release"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function loadEnv() {
|
||||
$dotenv = new Dotenv\Dotenv(__DIR__);
|
||||
$dotenv->load();
|
||||
}
|
||||
|
||||
protected function loadWPFunctions() {
|
||||
$this->loadEnv();
|
||||
define('ABSPATH', getenv('WP_TEST_PATH') . '/');
|
||||
define('WPINC', 'wp-includes');
|
||||
require_once(ABSPATH . WPINC . '/functions.php');
|
||||
require_once(ABSPATH . WPINC . '/formatting.php');
|
||||
require_once(ABSPATH . WPINC . '/plugin.php');
|
||||
require_once(ABSPATH . 'wp-admin/includes/plugin.php');
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ $block-text-line-height = $text-line-height
|
||||
position: relative
|
||||
line-height: $block-text-line-height
|
||||
|
||||
p, h1, h2, h3, h4, h5, h6
|
||||
h1, h2, h3, h4, h5, h6
|
||||
line-height: $block-text-line-height
|
||||
padding: 0
|
||||
margin: 0
|
||||
@ -44,9 +44,15 @@ $block-text-line-height = $text-line-height
|
||||
h1, h2, h3, h4, h5, h6
|
||||
margin-bottom: 0.3em
|
||||
|
||||
p
|
||||
p:not(.mailpoet_wp_post)
|
||||
margin-top: 0
|
||||
margin-bottom: 0
|
||||
padding: 0
|
||||
margin: 0
|
||||
|
||||
p
|
||||
line-height: $block-text-line-height
|
||||
font-style: normal
|
||||
font-weight: normal
|
||||
|
||||
ul
|
||||
|
@ -143,9 +143,7 @@ define('date',
|
||||
var convertedFormat = [];
|
||||
var escapeToken = false;
|
||||
|
||||
for (var index in format) {
|
||||
var token = format[index];
|
||||
|
||||
for(var index = 0, token = ''; token = format.charAt(index); index++){
|
||||
if (escapeToken === true) {
|
||||
convertedFormat.push('['+token+']');
|
||||
escapeToken = false;
|
||||
|
@ -1,10 +1,8 @@
|
||||
define('i18n',
|
||||
[
|
||||
'mailpoet',
|
||||
'underscore',
|
||||
'mailpoet'
|
||||
], function(
|
||||
MailPoet,
|
||||
_
|
||||
MailPoet
|
||||
) {
|
||||
'use strict';
|
||||
|
||||
|
@ -1,12 +1,11 @@
|
||||
define([
|
||||
'backbone',
|
||||
'backbone.marionette',
|
||||
'backbone.supermodel',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'handlebars',
|
||||
'handlebars_helpers'
|
||||
], function(Backbone, Marionette, SuperModel, jQuery, _, Handlebars) {
|
||||
], function(Backbone, Marionette, jQuery, _, Handlebars) {
|
||||
|
||||
var app = new Marionette.Application(), AppView;
|
||||
|
||||
|
@ -40,7 +40,7 @@ define([
|
||||
App.getChannel().trigger('beforeEditorSave', json);
|
||||
|
||||
// save newsletter
|
||||
CommunicationComponent.saveNewsletter(json).done(function(response) {
|
||||
return CommunicationComponent.saveNewsletter(json).done(function(response) {
|
||||
if(response.success !== undefined && response.success === true) {
|
||||
// TODO: Handle translations
|
||||
//MailPoet.Notice.success("<?php _e('Newsletter has been saved.'); ?>");
|
||||
@ -66,6 +66,14 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
// For getting a promise after triggering save event
|
||||
Module.saveAndProvidePromise = function(saveResult) {
|
||||
var promise = Module.save();
|
||||
if (saveResult !== undefined) {
|
||||
saveResult.promise = promise;
|
||||
}
|
||||
};
|
||||
|
||||
Module.getThumbnail = function(element, options) {
|
||||
var promise = html2canvas(element, options || {});
|
||||
|
||||
@ -335,12 +343,12 @@ define([
|
||||
};
|
||||
|
||||
App.on('before:start', function(options) {
|
||||
App.save = Module.save;
|
||||
App.save = Module.saveAndProvidePromise;
|
||||
App.getChannel().on('autoSave', Module.autoSave);
|
||||
|
||||
window.onbeforeunload = Module.beforeExitWithUnsavedChanges;
|
||||
|
||||
App.getChannel().on('save', function() { App.save(); });
|
||||
App.getChannel().on('save', function(saveResult) { App.save(saveResult); });
|
||||
});
|
||||
|
||||
App.on('start', function(options) {
|
||||
|
@ -301,19 +301,25 @@ define([
|
||||
// send test email
|
||||
MailPoet.Modal.loading(true);
|
||||
|
||||
CommunicationComponent.previewNewsletter(data).always(function() {
|
||||
MailPoet.Modal.loading(false);
|
||||
}).done(function(response) {
|
||||
MailPoet.Notice.success(
|
||||
MailPoet.I18n.t('newsletterPreviewSent'),
|
||||
{ scroll: true });
|
||||
}).fail(function(response) {
|
||||
if (response.errors.length > 0) {
|
||||
MailPoet.Notice.error(
|
||||
response.errors.map(function(error) { return error.message; }),
|
||||
{ scroll: true, static: true }
|
||||
);
|
||||
}
|
||||
// save before sending
|
||||
var saveResult = {promise: null};
|
||||
App.getChannel().trigger('save', saveResult);
|
||||
|
||||
saveResult.promise.always(function() {
|
||||
CommunicationComponent.previewNewsletter(data).always(function() {
|
||||
MailPoet.Modal.loading(false);
|
||||
}).done(function(response) {
|
||||
MailPoet.Notice.success(
|
||||
MailPoet.I18n.t('newsletterPreviewSent'),
|
||||
{ scroll: true });
|
||||
}).fail(function(response) {
|
||||
if (response.errors.length > 0) {
|
||||
MailPoet.Notice.error(
|
||||
response.errors.map(function(error) { return error.message; }),
|
||||
{ scroll: true, static: true }
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -11,6 +11,8 @@ dependencies:
|
||||
# install PHP dependencies for WordPress
|
||||
- sudo apt-get update
|
||||
- sudo apt-get --assume-yes install php5-mysql
|
||||
# Add a fake sendmail mailer
|
||||
- cp ./.circle_ci/mailpoet_php.ini /opt/circleci/php/$(phpenv global)/etc/conf.d/
|
||||
# configure Apache
|
||||
- sudo cp ./.circle_ci/apache/mailpoet.loc.conf /etc/apache2/sites-available
|
||||
- sudo a2ensite mailpoet.loc
|
||||
@ -64,3 +66,6 @@ test:
|
||||
- cp tests/_output/report.xml $CIRCLE_TEST_REPORTS/codeception/report.xml
|
||||
# Uncomment to copy PHP coverage report
|
||||
#- cp tests/_output/coverage.xml $CIRCLE_TEST_REPORTS/codeception/coverage.xml
|
||||
# Store any email output, sent via sendmail during tests
|
||||
- mkdir $CIRCLE_TEST_REPORTS/fake-mailer
|
||||
- cp /tmp/mailpoet-* $CIRCLE_TEST_REPORTS/fake-mailer
|
||||
|
@ -14,7 +14,8 @@
|
||||
"swiftmailer/swiftmailer": "^5.4",
|
||||
"mtdowling/cron-expression": "^1.1",
|
||||
"nesbot/carbon": "^1.21",
|
||||
"soundasleep/html2text": "dev-master"
|
||||
"soundasleep/html2text": "dev-master",
|
||||
"sabberworm/php-css-parser": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeception/codeception": "*",
|
||||
@ -37,4 +38,4 @@
|
||||
"post-update-cmd": "rm -rf vendor/squizlabs/php_codesniffer/CodeSniffer/Standards/PHPCompatibility; cp -rpd vendor/wimg/php-compatibility vendor/squizlabs/php_codesniffer/CodeSniffer/Standards/PHPCompatibility",
|
||||
"post-install-cmd": "rm -rf vendor/squizlabs/php_codesniffer/CodeSniffer/Standards/PHPCompatibility; cp -rpd vendor/wimg/php-compatibility vendor/squizlabs/php_codesniffer/CodeSniffer/Standards/PHPCompatibility"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
606
composer.lock
generated
606
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -220,7 +220,9 @@ class Newsletters extends APIEndpoint {
|
||||
$newsletter->save();
|
||||
$subscriber = Subscriber::getCurrentWPUser();
|
||||
$preview_url = NewsletterUrl::getViewInBrowserUrl(
|
||||
$data, $subscriber, $queue = false, $preview = true
|
||||
NewsletterUrl::TYPE_LISTING_EDITOR,
|
||||
$newsletter,
|
||||
$subscriber
|
||||
);
|
||||
|
||||
return $this->successResponse(
|
||||
@ -245,13 +247,11 @@ class Newsletters extends APIEndpoint {
|
||||
APIError::NOT_FOUND => __('This newsletter does not exist.', 'mailpoet')
|
||||
));
|
||||
} else {
|
||||
$newsletter = $newsletter->asArray();
|
||||
|
||||
$renderer = new Renderer($newsletter, $preview = true);
|
||||
$rendered_newsletter = $renderer->render();
|
||||
$divider = '***MailPoet***';
|
||||
$data_for_shortcodes = array_merge(
|
||||
array($newsletter['subject']),
|
||||
array($newsletter->subject),
|
||||
$rendered_newsletter
|
||||
);
|
||||
|
||||
@ -264,24 +264,27 @@ class Newsletters extends APIEndpoint {
|
||||
$newsletter,
|
||||
$subscriber
|
||||
);
|
||||
|
||||
list(
|
||||
$newsletter['subject'],
|
||||
$newsletter['body']['html'],
|
||||
$newsletter['body']['text']
|
||||
$rendered_newsletter['subject'],
|
||||
$rendered_newsletter['body']['html'],
|
||||
$rendered_newsletter['body']['text']
|
||||
) = explode($divider, $shortcodes->replace($body));
|
||||
|
||||
try {
|
||||
$mailer = new \MailPoet\Mailer\Mailer(
|
||||
$mailer = false,
|
||||
$sender = false,
|
||||
$reply_to = false
|
||||
$mailer = (!empty($data['mailer'])) ?
|
||||
$data['mailer'] :
|
||||
new \MailPoet\Mailer\Mailer(
|
||||
$mailer = false,
|
||||
$sender = false,
|
||||
$reply_to = false
|
||||
);
|
||||
$result = $mailer->send($newsletter, $data['subscriber']);
|
||||
$result = $mailer->send($rendered_newsletter, $data['subscriber']);
|
||||
|
||||
if($result['response'] === false) {
|
||||
$error = sprintf(
|
||||
__('The email could not be sent: %s', 'mailpoet'),
|
||||
$result['error']
|
||||
$result['error_message']
|
||||
);
|
||||
return $this->errorResponse(array(APIError::BAD_REQUEST => $error));
|
||||
} else {
|
||||
@ -310,7 +313,7 @@ class Newsletters extends APIEndpoint {
|
||||
|
||||
if($newsletter->type === Newsletter::TYPE_STANDARD) {
|
||||
$newsletter
|
||||
->withSegments()
|
||||
->withSegments(true)
|
||||
->withSendingQueue()
|
||||
->withStatistics();
|
||||
} else if($newsletter->type === Newsletter::TYPE_WELCOME) {
|
||||
@ -321,11 +324,11 @@ class Newsletters extends APIEndpoint {
|
||||
} else if($newsletter->type === Newsletter::TYPE_NOTIFICATION) {
|
||||
$newsletter
|
||||
->withOptions()
|
||||
->withSegments()
|
||||
->withSegments(true)
|
||||
->withChildrenCount();
|
||||
} else if($newsletter->type === Newsletter::TYPE_NOTIFICATION_HISTORY) {
|
||||
$newsletter
|
||||
->withSegments()
|
||||
->withSegments(true)
|
||||
->withSendingQueue()
|
||||
->withStatistics();
|
||||
}
|
||||
@ -339,7 +342,11 @@ class Newsletters extends APIEndpoint {
|
||||
// get preview url
|
||||
$subscriber = Subscriber::getCurrentWPUser();
|
||||
$newsletter->preview_url = NewsletterUrl::getViewInBrowserUrl(
|
||||
$newsletter, $subscriber, $queue, $preview = true);
|
||||
NewsletterUrl::TYPE_LISTING_EDITOR,
|
||||
$newsletter,
|
||||
$subscriber,
|
||||
$queue
|
||||
);
|
||||
|
||||
$data[] = $newsletter->asArray();
|
||||
}
|
||||
|
94
lib/Config/Database.php
Normal file
94
lib/Config/Database.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
namespace MailPoet\Config;
|
||||
|
||||
use ORM as ORM;
|
||||
use PDO as PDO;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
require_once(ABSPATH . 'wp-admin/includes/plugin.php');
|
||||
|
||||
class Database {
|
||||
public $driver_option_wait_timeout = 60;
|
||||
|
||||
function init() {
|
||||
$this->setupConnection();
|
||||
$this->setupLogging();
|
||||
$this->setupDriverOptions();
|
||||
$this->defineTables();
|
||||
}
|
||||
|
||||
function setupConnection() {
|
||||
ORM::configure(Env::$db_source_name);
|
||||
ORM::configure('username', Env::$db_username);
|
||||
ORM::configure('password', Env::$db_password);
|
||||
}
|
||||
|
||||
function setupLogging() {
|
||||
ORM::configure('logging', WP_DEBUG);
|
||||
}
|
||||
|
||||
function setupDriverOptions() {
|
||||
$driver_options = array(
|
||||
'TIME_ZONE = "' . Env::$db_timezone_offset . '"',
|
||||
'sql_mode=(SELECT REPLACE(@@sql_mode,"ONLY_FULL_GROUP_BY",""))',
|
||||
);
|
||||
$current_options = ORM::for_table("")
|
||||
->raw_query('SELECT @@session.wait_timeout as wait_timeout')
|
||||
->findOne();
|
||||
if($current_options && (int)$current_options->wait_timeout < $this->driver_option_wait_timeout) {
|
||||
$driver_options[] = 'SESSION wait_timeout = ' . $this->driver_option_wait_timeout;
|
||||
}
|
||||
// reset the database
|
||||
ORM::set_db(null);
|
||||
ORM::configure('driver_options', array(
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET ' . implode(', ', $driver_options)
|
||||
));
|
||||
}
|
||||
|
||||
function defineTables() {
|
||||
if(!defined('MP_SETTINGS_TABLE')) {
|
||||
$settings = Env::$db_prefix . 'settings';
|
||||
$segments = Env::$db_prefix . 'segments';
|
||||
$forms = Env::$db_prefix . 'forms';
|
||||
$custom_fields = Env::$db_prefix . 'custom_fields';
|
||||
$subscribers = Env::$db_prefix . 'subscribers';
|
||||
$subscriber_segment = Env::$db_prefix . 'subscriber_segment';
|
||||
$subscriber_custom_field = Env::$db_prefix . 'subscriber_custom_field';
|
||||
$newsletter_segment = Env::$db_prefix . 'newsletter_segment';
|
||||
$sending_queues = Env::$db_prefix . 'sending_queues';
|
||||
$newsletters = Env::$db_prefix . 'newsletters';
|
||||
$newsletter_templates = Env::$db_prefix . 'newsletter_templates';
|
||||
$newsletter_option_fields = Env::$db_prefix . 'newsletter_option_fields';
|
||||
$newsletter_option = Env::$db_prefix . 'newsletter_option';
|
||||
$newsletter_links = Env::$db_prefix . 'newsletter_links';
|
||||
$newsletter_posts = Env::$db_prefix . 'newsletter_posts';
|
||||
$statistics_newsletters = Env::$db_prefix . 'statistics_newsletters';
|
||||
$statistics_clicks = Env::$db_prefix . 'statistics_clicks';
|
||||
$statistics_opens = Env::$db_prefix . 'statistics_opens';
|
||||
$statistics_unsubscribes = Env::$db_prefix . 'statistics_unsubscribes';
|
||||
$statistics_forms = Env::$db_prefix . 'statistics_forms';
|
||||
|
||||
define('MP_SETTINGS_TABLE', $settings);
|
||||
define('MP_SEGMENTS_TABLE', $segments);
|
||||
define('MP_FORMS_TABLE', $forms);
|
||||
define('MP_CUSTOM_FIELDS_TABLE', $custom_fields);
|
||||
define('MP_SUBSCRIBERS_TABLE', $subscribers);
|
||||
define('MP_SUBSCRIBER_SEGMENT_TABLE', $subscriber_segment);
|
||||
define('MP_SUBSCRIBER_CUSTOM_FIELD_TABLE', $subscriber_custom_field);
|
||||
define('MP_SENDING_QUEUES_TABLE', $sending_queues);
|
||||
define('MP_NEWSLETTERS_TABLE', $newsletters);
|
||||
define('MP_NEWSLETTER_TEMPLATES_TABLE', $newsletter_templates);
|
||||
define('MP_NEWSLETTER_SEGMENT_TABLE', $newsletter_segment);
|
||||
define('MP_NEWSLETTER_OPTION_FIELDS_TABLE', $newsletter_option_fields);
|
||||
define('MP_NEWSLETTER_LINKS_TABLE', $newsletter_links);
|
||||
define('MP_NEWSLETTER_POSTS_TABLE', $newsletter_posts);
|
||||
define('MP_NEWSLETTER_OPTION_TABLE', $newsletter_option);
|
||||
define('MP_STATISTICS_NEWSLETTERS_TABLE', $statistics_newsletters);
|
||||
define('MP_STATISTICS_CLICKS_TABLE', $statistics_clicks);
|
||||
define('MP_STATISTICS_OPENS_TABLE', $statistics_opens);
|
||||
define('MP_STATISTICS_UNSUBSCRIBES_TABLE', $statistics_unsubscribes);
|
||||
define('MP_STATISTICS_FORMS_TABLE', $statistics_forms);
|
||||
}
|
||||
}
|
||||
}
|
@ -29,6 +29,7 @@ class Env {
|
||||
static $db_password;
|
||||
static $db_charset;
|
||||
static $db_timezone_offset;
|
||||
static $required_permission = 'manage_options';
|
||||
|
||||
static function init($file, $version) {
|
||||
global $wpdb;
|
||||
@ -80,12 +81,13 @@ class Env {
|
||||
return implode('', $source_name);
|
||||
}
|
||||
|
||||
private static function getDbTimezoneOffset() {
|
||||
$mins = get_option('gmt_offset') * 60;
|
||||
static function getDbTimezoneOffset($offset = false) {
|
||||
$offset = ($offset) ? $offset : get_option('gmt_offset');
|
||||
$mins = $offset * 60;
|
||||
$sgn = ($mins < 0 ? -1 : 1);
|
||||
$mins = abs($mins);
|
||||
$hrs = floor($mins / 60);
|
||||
$mins -= $hrs * 60;
|
||||
return sprintf('%+d:%02d', $hrs * $sgn, $mins);
|
||||
return sprintf('%+03d:%02d', $hrs * $sgn, $mins);
|
||||
}
|
||||
}
|
@ -169,10 +169,13 @@ class Hooks {
|
||||
}
|
||||
|
||||
function setupPostNotifications() {
|
||||
add_filter(
|
||||
'publish_post',
|
||||
'\MailPoet\Newsletter\Scheduler\Scheduler::schedulePostNotification',
|
||||
10, 1
|
||||
);
|
||||
$post_types = get_post_types();
|
||||
foreach($post_types as $post_type) {
|
||||
add_filter(
|
||||
'publish_' . $post_type,
|
||||
'\MailPoet\Newsletter\Scheduler\Scheduler::schedulePostNotification',
|
||||
10, 1
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
<?php
|
||||
namespace MailPoet\Config;
|
||||
|
||||
use MailPoet\API;
|
||||
use MailPoet\Cron\CronTrigger;
|
||||
use MailPoet\Router;
|
||||
use MailPoet\API;
|
||||
use MailPoet\Util\ConflictResolver;
|
||||
use MailPoet\WP\Notice as WPNotice;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
@ -18,26 +19,46 @@ class Initializer {
|
||||
'file' => '',
|
||||
'version' => '1.0.0'
|
||||
)) {
|
||||
Env::init($params['file'], $params['version']);
|
||||
Env::init($params['file'], $params['version']);
|
||||
}
|
||||
|
||||
function init() {
|
||||
$requiments_check_results = $this->checkRequirements();
|
||||
$requirements_check_results = $this->checkRequirements();
|
||||
|
||||
// abort initialization if PDO extension is missing
|
||||
if(!$requiments_check_results[RequirementsChecker::TEST_PDO_EXTENSION]) return;
|
||||
if(!$requirements_check_results[RequirementsChecker::TEST_PDO_EXTENSION] ||
|
||||
!$requirements_check_results[RequirementsChecker::TEST_VENDOR_SOURCE]
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->setupDB();
|
||||
|
||||
// activation function
|
||||
register_activation_hook(
|
||||
Env::$file,
|
||||
array('MailPoet\Config\Activator', 'activate')
|
||||
array(
|
||||
'MailPoet\Config\Activator',
|
||||
'activate'
|
||||
)
|
||||
);
|
||||
|
||||
add_action('plugins_loaded', array($this, 'setup'));
|
||||
add_action('init', array($this, 'onInit'));
|
||||
add_action('widgets_init', array($this, 'setupWidget'));
|
||||
add_action('plugins_loaded', array(
|
||||
$this,
|
||||
'setup'
|
||||
));
|
||||
add_action('init', array(
|
||||
$this,
|
||||
'onInit'
|
||||
));
|
||||
add_action('widgets_init', array(
|
||||
$this,
|
||||
'setupWidget'
|
||||
));
|
||||
add_action('wp_loaded', array(
|
||||
$this,
|
||||
'setupHooks'
|
||||
));
|
||||
}
|
||||
|
||||
function checkRequirements() {
|
||||
@ -46,57 +67,8 @@ class Initializer {
|
||||
}
|
||||
|
||||
function setupDB() {
|
||||
\ORM::configure(Env::$db_source_name);
|
||||
\ORM::configure('username', Env::$db_username);
|
||||
\ORM::configure('password', Env::$db_password);
|
||||
\ORM::configure('logging', WP_DEBUG);
|
||||
\ORM::configure('driver_options', array(
|
||||
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
|
||||
\PDO::MYSQL_ATTR_INIT_COMMAND =>
|
||||
'SET TIME_ZONE = "' . Env::$db_timezone_offset. '"'
|
||||
));
|
||||
|
||||
$settings = Env::$db_prefix . 'settings';
|
||||
$segments = Env::$db_prefix . 'segments';
|
||||
$forms = Env::$db_prefix . 'forms';
|
||||
$custom_fields = Env::$db_prefix . 'custom_fields';
|
||||
$subscribers = Env::$db_prefix . 'subscribers';
|
||||
$subscriber_segment = Env::$db_prefix . 'subscriber_segment';
|
||||
$subscriber_custom_field = Env::$db_prefix . 'subscriber_custom_field';
|
||||
$newsletter_segment = Env::$db_prefix . 'newsletter_segment';
|
||||
$sending_queues = Env::$db_prefix . 'sending_queues';
|
||||
$newsletters = Env::$db_prefix . 'newsletters';
|
||||
$newsletter_templates = Env::$db_prefix . 'newsletter_templates';
|
||||
$newsletter_option_fields = Env::$db_prefix . 'newsletter_option_fields';
|
||||
$newsletter_option = Env::$db_prefix . 'newsletter_option';
|
||||
$newsletter_links = Env::$db_prefix . 'newsletter_links';
|
||||
$newsletter_posts = Env::$db_prefix . 'newsletter_posts';
|
||||
$statistics_newsletters = Env::$db_prefix . 'statistics_newsletters';
|
||||
$statistics_clicks = Env::$db_prefix . 'statistics_clicks';
|
||||
$statistics_opens = Env::$db_prefix . 'statistics_opens';
|
||||
$statistics_unsubscribes = Env::$db_prefix . 'statistics_unsubscribes';
|
||||
$statistics_forms = Env::$db_prefix . 'statistics_forms';
|
||||
|
||||
define('MP_SETTINGS_TABLE', $settings);
|
||||
define('MP_SEGMENTS_TABLE', $segments);
|
||||
define('MP_FORMS_TABLE', $forms);
|
||||
define('MP_CUSTOM_FIELDS_TABLE', $custom_fields);
|
||||
define('MP_SUBSCRIBERS_TABLE', $subscribers);
|
||||
define('MP_SUBSCRIBER_SEGMENT_TABLE', $subscriber_segment);
|
||||
define('MP_SUBSCRIBER_CUSTOM_FIELD_TABLE', $subscriber_custom_field);
|
||||
define('MP_SENDING_QUEUES_TABLE', $sending_queues);
|
||||
define('MP_NEWSLETTERS_TABLE', $newsletters);
|
||||
define('MP_NEWSLETTER_TEMPLATES_TABLE', $newsletter_templates);
|
||||
define('MP_NEWSLETTER_SEGMENT_TABLE', $newsletter_segment);
|
||||
define('MP_NEWSLETTER_OPTION_FIELDS_TABLE', $newsletter_option_fields);
|
||||
define('MP_NEWSLETTER_LINKS_TABLE', $newsletter_links);
|
||||
define('MP_NEWSLETTER_POSTS_TABLE', $newsletter_posts);
|
||||
define('MP_NEWSLETTER_OPTION_TABLE', $newsletter_option);
|
||||
define('MP_STATISTICS_NEWSLETTERS_TABLE', $statistics_newsletters);
|
||||
define('MP_STATISTICS_CLICKS_TABLE', $statistics_clicks);
|
||||
define('MP_STATISTICS_OPENS_TABLE', $statistics_opens);
|
||||
define('MP_STATISTICS_UNSUBSCRIBES_TABLE', $statistics_unsubscribes);
|
||||
define('MP_STATISTICS_FORMS_TABLE', $statistics_forms);
|
||||
$database = new Database();
|
||||
$database->init();
|
||||
}
|
||||
|
||||
function setup() {
|
||||
@ -108,9 +80,9 @@ class Initializer {
|
||||
$this->setupAnalytics();
|
||||
$this->setupChangelog();
|
||||
$this->setupShortcodes();
|
||||
$this->setupHooks();
|
||||
$this->setupImages();
|
||||
$this->setupCronTrigger();
|
||||
$this->setupConflictResolver();
|
||||
|
||||
$this->plugin_initialized = true;
|
||||
} catch(\Exception $e) {
|
||||
@ -128,6 +100,7 @@ class Initializer {
|
||||
$this->setupAPI();
|
||||
$this->setupRouter();
|
||||
$this->setupPages();
|
||||
do_action('mailpoet_initialized', MAILPOET_VERSION);
|
||||
} catch(\Exception $e) {
|
||||
$this->handleFailedInitialization($e);
|
||||
}
|
||||
@ -220,6 +193,11 @@ class Initializer {
|
||||
add_image_size('mailpoet_newsletter_max', 1320);
|
||||
}
|
||||
|
||||
function setupConflictResolver() {
|
||||
$conflict_resolver = new ConflictResolver();
|
||||
$conflict_resolver->init();
|
||||
}
|
||||
|
||||
function handleFailedInitialization($message) {
|
||||
return WPNotice::displayError($message);
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ namespace MailPoet\Config;
|
||||
use MailPoet\Cron\CronTrigger;
|
||||
use MailPoet\Form\Block;
|
||||
use MailPoet\Form\Renderer as FormRenderer;
|
||||
use MailPoet\Listing;
|
||||
use MailPoet\Models\CustomField;
|
||||
use MailPoet\Models\Form;
|
||||
use MailPoet\Models\Segment;
|
||||
@ -13,10 +14,10 @@ use MailPoet\Newsletter\Shortcodes\ShortcodesHelper;
|
||||
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;
|
||||
use MailPoet\WP\Notice as WPNotice;
|
||||
use MailPoet\WP\Readme;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
@ -39,12 +40,17 @@ class Menu {
|
||||
}
|
||||
|
||||
function setup() {
|
||||
if(self::isOnMailPoetAdminPage()) {
|
||||
do_action('mailpoet_conflict_resolver_styles');
|
||||
do_action('mailpoet_conflict_resolver_scripts');
|
||||
}
|
||||
|
||||
$main_page_slug = 'mailpoet-newsletters';
|
||||
|
||||
add_menu_page(
|
||||
'MailPoet',
|
||||
'MailPoet',
|
||||
'manage_options',
|
||||
Env::$required_permission,
|
||||
$main_page_slug,
|
||||
null,
|
||||
$this->assets_url . '/img/menu_icon.png',
|
||||
@ -55,13 +61,16 @@ class Menu {
|
||||
$main_page_slug,
|
||||
$this->setPageTitle(__('Emails', 'mailpoet')),
|
||||
__('Emails', 'mailpoet'),
|
||||
'manage_options',
|
||||
Env::$required_permission,
|
||||
$main_page_slug,
|
||||
array($this, 'newsletters')
|
||||
array(
|
||||
$this,
|
||||
'newsletters'
|
||||
)
|
||||
);
|
||||
|
||||
// add limit per page to screen options
|
||||
add_action('load-'.$newsletters_page, function() {
|
||||
add_action('load-' . $newsletters_page, function() {
|
||||
add_screen_option('per_page', array(
|
||||
'label' => _x(
|
||||
'Number of newsletters per page',
|
||||
@ -75,12 +84,15 @@ class Menu {
|
||||
$main_page_slug,
|
||||
$this->setPageTitle(__('Forms', 'mailpoet')),
|
||||
__('Forms', 'mailpoet'),
|
||||
'manage_options',
|
||||
Env::$required_permission,
|
||||
'mailpoet-forms',
|
||||
array($this, 'forms')
|
||||
array(
|
||||
$this,
|
||||
'forms'
|
||||
)
|
||||
);
|
||||
// add limit per page to screen options
|
||||
add_action('load-'.$forms_page, function() {
|
||||
add_action('load-' . $forms_page, function() {
|
||||
add_screen_option('per_page', array(
|
||||
'label' => _x(
|
||||
'Number of forms per page',
|
||||
@ -94,12 +106,15 @@ class Menu {
|
||||
$main_page_slug,
|
||||
$this->setPageTitle(__('Subscribers', 'mailpoet')),
|
||||
__('Subscribers', 'mailpoet'),
|
||||
'manage_options',
|
||||
Env::$required_permission,
|
||||
'mailpoet-subscribers',
|
||||
array($this, 'subscribers')
|
||||
array(
|
||||
$this,
|
||||
'subscribers'
|
||||
)
|
||||
);
|
||||
// add limit per page to screen options
|
||||
add_action('load-'.$subscribers_page, function() {
|
||||
add_action('load-' . $subscribers_page, function() {
|
||||
add_screen_option('per_page', array(
|
||||
'label' => _x(
|
||||
'Number of subscribers per page',
|
||||
@ -113,13 +128,16 @@ class Menu {
|
||||
$main_page_slug,
|
||||
$this->setPageTitle(__('Lists', 'mailpoet')),
|
||||
__('Lists', 'mailpoet'),
|
||||
'manage_options',
|
||||
Env::$required_permission,
|
||||
'mailpoet-segments',
|
||||
array($this, 'segments')
|
||||
array(
|
||||
$this,
|
||||
'segments'
|
||||
)
|
||||
);
|
||||
|
||||
// add limit per page to screen options
|
||||
add_action('load-'.$segments_page, function() {
|
||||
add_action('load-' . $segments_page, function() {
|
||||
add_screen_option('per_page', array(
|
||||
'label' => _x(
|
||||
'Number of segments per page',
|
||||
@ -131,64 +149,85 @@ class Menu {
|
||||
|
||||
add_submenu_page(
|
||||
$main_page_slug,
|
||||
$this->setPageTitle( __('Settings', 'mailpoet')),
|
||||
$this->setPageTitle(__('Settings', 'mailpoet')),
|
||||
__('Settings', 'mailpoet'),
|
||||
'manage_options',
|
||||
Env::$required_permission,
|
||||
'mailpoet-settings',
|
||||
array($this, 'settings')
|
||||
array(
|
||||
$this,
|
||||
'settings'
|
||||
)
|
||||
);
|
||||
add_submenu_page(
|
||||
'admin.php?page=mailpoet-subscribers',
|
||||
$this->setPageTitle( __('Import', 'mailpoet')),
|
||||
$this->setPageTitle(__('Import', 'mailpoet')),
|
||||
__('Import', 'mailpoet'),
|
||||
'manage_options',
|
||||
Env::$required_permission,
|
||||
'mailpoet-import',
|
||||
array($this, 'import')
|
||||
array(
|
||||
$this,
|
||||
'import'
|
||||
)
|
||||
);
|
||||
|
||||
add_submenu_page(
|
||||
true,
|
||||
$this->setPageTitle(__('Export', 'mailpoet')),
|
||||
__('Export', 'mailpoet'),
|
||||
'manage_options',
|
||||
Env::$required_permission,
|
||||
'mailpoet-export',
|
||||
array($this, 'export')
|
||||
array(
|
||||
$this,
|
||||
'export'
|
||||
)
|
||||
);
|
||||
|
||||
add_submenu_page(
|
||||
true,
|
||||
$this->setPageTitle(__('Welcome', 'mailpoet')),
|
||||
__('Welcome', 'mailpoet'),
|
||||
'manage_options',
|
||||
Env::$required_permission,
|
||||
'mailpoet-welcome',
|
||||
array($this, 'welcome')
|
||||
array(
|
||||
$this,
|
||||
'welcome'
|
||||
)
|
||||
);
|
||||
|
||||
add_submenu_page(
|
||||
true,
|
||||
$this->setPageTitle(__('Update', 'mailpoet')),
|
||||
__('Update', 'mailpoet'),
|
||||
'manage_options',
|
||||
Env::$required_permission,
|
||||
'mailpoet-update',
|
||||
array($this, 'update')
|
||||
array(
|
||||
$this,
|
||||
'update'
|
||||
)
|
||||
);
|
||||
|
||||
add_submenu_page(
|
||||
true,
|
||||
$this->setPageTitle(__('Form Editor', 'mailpoet')),
|
||||
__('Form Editor', 'mailpoet'),
|
||||
'manage_options',
|
||||
Env::$required_permission,
|
||||
'mailpoet-form-editor',
|
||||
array($this, 'formEditor')
|
||||
array(
|
||||
$this,
|
||||
'formEditor'
|
||||
)
|
||||
);
|
||||
|
||||
add_submenu_page(
|
||||
true,
|
||||
$this->setPageTitle(__('Newsletter', 'mailpoet')),
|
||||
__('Newsletter Editor', 'mailpoet'),
|
||||
'manage_options',
|
||||
Env::$required_permission,
|
||||
'mailpoet-newsletter-editor',
|
||||
array($this, 'newletterEditor')
|
||||
array(
|
||||
$this,
|
||||
'newletterEditor'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -242,11 +281,19 @@ class Menu {
|
||||
'sub_menu' => 'mailpoet-newsletters'
|
||||
);
|
||||
|
||||
$readme_file = Env::$path . '/readme.txt';
|
||||
if(is_readable($readme_file)) {
|
||||
$changelog = Readme::parseChangelog(file_get_contents($readme_file), 2);
|
||||
if($changelog) {
|
||||
$data['changelog'] = $changelog;
|
||||
}
|
||||
}
|
||||
|
||||
$this->displayPage('update.html', $data);
|
||||
}
|
||||
|
||||
function settings() {
|
||||
if ($this->subscribers_over_limit) return $this->displaySubscriberLimitExceededTemplate();
|
||||
if($this->subscribers_over_limit) return $this->displaySubscriberLimitExceededTemplate();
|
||||
|
||||
$settings = Setting::getAll();
|
||||
$flags = $this->_getFlags();
|
||||
@ -320,7 +367,7 @@ class Menu {
|
||||
}
|
||||
|
||||
function segments() {
|
||||
if ($this->subscribers_over_limit) return $this->displaySubscriberLimitExceededTemplate();
|
||||
if($this->subscribers_over_limit) return $this->displaySubscriberLimitExceededTemplate();
|
||||
|
||||
$data = array();
|
||||
$data['items_per_page'] = $this->getLimitPerPage('segments');
|
||||
@ -328,7 +375,7 @@ class Menu {
|
||||
}
|
||||
|
||||
function forms() {
|
||||
if ($this->subscribers_over_limit) return $this->displaySubscriberLimitExceededTemplate();
|
||||
if($this->subscribers_over_limit) return $this->displaySubscriberLimitExceededTemplate();
|
||||
|
||||
$data = array();
|
||||
|
||||
@ -339,7 +386,7 @@ class Menu {
|
||||
}
|
||||
|
||||
function newsletters() {
|
||||
if ($this->subscribers_over_limit) return $this->displaySubscriberLimitExceededTemplate();
|
||||
if($this->subscribers_over_limit) return $this->displaySubscriberLimitExceededTemplate();
|
||||
|
||||
global $wp_roles;
|
||||
|
||||
@ -442,7 +489,7 @@ class Menu {
|
||||
}
|
||||
|
||||
$listing_per_page = get_user_meta(
|
||||
get_current_user_id(), 'mailpoet_'.$model.'_per_page', true
|
||||
get_current_user_id(), 'mailpoet_' . $model . '_per_page', true
|
||||
);
|
||||
return (!empty($listing_per_page))
|
||||
? (int)$listing_per_page
|
||||
@ -452,9 +499,13 @@ class Menu {
|
||||
private function displayPage($template, $data) {
|
||||
try {
|
||||
echo $this->renderer->render($template, $data);
|
||||
} catch (\Exception $e) {
|
||||
} catch(\Exception $e) {
|
||||
$notice = new WPNotice(WPNotice::TYPE_ERROR, $e->getMessage());
|
||||
$notice->displayWPNotice();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function isOnMailPoetAdminPage() {
|
||||
return (!empty($_REQUEST['page']) && stripos($_REQUEST['page'], 'mailpoet-') !== false);
|
||||
}
|
||||
}
|
@ -106,6 +106,7 @@ class Migrator {
|
||||
function sendingQueues() {
|
||||
$attributes = array(
|
||||
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
||||
'type varchar(12) NULL DEFAULT NULL,',
|
||||
'newsletter_id mediumint(9) NOT NULL,',
|
||||
'newsletter_rendered_body longtext,',
|
||||
'newsletter_rendered_subject varchar(250) NULL DEFAULT NULL,',
|
||||
@ -139,6 +140,7 @@ class Migrator {
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'deleted_at TIMESTAMP NULL,',
|
||||
'unconfirmed_data longtext,',
|
||||
'PRIMARY KEY (id),',
|
||||
'UNIQUE KEY email (email)'
|
||||
);
|
||||
@ -176,6 +178,7 @@ class Migrator {
|
||||
function newsletters() {
|
||||
$attributes = array(
|
||||
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
|
||||
'hash varchar(150) NULL DEFAULT NULL,',
|
||||
'parent_id mediumint(9) NULL,',
|
||||
'subject varchar(250) NOT NULL DEFAULT "",',
|
||||
'type varchar(20) NOT NULL DEFAULT "standard",',
|
||||
@ -312,7 +315,8 @@ class Migrator {
|
||||
'count mediumint(9) NOT NULL,',
|
||||
'created_at TIMESTAMP NULL,',
|
||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id)',
|
||||
'PRIMARY KEY (id),',
|
||||
'KEY queue_id (queue_id)',
|
||||
);
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
}
|
||||
@ -324,7 +328,8 @@ class Migrator {
|
||||
'subscriber_id mediumint(9) NOT NULL,',
|
||||
'queue_id mediumint(9) NOT NULL,',
|
||||
'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id)',
|
||||
'PRIMARY KEY (id),',
|
||||
'KEY queue_id (queue_id)',
|
||||
);
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
}
|
||||
@ -336,7 +341,8 @@ class Migrator {
|
||||
'subscriber_id mediumint(9) NOT NULL,',
|
||||
'queue_id mediumint(9) NOT NULL,',
|
||||
'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
|
||||
'PRIMARY KEY (id)',
|
||||
'PRIMARY KEY (id),',
|
||||
'KEY queue_id (queue_id)',
|
||||
);
|
||||
return $this->sqlify(__FUNCTION__, $attributes);
|
||||
}
|
||||
|
@ -55,8 +55,8 @@ class Renderer {
|
||||
|
||||
function setupGlobalVariables() {
|
||||
$this->renderer->addExtension(new Twig\Assets(array(
|
||||
'assets_url' => Env::$assets_url,
|
||||
'assets_path' => Env::$assets_path
|
||||
'version' => Env::$version,
|
||||
'assets_url' => Env::$assets_url
|
||||
)));
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace MailPoet\Config;
|
||||
|
||||
use MailPoet\Config\Env;
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoet\WP\Notice as WPNotice;
|
||||
|
||||
@ -11,7 +12,30 @@ class RequirementsChecker {
|
||||
const TEST_FOLDER_PERMISSIONS = 'TempAndCacheFolderCreation';
|
||||
const TEST_PDO_EXTENSION = 'PDOExtension';
|
||||
const TEST_MBSTRING_EXTENSION = 'MbstringExtension';
|
||||
const TEST_VENDOR_SOURCE = 'VendorSource';
|
||||
|
||||
public $display_error_notice;
|
||||
public $vendor_classes = array(
|
||||
'\ORM',
|
||||
'\Model',
|
||||
'\Twig_Environment',
|
||||
'\Twig_Loader_Filesystem',
|
||||
'\Twig_Lexer',
|
||||
'\Twig_Extension',
|
||||
'\Twig_Extension_GlobalsInterface',
|
||||
'\Twig_SimpleFunction',
|
||||
'\Swift_Mailer',
|
||||
'\Swift_SmtpTransport',
|
||||
'\Swift_Message',
|
||||
'\Carbon\Carbon',
|
||||
'\Sudzy\ValidModel',
|
||||
'\Sudzy\ValidationException',
|
||||
'\Sudzy\Engine',
|
||||
'\pQuery',
|
||||
'\Cron\CronExpression',
|
||||
'\Html2Text\Html2Text',
|
||||
'\csstidy'
|
||||
);
|
||||
|
||||
function __construct($display_error_notice = true) {
|
||||
$this->display_error_notice = $display_error_notice;
|
||||
@ -22,7 +46,8 @@ class RequirementsChecker {
|
||||
self::TEST_PDO_EXTENSION,
|
||||
self::TEST_PHP_VERSION,
|
||||
self::TEST_FOLDER_PERMISSIONS,
|
||||
self::TEST_MBSTRING_EXTENSION
|
||||
self::TEST_MBSTRING_EXTENSION,
|
||||
self::TEST_VENDOR_SOURCE
|
||||
);
|
||||
$results = array();
|
||||
foreach($available_tests as $test) {
|
||||
@ -84,10 +109,47 @@ class RequirementsChecker {
|
||||
return true;
|
||||
}
|
||||
|
||||
function checkVendorSource() {
|
||||
foreach($this->vendor_classes as $dependency) {
|
||||
$dependency_path = $this->getDependencyPath($dependency);
|
||||
if(!$dependency_path) {
|
||||
$error = sprintf(
|
||||
__('A MailPoet dependency (%s) does not appear to be loaded correctly, thus MailPoet will not work correctly. Please reinstall the plugin.', 'mailpoet'),
|
||||
$dependency
|
||||
);
|
||||
|
||||
return $this->processError($error);
|
||||
}
|
||||
|
||||
$pattern = '#' . preg_quote(Env::$path) . '[\\\/]#';
|
||||
$is_loaded_by_plugin = preg_match($pattern, $dependency_path);
|
||||
if(!$is_loaded_by_plugin) {
|
||||
$error = sprintf(
|
||||
__('MailPoet has detected a dependency conflict (%s) with another plugin (%s), which may cause unexpected behavior. Please disable the offending plugin to fix this issue.', 'mailpoet'),
|
||||
$dependency,
|
||||
$dependency_path
|
||||
);
|
||||
|
||||
return $this->processError($error);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getDependencyPath($namespaced_class) {
|
||||
try {
|
||||
$reflector = new \ReflectionClass($namespaced_class);
|
||||
return $reflector->getFileName();
|
||||
} catch(\ReflectionException $ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function processError($error) {
|
||||
if($this->display_error_notice) {
|
||||
WPNotice::displayError($error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ namespace MailPoet\Config;
|
||||
use \MailPoet\Models\Newsletter;
|
||||
use \MailPoet\Models\Subscriber;
|
||||
use \MailPoet\Models\SubscriberSegment;
|
||||
use \MailPoet\Subscription;
|
||||
use MailPoet\Newsletter\Url as NewsletterUrl;
|
||||
|
||||
class Shortcodes {
|
||||
@ -33,7 +32,7 @@ class Shortcodes {
|
||||
), 2);
|
||||
add_filter('mailpoet_archive_subject', array(
|
||||
$this, 'renderArchiveSubject'
|
||||
), 2);
|
||||
), 2, 3);
|
||||
}
|
||||
|
||||
function formWidget($params = array()) {
|
||||
@ -78,6 +77,8 @@ class Shortcodes {
|
||||
|
||||
$newsletters = Newsletter::getArchives($segment_ids);
|
||||
|
||||
$subscriber = Subscriber::getCurrentWPUser();
|
||||
|
||||
if(empty($newsletters)) {
|
||||
return apply_filters(
|
||||
'mailpoet_archive_no_newsletters',
|
||||
@ -91,12 +92,13 @@ class Shortcodes {
|
||||
|
||||
$html .= '<ul class="mailpoet_archive">';
|
||||
foreach($newsletters as $newsletter) {
|
||||
$queue = $newsletter->queue()->findOne();
|
||||
$html .= '<li>'.
|
||||
'<span class="mailpoet_archive_date">'.
|
||||
apply_filters('mailpoet_archive_date', $newsletter).
|
||||
'</span>
|
||||
<span class="mailpoet_archive_subject">'.
|
||||
apply_filters('mailpoet_archive_subject', $newsletter).
|
||||
apply_filters('mailpoet_archive_subject', $newsletter, $subscriber, $queue).
|
||||
'</span>
|
||||
</li>';
|
||||
}
|
||||
@ -112,13 +114,16 @@ class Shortcodes {
|
||||
);
|
||||
}
|
||||
|
||||
function renderArchiveSubject($newsletter) {
|
||||
$preview_url = NewsletterUrl::getViewInBrowserUrl($newsletter);
|
||||
|
||||
function renderArchiveSubject($newsletter, $subscriber, $queue) {
|
||||
$preview_url = NewsletterUrl::getViewInBrowserUrl(
|
||||
NewsletterUrl::TYPE_ARCHIVE,
|
||||
$newsletter,
|
||||
$subscriber,
|
||||
$queue
|
||||
);
|
||||
return '<a href="'.esc_attr($preview_url).'" target="_blank" title="'
|
||||
.esc_attr(__('Preview in a new tab', 'mailpoet')).'">'
|
||||
.esc_attr($newsletter->subject).
|
||||
'</a>';
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ class Widget {
|
||||
$this->setupDependencies();
|
||||
$this->setupIframe();
|
||||
} else {
|
||||
$this->setupAdminDependencies();
|
||||
add_action('widgets_admin_page', array($this, 'setupAdminWidgetPageDependencies'));
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,26 +110,20 @@ class Widget {
|
||||
));
|
||||
}
|
||||
|
||||
function setupAdminDependencies() {
|
||||
if(
|
||||
empty($_GET['page'])
|
||||
or
|
||||
isset($_GET['page']) && strpos($_GET['page'], 'mailpoet') === false
|
||||
) {
|
||||
wp_enqueue_script('mailpoet_vendor',
|
||||
Env::$assets_url.'/js/vendor.js',
|
||||
array(),
|
||||
Env::$version,
|
||||
true
|
||||
);
|
||||
function setupAdminWidgetPageDependencies() {
|
||||
wp_enqueue_script('mailpoet_vendor',
|
||||
Env::$assets_url.'/js/vendor.js',
|
||||
array(),
|
||||
Env::$version,
|
||||
true
|
||||
);
|
||||
|
||||
wp_enqueue_script('mailpoet_admin',
|
||||
Env::$assets_url.'/js/mailpoet.js',
|
||||
array(),
|
||||
Env::$version,
|
||||
true
|
||||
);
|
||||
}
|
||||
wp_enqueue_script('mailpoet_admin',
|
||||
Env::$assets_url.'/js/mailpoet.js',
|
||||
array(),
|
||||
Env::$version,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: extract this method into an Initializer
|
||||
|
@ -2,7 +2,7 @@
|
||||
namespace MailPoet\Cron;
|
||||
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Router\Endpoints\Queue as QueueEndpoint;
|
||||
use MailPoet\Router\Endpoints\CronDaemon as CronDaemonEndpoint;
|
||||
use MailPoet\Router\Router;
|
||||
use MailPoet\Util\Security;
|
||||
|
||||
@ -46,11 +46,27 @@ class CronHelper {
|
||||
return Security::generateRandomString();
|
||||
}
|
||||
|
||||
static function pingDaemon() {
|
||||
$url = Router::buildRequest(
|
||||
CronDaemonEndpoint::ENDPOINT,
|
||||
CronDaemonEndpoint::ACTION_PING_RESPONSE
|
||||
);
|
||||
$url = str_replace(home_url(), self::getSiteUrl(), $url);
|
||||
$args = array(
|
||||
'blocking' => true,
|
||||
'sslverify' => false,
|
||||
'timeout' => self::DAEMON_REQUEST_TIMEOUT,
|
||||
'user-agent' => 'MailPoet (www.mailpoet.com) Cron'
|
||||
);
|
||||
$result = wp_remote_get($url, $args);
|
||||
return wp_remote_retrieve_body($result) === 'pong';
|
||||
}
|
||||
|
||||
static function accessDaemon($token, $timeout = self::DAEMON_REQUEST_TIMEOUT) {
|
||||
$data = array('token' => $token);
|
||||
$url = Router::buildRequest(
|
||||
QueueEndpoint::ENDPOINT,
|
||||
QueueEndpoint::ACTION_RUN,
|
||||
CronDaemonEndpoint::ENDPOINT,
|
||||
CronDaemonEndpoint::ACTION_RUN,
|
||||
$data
|
||||
);
|
||||
$url = str_replace(home_url(), self::getSiteUrl(), $url);
|
||||
|
@ -2,6 +2,7 @@
|
||||
namespace MailPoet\Cron;
|
||||
use MailPoet\Cron\Workers\Scheduler as SchedulerWorker;
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
|
||||
use MailPoet\Cron\Workers\Bounce as BounceWorker;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
require_once(ABSPATH . 'wp-includes/pluggable.php');
|
||||
@ -19,6 +20,10 @@ class Daemon {
|
||||
$this->timer = microtime(true);
|
||||
}
|
||||
|
||||
function ping() {
|
||||
$this->terminateRequest('pong');
|
||||
}
|
||||
|
||||
function run() {
|
||||
ignore_user_abort(true);
|
||||
if(!$this->request_data) {
|
||||
@ -43,6 +48,7 @@ class Daemon {
|
||||
try {
|
||||
$this->executeScheduleWorker();
|
||||
$this->executeQueueWorker();
|
||||
$this->executeBounceWorker();
|
||||
} catch(\Exception $e) {
|
||||
// continue processing, no need to handle errors
|
||||
}
|
||||
@ -74,6 +80,11 @@ class Daemon {
|
||||
return $queue->process();
|
||||
}
|
||||
|
||||
function executeBounceWorker() {
|
||||
$bounce = new BounceWorker($this->timer);
|
||||
return $bounce->process();
|
||||
}
|
||||
|
||||
function callSelf() {
|
||||
CronHelper::accessDaemon($this->token, self::REQUEST_TIMEOUT);
|
||||
return $this->terminateRequest();
|
||||
@ -84,7 +95,7 @@ class Daemon {
|
||||
exit;
|
||||
}
|
||||
|
||||
function terminateRequest() {
|
||||
exit;
|
||||
function terminateRequest($message = false) {
|
||||
die($message);
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ namespace MailPoet\Cron\Triggers;
|
||||
use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\Cron\Workers\Scheduler as SchedulerWorker;
|
||||
use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker;
|
||||
use MailPoet\Cron\Workers\Bounce as BounceWorker;
|
||||
use MailPoet\Mailer\MailerLog;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
@ -19,7 +20,11 @@ class WordPress {
|
||||
$scheduled_queues = SchedulerWorker::getScheduledQueues();
|
||||
$running_queues = SendingQueueWorker::getRunningQueues();
|
||||
$sending_limit_reached = MailerLog::isSendingLimitReached();
|
||||
return (($scheduled_queues || $running_queues) && !$sending_limit_reached);
|
||||
$bounce_sync_available = BounceWorker::checkBounceSyncAvailable();
|
||||
$bounce_due_queues = BounceWorker::getAllDueQueues();
|
||||
$bounce_future_queues = BounceWorker::getFutureQueues();
|
||||
return (($scheduled_queues || $running_queues) && !$sending_limit_reached)
|
||||
|| ($bounce_sync_available && ($bounce_due_queues || !$bounce_future_queues));
|
||||
}
|
||||
|
||||
static function cleanup() {
|
||||
|
197
lib/Cron/Workers/Bounce.php
Normal file
197
lib/Cron/Workers/Bounce.php
Normal file
@ -0,0 +1,197 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron\Workers;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\Mailer\Mailer;
|
||||
use MailPoet\Models\SendingQueue;
|
||||
use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Util\Helpers;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class Bounce {
|
||||
const BOUNCED_HARD = 'hard';
|
||||
const BOUNCED_SOFT = 'soft';
|
||||
const NOT_BOUNCED = null;
|
||||
const BATCH_SIZE = 100;
|
||||
|
||||
public $timer;
|
||||
public $api;
|
||||
|
||||
function __construct($timer = false) {
|
||||
$this->timer = ($timer) ? $timer : microtime(true);
|
||||
// abort if execution limit is reached
|
||||
CronHelper::enforceExecutionLimit($this->timer);
|
||||
}
|
||||
|
||||
static function checkBounceSyncAvailable() {
|
||||
$mailer_config = Mailer::getMailerConfig();
|
||||
return !empty($mailer_config['method'])
|
||||
&& $mailer_config['method'] === Mailer::METHOD_MAILPOET;
|
||||
}
|
||||
|
||||
function initApi() {
|
||||
if(!$this->api) {
|
||||
$mailer_config = Mailer::getMailerConfig();
|
||||
$this->api = new Bounce\API($mailer_config['mailpoet_api_key']);
|
||||
}
|
||||
}
|
||||
|
||||
function process() {
|
||||
if(!self::checkBounceSyncAvailable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->initApi();
|
||||
|
||||
$scheduled_queues = self::getScheduledQueues();
|
||||
$running_queues = self::getRunningQueues();
|
||||
|
||||
if(!$scheduled_queues && !$running_queues) {
|
||||
self::scheduleBounceSync();
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach($scheduled_queues as $i => $queue) {
|
||||
$this->prepareBounceQueue($queue);
|
||||
}
|
||||
foreach($running_queues as $i => $queue) {
|
||||
$this->processBounceQueue($queue);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static function scheduleBounceSync() {
|
||||
$already_scheduled = SendingQueue::where('type', 'bounce')
|
||||
->whereNull('deleted_at')
|
||||
->where('status', SendingQueue::STATUS_SCHEDULED)
|
||||
->findMany();
|
||||
if($already_scheduled) {
|
||||
return false;
|
||||
}
|
||||
$queue = SendingQueue::create();
|
||||
$queue->type = 'bounce';
|
||||
$queue->status = SendingQueue::STATUS_SCHEDULED;
|
||||
$queue->priority = SendingQueue::PRIORITY_LOW;
|
||||
$queue->scheduled_at = self::getNextRunDate();
|
||||
$queue->newsletter_id = 0;
|
||||
$queue->save();
|
||||
return $queue;
|
||||
}
|
||||
|
||||
function prepareBounceQueue(SendingQueue $queue) {
|
||||
$subscribers = Subscriber::select('id')
|
||||
->whereNull('deleted_at')
|
||||
->whereIn('status', array(
|
||||
Subscriber::STATUS_SUBSCRIBED,
|
||||
Subscriber::STATUS_UNCONFIRMED
|
||||
))
|
||||
->findArray();
|
||||
$subscribers = Helpers::arrayColumn($subscribers, 'id');
|
||||
|
||||
if(empty($subscribers)) {
|
||||
$queue->delete();
|
||||
return false;
|
||||
}
|
||||
|
||||
// update current queue
|
||||
$queue->subscribers = serialize(
|
||||
array(
|
||||
'to_process' => $subscribers
|
||||
)
|
||||
);
|
||||
$queue->count_total = $queue->count_to_process = count($subscribers);
|
||||
$queue->status = null;
|
||||
$queue->save();
|
||||
|
||||
// abort if execution limit is reached
|
||||
CronHelper::enforceExecutionLimit($this->timer);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function processBounceQueue(SendingQueue $queue) {
|
||||
$queue->subscribers = $queue->getSubscribers();
|
||||
if(empty($queue->subscribers['to_process'])) {
|
||||
$queue->delete();
|
||||
return false;
|
||||
}
|
||||
|
||||
$subscriber_batches = array_chunk(
|
||||
$queue->subscribers['to_process'],
|
||||
self::BATCH_SIZE
|
||||
);
|
||||
|
||||
foreach($subscriber_batches as $subscribers_to_process_ids) {
|
||||
// abort if execution limit is reached
|
||||
CronHelper::enforceExecutionLimit($this->timer);
|
||||
|
||||
$subscriber_emails = Subscriber::select('email')
|
||||
->whereIn('id', $subscribers_to_process_ids)
|
||||
->whereNull('deleted_at')
|
||||
->findArray();
|
||||
$subscriber_emails = Helpers::arrayColumn($subscriber_emails, 'email');
|
||||
|
||||
$this->processEmails($subscriber_emails);
|
||||
|
||||
$queue->updateProcessedSubscribers($subscribers_to_process_ids);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function processEmails(array $subscriber_emails) {
|
||||
$checked_emails = $this->api->check($subscriber_emails);
|
||||
$this->processApiResponse((array)$checked_emails);
|
||||
}
|
||||
|
||||
function processApiResponse(array $checked_emails) {
|
||||
foreach($checked_emails as $email) {
|
||||
if(!isset($email['address'], $email['bounce'])) {
|
||||
continue;
|
||||
}
|
||||
if($email['bounce'] === self::BOUNCED_HARD) {
|
||||
$subscriber = Subscriber::findOne($email['address']);
|
||||
$subscriber->status = Subscriber::STATUS_BOUNCED;
|
||||
$subscriber->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function getNextRunDate() {
|
||||
$date = Carbon::createFromTimestamp(current_time('timestamp'));
|
||||
// Random day of the next week
|
||||
$date->setISODate($date->format('o'), $date->format('W') + 1, mt_rand(1, 7));
|
||||
$date->startOfDay();
|
||||
return $date;
|
||||
}
|
||||
|
||||
static function getScheduledQueues($future = false) {
|
||||
$dateWhere = ($future) ? 'whereGt' : 'whereLte';
|
||||
return SendingQueue::where('type', 'bounce')
|
||||
->$dateWhere('scheduled_at', Carbon::createFromTimestamp(current_time('timestamp')))
|
||||
->whereNull('deleted_at')
|
||||
->where('status', SendingQueue::STATUS_SCHEDULED)
|
||||
->findMany();
|
||||
}
|
||||
|
||||
static function getRunningQueues() {
|
||||
return SendingQueue::where('type', 'bounce')
|
||||
->whereLte('scheduled_at', Carbon::createFromTimestamp(current_time('timestamp')))
|
||||
->whereNull('deleted_at')
|
||||
->whereNull('status')
|
||||
->findMany();
|
||||
}
|
||||
|
||||
static function getAllDueQueues() {
|
||||
$scheduled_queues = self::getScheduledQueues();
|
||||
$running_queues = self::getRunningQueues();
|
||||
return array_merge((array)$scheduled_queues, (array)$running_queues);
|
||||
}
|
||||
|
||||
static function getFutureQueues() {
|
||||
return self::getScheduledQueues(true);
|
||||
}
|
||||
}
|
41
lib/Cron/Workers/Bounce/API.php
Normal file
41
lib/Cron/Workers/Bounce/API.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron\Workers\Bounce;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class API {
|
||||
public $url = 'https://bridge.mailpoet.com/api/v0/bounces/search';
|
||||
public $api_key;
|
||||
|
||||
function __construct($api_key) {
|
||||
$this->api_key = $api_key;
|
||||
}
|
||||
|
||||
function check(array $emails) {
|
||||
$result = wp_remote_post(
|
||||
$this->url,
|
||||
$this->request($emails)
|
||||
);
|
||||
if(wp_remote_retrieve_response_code($result) === 200) {
|
||||
return json_decode(wp_remote_retrieve_body($result), true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function auth() {
|
||||
return 'Basic ' . base64_encode('api:' . $this->api_key);
|
||||
}
|
||||
|
||||
private function request($body) {
|
||||
return array(
|
||||
'timeout' => 10,
|
||||
'httpversion' => '1.0',
|
||||
'method' => 'POST',
|
||||
'headers' => array(
|
||||
'Content-Type' => 'application/json',
|
||||
'Authorization' => $this->auth()
|
||||
),
|
||||
'body' => json_encode($body)
|
||||
);
|
||||
}
|
||||
}
|
@ -187,6 +187,7 @@ class Scheduler {
|
||||
static function getScheduledQueues() {
|
||||
return SendingQueue::where('status', 'scheduled')
|
||||
->whereLte('scheduled_at', Carbon::createFromTimestamp(current_time('timestamp')))
|
||||
->whereNull('type')
|
||||
->findMany();
|
||||
}
|
||||
}
|
@ -167,6 +167,7 @@ class SendingQueue {
|
||||
return SendingQueueModel::orderByAsc('priority')
|
||||
->whereNull('deleted_at')
|
||||
->whereNull('status')
|
||||
->whereNull('type')
|
||||
->findMany();
|
||||
}
|
||||
}
|
@ -29,11 +29,17 @@ class Select extends Base {
|
||||
);
|
||||
|
||||
foreach($options as $option) {
|
||||
if(!empty($option['is_hidden'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$is_selected = (
|
||||
(isset($option['is_checked']) && $option['is_checked'])
|
||||
||
|
||||
(self::getFieldValue($block) === $option['value'])
|
||||
) ? 'selected="selected"' : '';
|
||||
) ? ' selected="selected"' : '';
|
||||
|
||||
$is_disabled = (!empty($option['is_disabled'])) ? ' disabled="disabled"' : '';
|
||||
|
||||
if(is_array($option['value'])) {
|
||||
$value = key($option['value']);
|
||||
@ -43,7 +49,7 @@ class Select extends Base {
|
||||
$label = $option['value'];
|
||||
}
|
||||
|
||||
$html .= '<option value="'.$value.'" '.$is_selected.'>';
|
||||
$html .= '<option value="'.$value.'"' . $is_selected . $is_disabled . '>';
|
||||
$html .= esc_attr($label);
|
||||
$html .= '</option>';
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ class Renderer {
|
||||
&& strlen(trim($form['styles'])) > 0) {
|
||||
return strip_tags($form['styles']);
|
||||
} else {
|
||||
return Util\Styles::$defaults;
|
||||
return Util\Styles::$default_styles;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
<?php
|
||||
namespace MailPoet\Form\Util;
|
||||
|
||||
class Styles {
|
||||
private $_stylesheet = null;
|
||||
private $_styles = array();
|
||||
use Sabberworm\CSS\Parser as CSSParser;
|
||||
|
||||
static $defaults =<<<EOL
|
||||
class Styles {
|
||||
public $styles;
|
||||
static $default_styles = <<<EOL
|
||||
/* form */
|
||||
.mailpoet_form {
|
||||
|
||||
@ -49,130 +49,25 @@ class Styles {
|
||||
EOL;
|
||||
|
||||
function __construct($stylesheet = null) {
|
||||
// store raw styles
|
||||
$this->setStylesheet($stylesheet);
|
||||
|
||||
// extract rules/properties
|
||||
$this->parseStyles();
|
||||
|
||||
return $this;
|
||||
$this->stylesheet = $stylesheet;
|
||||
}
|
||||
|
||||
function render($prefix = '') {
|
||||
$styles = $this->getStyles();
|
||||
if(!empty($styles)) {
|
||||
$output = array();
|
||||
|
||||
// add prefix on each selector
|
||||
foreach($styles as $style) {
|
||||
// check if selector is an array
|
||||
if(is_array($style['selector'])) {
|
||||
$selector = join(",\n", array_map(function($value) use ($prefix) {
|
||||
return $prefix.' '.$value;
|
||||
}, $style['selector']));
|
||||
} else {
|
||||
$selector = $prefix.' '.$style['selector'];
|
||||
}
|
||||
|
||||
// format selector
|
||||
$output[] = $selector . ' {';
|
||||
|
||||
// format rules
|
||||
if(!empty($style['rules'])) {
|
||||
$rules = join("\n", array_map(function($rule) {
|
||||
return "\t".$rule['property'] . ': ' . $rule['value'].';';
|
||||
}, $style['rules']));
|
||||
|
||||
$output[] = $rules;
|
||||
}
|
||||
|
||||
$output[] = '}';
|
||||
}
|
||||
|
||||
return join("\n", $output);
|
||||
if(!$this->stylesheet) return;
|
||||
$styles = new CSSParser($this->stylesheet);
|
||||
$styles = $styles->parse();
|
||||
$formatted_styles = array();
|
||||
foreach($styles->getAllDeclarationBlocks() as $style_declaration) {
|
||||
$selectors = array_map(function($selector) use ($prefix) {
|
||||
return sprintf('%s %s', $prefix, $selector->__toString());
|
||||
}, $style_declaration->getSelectors());
|
||||
$selectors = implode(', ', $selectors);
|
||||
$rules = array_map(function($rule) {
|
||||
return $rule->__toString();
|
||||
}, $style_declaration->getRules());
|
||||
$rules = sprintf('{ %s }', implode(' ', $rules));
|
||||
$formatted_styles[] = sprintf('%s %s', $selectors, $rules);
|
||||
}
|
||||
return implode(PHP_EOL, $formatted_styles);
|
||||
}
|
||||
|
||||
private function setStylesheet($stylesheet) {
|
||||
$this->_stylesheet = $this->stripComments($stylesheet);
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function stripComments($stylesheet) {
|
||||
// remove comments
|
||||
return preg_replace('!/\*.*?\*/!s', '', $stylesheet);
|
||||
}
|
||||
|
||||
private function getStylesheet() {
|
||||
return $this->_stylesheet;
|
||||
}
|
||||
|
||||
private function setStyles($styles) {
|
||||
$this->_styles = $styles;
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function getStyles() {
|
||||
return $this->_styles;
|
||||
}
|
||||
|
||||
private function parseStyles() {
|
||||
if($this->getStylesheet() !== null) {
|
||||
// extract selectors and rules
|
||||
preg_match_all( '/(?ims)([a-z0-9\s\.\:#_\-@,]+)\{([^\}]*)\}/',
|
||||
$this->getStylesheet(),
|
||||
$matches
|
||||
);
|
||||
$selectors = $matches[1];
|
||||
$rules = $matches[2];
|
||||
|
||||
// extracted styles
|
||||
$styles = array();
|
||||
|
||||
// loop through each selector
|
||||
foreach($selectors as $index => $selector) {
|
||||
// trim selector
|
||||
$selector = trim($selector);
|
||||
// get selector rules
|
||||
$selector_rules = array_filter(array_map(function($value) {
|
||||
if(strlen(trim($value)) > 0) {
|
||||
// split property / value
|
||||
$pair = explode(':', trim($value));
|
||||
if(isset($pair[0]) && isset($pair[1])) {
|
||||
return array(
|
||||
'property' => $pair[0],
|
||||
'value' => $pair[1]
|
||||
);
|
||||
}
|
||||
}
|
||||
}, explode(';', trim($rules[$index]))));
|
||||
|
||||
// check if we have multiple selectors
|
||||
if(strpos($selector, ',') !== false) {
|
||||
$selectors_array = array_filter(array_map(function($value) {
|
||||
return trim($value);
|
||||
}, explode(',', $selector)));
|
||||
|
||||
// multiple selectors
|
||||
$styles[$index] = array(
|
||||
'selector' => $selectors_array,
|
||||
'rules' => $selector_rules
|
||||
);
|
||||
} else {
|
||||
// it's a single selector
|
||||
$styles[$index] = array(
|
||||
'selector' => $selector,
|
||||
'rules' => $selector_rules
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->setStyles($styles);
|
||||
}
|
||||
}
|
||||
|
||||
function __toString() {
|
||||
$this->stripComments();
|
||||
return $this->render();
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ class Mailer {
|
||||
public $mailer_config;
|
||||
public $sender;
|
||||
public $reply_to;
|
||||
public $return_path;
|
||||
public $mailer_instance;
|
||||
const MAILER_CONFIG_SETTING_NAME = 'mta';
|
||||
const SENDING_LIMIT_INTERVAL_MULTIPLIER = 60;
|
||||
@ -19,10 +20,11 @@ class Mailer {
|
||||
const METHOD_PHPMAIL = 'PHPMail';
|
||||
const METHOD_SMTP = 'SMTP';
|
||||
|
||||
function __construct($mailer = false, $sender = false, $reply_to = false) {
|
||||
function __construct($mailer = false, $sender = false, $reply_to = false, $return_path = false) {
|
||||
$this->mailer_config = self::getMailerConfig($mailer);
|
||||
$this->sender = $this->getSenderNameAndAddress($sender);
|
||||
$this->reply_to = $this->getReplyToNameAndAddress($reply_to);
|
||||
$this->return_path = $this->getReturnPathAddress($return_path);
|
||||
$this->mailer_instance = $this->buildMailer();
|
||||
}
|
||||
|
||||
@ -39,7 +41,8 @@ class Mailer {
|
||||
$this->mailer_config['access_key'],
|
||||
$this->mailer_config['secret_key'],
|
||||
$this->sender,
|
||||
$this->reply_to
|
||||
$this->reply_to,
|
||||
$this->return_path
|
||||
);
|
||||
break;
|
||||
case self::METHOD_MAILPOET:
|
||||
@ -59,7 +62,8 @@ class Mailer {
|
||||
case self::METHOD_PHPMAIL:
|
||||
$mailer_instance = new $this->mailer_config['class'](
|
||||
$this->sender,
|
||||
$this->reply_to
|
||||
$this->reply_to,
|
||||
$this->return_path
|
||||
);
|
||||
break;
|
||||
case self::METHOD_SMTP:
|
||||
@ -71,7 +75,8 @@ class Mailer {
|
||||
$this->mailer_config['password'],
|
||||
$this->mailer_config['encryption'],
|
||||
$this->sender,
|
||||
$this->reply_to
|
||||
$this->reply_to,
|
||||
$this->return_path
|
||||
);
|
||||
break;
|
||||
default:
|
||||
@ -112,7 +117,7 @@ class Mailer {
|
||||
|
||||
function getReplyToNameAndAddress($reply_to = array()) {
|
||||
if(!$reply_to) {
|
||||
$reply_to = Setting::getValue('reply_to', null);
|
||||
$reply_to = Setting::getValue('reply_to');
|
||||
$reply_to['name'] = (!empty($reply_to['name'])) ?
|
||||
$reply_to['name'] :
|
||||
$this->sender['from_name'];
|
||||
@ -131,6 +136,12 @@ class Mailer {
|
||||
);
|
||||
}
|
||||
|
||||
function getReturnPathAddress($return_path) {
|
||||
return ($return_path) ?
|
||||
$return_path :
|
||||
Setting::getValue('bounce.address');
|
||||
}
|
||||
|
||||
function formatSubscriberNameAndEmailAddress($subscriber) {
|
||||
$subscriber = (is_object($subscriber)) ? $subscriber->asArray() : $subscriber;
|
||||
if(!is_array($subscriber)) return $subscriber;
|
||||
|
@ -17,6 +17,7 @@ class AmazonSES {
|
||||
public $url;
|
||||
public $sender;
|
||||
public $reply_to;
|
||||
public $return_path;
|
||||
public $date;
|
||||
public $date_without_time;
|
||||
private $available_regions = array(
|
||||
@ -25,7 +26,7 @@ class AmazonSES {
|
||||
'EU (Ireland)' => 'eu-west-1'
|
||||
);
|
||||
|
||||
function __construct($region, $access_key, $secret_key, $sender, $reply_to) {
|
||||
function __construct($region, $access_key, $secret_key, $sender, $reply_to, $return_path) {
|
||||
$this->aws_access_key = $access_key;
|
||||
$this->aws_secret_key = $secret_key;
|
||||
$this->aws_region = (in_array($region, $this->available_regions)) ? $region : false;
|
||||
@ -40,11 +41,13 @@ class AmazonSES {
|
||||
$this->url = 'https://' . $this->aws_endpoint;
|
||||
$this->sender = $sender;
|
||||
$this->reply_to = $reply_to;
|
||||
$this->return_path = ($return_path) ?
|
||||
$return_path :
|
||||
$this->sender['from_email'];
|
||||
$this->date = gmdate('Ymd\THis\Z');
|
||||
$this->date_without_time = gmdate('Ymd');
|
||||
}
|
||||
|
||||
|
||||
function send($newsletter, $subscriber) {
|
||||
$result = wp_remote_post(
|
||||
$this->url,
|
||||
@ -71,7 +74,7 @@ class AmazonSES {
|
||||
'Source' => $this->sender['from_name_email'],
|
||||
'ReplyToAddresses.member.1' => $this->reply_to['reply_to_name_email'],
|
||||
'Message.Subject.Data' => $newsletter['subject'],
|
||||
'ReturnPath' => $this->sender['from_name_email'],
|
||||
'ReturnPath' => $this->return_path
|
||||
);
|
||||
if(!empty($newsletter['body']['html'])) {
|
||||
$body['Message.Body.Html.Data'] = $newsletter['body']['html'];
|
||||
|
@ -8,11 +8,15 @@ if(!defined('ABSPATH')) exit;
|
||||
class PHPMail {
|
||||
public $sender;
|
||||
public $reply_to;
|
||||
public $return_path;
|
||||
public $mailer;
|
||||
|
||||
function __construct($sender, $reply_to) {
|
||||
function __construct($sender, $reply_to, $return_path) {
|
||||
$this->sender = $sender;
|
||||
$this->reply_to = $reply_to;
|
||||
$this->return_path = ($return_path) ?
|
||||
$return_path :
|
||||
$this->sender['from_email'];
|
||||
$this->mailer = $this->buildMailer();
|
||||
}
|
||||
|
||||
@ -41,9 +45,11 @@ class PHPMail {
|
||||
->setFrom(array(
|
||||
$this->sender['from_email'] => $this->sender['from_name']
|
||||
))
|
||||
->setSender($this->sender['from_email'])
|
||||
->setReplyTo(array(
|
||||
$this->reply_to['reply_to_email'] => $this->reply_to['reply_to_name']
|
||||
))
|
||||
->setReturnPath($this->return_path)
|
||||
->setSubject($newsletter['subject']);
|
||||
if(!empty($newsletter['body']['html'])) {
|
||||
$message = $message->setBody($newsletter['body']['html'], 'text/html');
|
||||
|
@ -14,11 +14,12 @@ class SMTP {
|
||||
public $encryption;
|
||||
public $sender;
|
||||
public $reply_to;
|
||||
public $return_path;
|
||||
public $mailer;
|
||||
|
||||
function __construct(
|
||||
$host, $port, $authentication, $login = null, $password = null, $encryption,
|
||||
$sender, $reply_to) {
|
||||
$sender, $reply_to, $return_path) {
|
||||
$this->host = $host;
|
||||
$this->port = $port;
|
||||
$this->authentication = $authentication;
|
||||
@ -27,6 +28,9 @@ class SMTP {
|
||||
$this->encryption = $encryption;
|
||||
$this->sender = $sender;
|
||||
$this->reply_to = $reply_to;
|
||||
$this->return_path = ($return_path) ?
|
||||
$return_path :
|
||||
$this->sender['from_email'];
|
||||
$this->mailer = $this->buildMailer();
|
||||
}
|
||||
|
||||
@ -62,9 +66,11 @@ class SMTP {
|
||||
->setFrom(array(
|
||||
$this->sender['from_email'] => $this->sender['from_name']
|
||||
))
|
||||
->setSender($this->sender['from_email'])
|
||||
->setReplyTo(array(
|
||||
$this->reply_to['reply_to_email'] => $this->reply_to['reply_to_name']
|
||||
))
|
||||
->setReturnPath($this->return_path)
|
||||
->setSubject($newsletter['subject']);
|
||||
if(!empty($newsletter['body']['html'])) {
|
||||
$message = $message->setBody($newsletter['body']['html'], 'text/html');
|
||||
|
@ -2,6 +2,7 @@
|
||||
namespace MailPoet\Models;
|
||||
use MailPoet\Newsletter\Renderer\Renderer;
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoet\Util\Security;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
@ -11,7 +12,6 @@ class Newsletter extends Model {
|
||||
const TYPE_WELCOME = 'welcome';
|
||||
const TYPE_NOTIFICATION = 'notification';
|
||||
const TYPE_NOTIFICATION_HISTORY = 'notification_history';
|
||||
|
||||
// standard newsletters
|
||||
const STATUS_DRAFT = 'draft';
|
||||
const STATUS_SCHEDULED = 'scheduled';
|
||||
@ -19,6 +19,7 @@ class Newsletter extends Model {
|
||||
const STATUS_SENT = 'sent';
|
||||
// automatic newsletters status
|
||||
const STATUS_ACTIVE = 'active';
|
||||
const NEWSLETTER_HASH_LENGTH = 6;
|
||||
|
||||
function __construct() {
|
||||
parent::__construct();
|
||||
@ -27,6 +28,10 @@ class Newsletter extends Model {
|
||||
));
|
||||
}
|
||||
|
||||
function queue() {
|
||||
return $this->has_one(__NAMESPACE__ . '\SendingQueue', 'newsletter_id', 'id');
|
||||
}
|
||||
|
||||
function save() {
|
||||
if(is_string($this->deleted_at) && strlen(trim($this->deleted_at)) === 0) {
|
||||
$this->set_expr('deleted_at', 'NULL');
|
||||
@ -37,6 +42,12 @@ class Newsletter extends Model {
|
||||
? json_encode($this->body)
|
||||
: $this->body
|
||||
);
|
||||
|
||||
$this->set('hash',
|
||||
($this->hash)
|
||||
? $this->hash
|
||||
: self::generateHash()
|
||||
);
|
||||
return parent::save();
|
||||
}
|
||||
|
||||
@ -74,6 +85,9 @@ class Newsletter extends Model {
|
||||
// reset status
|
||||
$duplicate->set('status', self::STATUS_DRAFT);
|
||||
|
||||
// reset hash
|
||||
$duplicate->set('hash', null);
|
||||
|
||||
$duplicate->save();
|
||||
|
||||
if($duplicate->getErrors() === false) {
|
||||
@ -130,6 +144,9 @@ class Newsletter extends Model {
|
||||
$notification_history->set_expr('updated_at', 'NOW()');
|
||||
$notification_history->set_expr('deleted_at', 'NULL');
|
||||
|
||||
// reset hash
|
||||
$notification_history->set('hash', null);
|
||||
|
||||
$notification_history->save();
|
||||
|
||||
if($notification_history->getErrors() === false) {
|
||||
@ -182,8 +199,39 @@ class Newsletter extends Model {
|
||||
);
|
||||
}
|
||||
|
||||
function withSegments() {
|
||||
function withSegments($incl_deleted = false) {
|
||||
$this->segments = $this->segments()->findArray();
|
||||
if($incl_deleted) {
|
||||
$this->withDeletedSegments();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Intermediary table only
|
||||
function segmentLinks() {
|
||||
return $this->has_many(
|
||||
__NAMESPACE__.'\NewsletterSegment',
|
||||
'newsletter_id',
|
||||
'id'
|
||||
);
|
||||
}
|
||||
|
||||
function withDeletedSegments() {
|
||||
if(!empty($this->segments)) {
|
||||
$segment_ids = Helpers::arrayColumn($this->segments, 'id');
|
||||
$links = $this->segmentLinks()
|
||||
->whereNotIn('segment_id', $segment_ids)->findArray();
|
||||
$deleted_segments = array();
|
||||
|
||||
foreach($links as $link) {
|
||||
$deleted_segments[] = array(
|
||||
'id' => $link['segment_id'],
|
||||
'name' => __('Deleted list', 'mailpoet')
|
||||
);
|
||||
}
|
||||
$this->segments = array_merge($this->segments, $deleted_segments);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -201,14 +249,23 @@ class Newsletter extends Model {
|
||||
)->select_expr(MP_NEWSLETTER_OPTION_TABLE.'.value');
|
||||
}
|
||||
|
||||
function getQueue() {
|
||||
return SendingQueue::where('newsletter_id', $this->id)
|
||||
function getQueue($columns = '*') {
|
||||
return SendingQueue::select($columns)
|
||||
->where('newsletter_id', $this->id)
|
||||
->orderByDesc('updated_at')
|
||||
->findOne();
|
||||
}
|
||||
|
||||
function withSendingQueue() {
|
||||
$queue = $this->getQueue();
|
||||
$queue = $this->getQueue(array(
|
||||
'id',
|
||||
'newsletter_id',
|
||||
'newsletter_rendered_subject',
|
||||
'status',
|
||||
'count_processed',
|
||||
'count_total',
|
||||
'scheduled_at'
|
||||
));
|
||||
if($queue === false) {
|
||||
$this->queue = false;
|
||||
} else {
|
||||
@ -237,11 +294,7 @@ class Newsletter extends Model {
|
||||
|
||||
function withStatistics() {
|
||||
$statistics = $this->getStatistics();
|
||||
if($statistics === false) {
|
||||
$this->statistics = false;
|
||||
} else {
|
||||
$this->statistics = $statistics->asArray();
|
||||
}
|
||||
$this->statistics = $statistics;
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -251,42 +304,42 @@ class Newsletter extends Model {
|
||||
}
|
||||
|
||||
function getStatistics() {
|
||||
$statistics_query = SendingQueue::tableAlias('queues')
|
||||
->selectExpr(
|
||||
'COUNT(DISTINCT(clicks.subscriber_id)) as clicked, ' .
|
||||
'COUNT(DISTINCT(opens.subscriber_id)) as opened, ' .
|
||||
'COUNT(DISTINCT(unsubscribes.subscriber_id)) as unsubscribed '
|
||||
)
|
||||
->leftOuterJoin(
|
||||
MP_STATISTICS_CLICKS_TABLE,
|
||||
'queues.id = clicks.queue_id',
|
||||
'clicks'
|
||||
)
|
||||
->leftOuterJoin(
|
||||
MP_STATISTICS_OPENS_TABLE,
|
||||
'queues.id = opens.queue_id',
|
||||
'opens'
|
||||
)
|
||||
->leftOuterJoin(
|
||||
MP_STATISTICS_UNSUBSCRIBES_TABLE,
|
||||
'queues.id = unsubscribes.queue_id',
|
||||
'unsubscribes'
|
||||
);
|
||||
|
||||
if($this->type === self::TYPE_WELCOME) {
|
||||
return $statistics_query
|
||||
->where('queues.newsletter_id', $this->id)
|
||||
->where('queues.status', SendingQueue::STATUS_COMPLETED)
|
||||
->findOne();
|
||||
$where = '`queue_id` IN (SELECT `id` FROM ' . MP_SENDING_QUEUES_TABLE . '
|
||||
WHERE `newsletter_id` = ? AND `status` = ?)';
|
||||
$where_params = array($this->id, SendingQueue::STATUS_COMPLETED);
|
||||
} else {
|
||||
if($this->queue === false) {
|
||||
return false;
|
||||
} else {
|
||||
return $statistics_query
|
||||
->where('queues.id', $this->queue['id'])
|
||||
->findOne();
|
||||
$where = '`queue_id` = ?';
|
||||
$where_params = array($this->queue['id']);
|
||||
}
|
||||
}
|
||||
|
||||
$clicks = StatisticsClicks::selectExpr(
|
||||
'COUNT(DISTINCT subscriber_id) as cnt'
|
||||
)
|
||||
->whereRaw($where, $where_params)
|
||||
->findOne();
|
||||
|
||||
$opens = StatisticsOpens::selectExpr(
|
||||
'COUNT(DISTINCT subscriber_id) as cnt'
|
||||
)
|
||||
->whereRaw($where, $where_params)
|
||||
->findOne();
|
||||
|
||||
$unsubscribes = StatisticsUnsubscribes::selectExpr(
|
||||
'COUNT(DISTINCT subscriber_id) as cnt'
|
||||
)
|
||||
->whereRaw($where, $where_params)
|
||||
->findOne();
|
||||
|
||||
return array(
|
||||
'clicked' => !empty($clicks->cnt) ? $clicks->cnt : 0,
|
||||
'opened' => !empty($opens->cnt) ? $opens->cnt : 0,
|
||||
'unsubscribed' => !empty($unsubscribes->cnt) ? $unsubscribes->cnt : 0
|
||||
);
|
||||
}
|
||||
|
||||
static function search($orm, $search = '') {
|
||||
@ -634,6 +687,7 @@ class Newsletter extends Model {
|
||||
'queues'
|
||||
)
|
||||
->where('queues.status', SendingQueue::STATUS_COMPLETED)
|
||||
->whereNull('newsletters.deleted_at')
|
||||
->select('queues.processed_at')
|
||||
->orderByDesc('queues.processed_at');
|
||||
|
||||
@ -647,4 +701,17 @@ class Newsletter extends Model {
|
||||
}
|
||||
return $orm->findMany();
|
||||
}
|
||||
}
|
||||
|
||||
static function getByHash($hash) {
|
||||
return parent::where('hash', $hash)
|
||||
->findOne();
|
||||
}
|
||||
|
||||
static function generateHash() {
|
||||
return substr(
|
||||
md5(AUTH_KEY . Security::generateRandomString(15)),
|
||||
0,
|
||||
self::NEWSLETTER_HASH_LENGTH
|
||||
);
|
||||
}
|
||||
}
|
@ -70,10 +70,13 @@ class SendingQueue extends Model {
|
||||
return $subscribers;
|
||||
}
|
||||
|
||||
function getNewsletterRenderedBody() {
|
||||
return (!is_serialized($this->newsletter_rendered_body)) ?
|
||||
function getNewsletterRenderedBody($type = false) {
|
||||
$rendered_newsletter = (!is_serialized($this->newsletter_rendered_body)) ?
|
||||
$this->newsletter_rendered_body :
|
||||
unserialize($this->newsletter_rendered_body);
|
||||
return ($type && !empty($rendered_newsletter[$type])) ?
|
||||
$rendered_newsletter[$type] :
|
||||
$rendered_newsletter;
|
||||
}
|
||||
|
||||
function isSubscriberProcessed($subscriber_id) {
|
||||
|
@ -14,8 +14,8 @@ class Subscriber extends Model {
|
||||
const STATUS_UNSUBSCRIBED = 'unsubscribed';
|
||||
const STATUS_UNCONFIRMED = 'unconfirmed';
|
||||
const STATUS_BOUNCED = 'bounced';
|
||||
|
||||
const SUBSCRIPTION_LIMIT_COOLDOWN = 60;
|
||||
const SUBSCRIBER_TOKEN_LENGTH = 6;
|
||||
|
||||
function __construct() {
|
||||
parent::__construct();
|
||||
@ -154,33 +154,23 @@ class Subscriber extends Model {
|
||||
|
||||
static function generateToken($email = null) {
|
||||
if($email !== null) {
|
||||
return md5(AUTH_KEY.$email);
|
||||
return substr(md5(AUTH_KEY . $email), 0, self::SUBSCRIBER_TOKEN_LENGTH);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static function verifyToken($email, $token) {
|
||||
return call_user_func('hash_equals', self::generateToken($email), $token);
|
||||
return call_user_func(
|
||||
'hash_equals',
|
||||
self::generateToken($email),
|
||||
substr($token, 0, self::SUBSCRIBER_TOKEN_LENGTH)
|
||||
);
|
||||
}
|
||||
|
||||
static function subscribe($subscriber_data = array(), $segment_ids = array()) {
|
||||
// filter out keys from the subscriber_data array
|
||||
// that should not be editable when subscribing
|
||||
$reserved_columns = array(
|
||||
'id',
|
||||
'wp_user_id',
|
||||
'status',
|
||||
'subscribed_ip',
|
||||
'confirmed_ip',
|
||||
'confirmed_at',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at'
|
||||
);
|
||||
$subscriber_data = array_diff_key(
|
||||
$subscriber_data,
|
||||
array_flip($reserved_columns)
|
||||
);
|
||||
$subscriber_data = self::filterOutReservedColumns($subscriber_data);
|
||||
|
||||
$signup_confirmation_enabled = (bool)Setting::getValue(
|
||||
'signup_confirmation.enabled'
|
||||
@ -205,14 +195,17 @@ class Subscriber extends Model {
|
||||
|
||||
$subscriber = self::findOne($subscriber_data['email']);
|
||||
|
||||
if($subscriber === false) {
|
||||
// create new subscriber
|
||||
if($subscriber === false || !$signup_confirmation_enabled) {
|
||||
// create new subscriber or update if no confirmation is required
|
||||
$subscriber = self::createOrUpdate($subscriber_data);
|
||||
if($subscriber->getErrors() !== false) {
|
||||
return $subscriber;
|
||||
}
|
||||
|
||||
$subscriber = self::findOne($subscriber->id);
|
||||
} else {
|
||||
// store subscriber data to be updated after confirmation
|
||||
$subscriber->setUnconfirmedData($subscriber_data);
|
||||
}
|
||||
|
||||
// restore trashed subscriber
|
||||
@ -246,6 +239,26 @@ class Subscriber extends Model {
|
||||
return $subscriber;
|
||||
}
|
||||
|
||||
static function filterOutReservedColumns(array $subscriber_data) {
|
||||
$reserved_columns = array(
|
||||
'id',
|
||||
'wp_user_id',
|
||||
'status',
|
||||
'subscribed_ip',
|
||||
'confirmed_ip',
|
||||
'confirmed_at',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
'unconfirmed_data'
|
||||
);
|
||||
$subscriber_data = array_diff_key(
|
||||
$subscriber_data,
|
||||
array_flip($reserved_columns)
|
||||
);
|
||||
return $subscriber_data;
|
||||
}
|
||||
|
||||
static function search($orm, $search = '') {
|
||||
if(strlen(trim($search) === 0)) {
|
||||
return $orm;
|
||||
@ -481,6 +494,9 @@ class Subscriber extends Model {
|
||||
}
|
||||
}
|
||||
|
||||
// wipe any unconfirmed data at this point
|
||||
$data['unconfirmed_data'] = null;
|
||||
|
||||
$old_status = false;
|
||||
$new_status = false;
|
||||
|
||||
@ -583,6 +599,20 @@ class Subscriber extends Model {
|
||||
));
|
||||
}
|
||||
|
||||
function setUnconfirmedData(array $subscriber_data) {
|
||||
$subscriber_data = self::filterOutReservedColumns($subscriber_data);
|
||||
$this->unconfirmed_data = json_encode($subscriber_data);
|
||||
}
|
||||
|
||||
function getUnconfirmedData() {
|
||||
if(!empty($this->unconfirmed_data)) {
|
||||
$subscriber_data = json_decode($this->unconfirmed_data, true);
|
||||
$subscriber_data = self::filterOutReservedColumns((array)$subscriber_data);
|
||||
return $subscriber_data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static function bulkAddToList($orm, $data = array()) {
|
||||
$segment_id = (isset($data['segment_id']) ? (int)$data['segment_id'] : 0);
|
||||
$segment = Segment::findOne($segment_id);
|
||||
@ -808,6 +838,7 @@ class Subscriber extends Model {
|
||||
'UPDATE `' . self::$_table . '` ' .
|
||||
'SET ' . implode(', ', $sql('statement')) . ' '.
|
||||
(($updated_at) ? ', updated_at = "' . $updated_at . '" ' : '') .
|
||||
', unconfirmed_data = NULL ' .
|
||||
'WHERE email IN ' .
|
||||
'(' . rtrim(str_repeat('?,', count($subscribers)), ',') . ')',
|
||||
array_merge(
|
||||
|
@ -6,6 +6,7 @@ if(!defined('ABSPATH')) exit;
|
||||
class PostContentManager {
|
||||
|
||||
const MAX_EXCERPT_LENGTH = 60;
|
||||
const WP_POST_CLASS = 'mailpoet_wp_post';
|
||||
|
||||
function getContent($post, $displayType) {
|
||||
if($displayType === 'titleOnly') {
|
||||
@ -42,7 +43,7 @@ class PostContentManager {
|
||||
'<h2>', '<h3>', '<a>', '<ul>', '<ol>', '<li>', '<br>'
|
||||
);
|
||||
$content = strip_tags($content, implode('', $tags_not_being_stripped));
|
||||
$content = wpautop($content);
|
||||
$content = str_replace('<p', '<p class="' . self::WP_POST_CLASS .'"', wpautop($content));
|
||||
|
||||
return $content;
|
||||
}
|
||||
@ -91,4 +92,4 @@ class PostContentManager {
|
||||
return $content;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -169,6 +169,6 @@ class PostTransformer {
|
||||
|
||||
$alignment = (in_array($this->args['titleAlignment'], array('left', 'right', 'center'))) ? $this->args['titleAlignment'] : 'left';
|
||||
|
||||
return '<' . $tag . ' data-post-id="' . $post->ID . '" style="text-align: ' . $alignment . '">' . $title . '</' . $tag . '>';
|
||||
return '<' . $tag . ' data-post-id="' . $post->ID . '" style="text-align: ' . $alignment . ';">' . $title . '</' . $tag . '>';
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ class StructureTransformer {
|
||||
} else {
|
||||
return array(
|
||||
'type' => 'text',
|
||||
'text' => $item->toString(),
|
||||
'text' => $item->toString()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -26,9 +26,9 @@ class TitleListTransformer {
|
||||
$alignment = (in_array($alignment, array('left', 'right', 'center'))) ? $alignment : 'left';
|
||||
|
||||
if($this->args['titleIsLink']) {
|
||||
$title = '<a href="' . get_permalink($post->ID) . '">' . $title . '</a>';
|
||||
$title = '<a data-post-id="' . $post->ID . '" href="' . get_permalink($post->ID) . '">' . $title . '</a>';
|
||||
}
|
||||
|
||||
return '<li style="text-align: ' . $alignment . '">' . $title . '</li>';
|
||||
return '<li style="text-align: ' . $alignment . ';">' . $title . '</li>';
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use MailPoet\Router\Router;
|
||||
use MailPoet\Router\Endpoints\Track as TrackEndpoint;
|
||||
use MailPoet\Models\NewsletterLink;
|
||||
use MailPoet\Newsletter\Shortcodes\Shortcodes;
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoet\Util\Security;
|
||||
|
||||
class Links {
|
||||
@ -101,24 +102,19 @@ class Links {
|
||||
$preview = false
|
||||
) {
|
||||
// match data tags
|
||||
$regex = sprintf(
|
||||
'/((%s|%s)(?:-\w+)?)/',
|
||||
preg_quote(self::DATA_TAG_CLICK),
|
||||
preg_quote(self::DATA_TAG_OPEN)
|
||||
);
|
||||
$subscriber = Subscriber::findOne($subscriber_id);
|
||||
preg_match_all($regex, $content, $matches);
|
||||
preg_match_all(self::getLinkRegex(), $content, $matches);
|
||||
foreach($matches[1] as $index => $match) {
|
||||
$hash = null;
|
||||
if(preg_match('/-/', $match)) {
|
||||
list(, $hash) = explode('-', $match);
|
||||
}
|
||||
$data = array(
|
||||
'subscriber_id' => $subscriber->id,
|
||||
'subscriber_token' => Subscriber::generateToken($subscriber->email),
|
||||
'queue_id' => $queue_id,
|
||||
'link_hash' => $hash,
|
||||
'preview' => $preview
|
||||
$data = self::createUrlDataObject(
|
||||
$subscriber->id,
|
||||
$subscriber->email,
|
||||
$queue_id,
|
||||
$hash,
|
||||
$preview
|
||||
);
|
||||
$router_action = ($matches[2][$index] === self::DATA_TAG_CLICK) ?
|
||||
TrackEndpoint::ACTION_CLICK :
|
||||
@ -144,4 +140,54 @@ class Links {
|
||||
$newsletter_link->save();
|
||||
}
|
||||
}
|
||||
|
||||
static function convertHashedLinksToShortcodesAndUrls($content, $convert_all = false) {
|
||||
preg_match_all(self::getLinkRegex(), $content, $links);
|
||||
$links = array_unique(Helpers::flattenArray($links));
|
||||
foreach($links as $link) {
|
||||
$link_hash = explode('-', $link);
|
||||
if(!isset($link_hash[1])) continue;
|
||||
$newsletter_link = NewsletterLink::getByHash($link_hash[1]);
|
||||
// convert either only link shortcodes or all hashes links if "convert all"
|
||||
// option is specified
|
||||
if($newsletter_link &&
|
||||
(preg_match('/\[link:/', $newsletter_link->url) || $convert_all)
|
||||
) {
|
||||
$content = str_replace($link, $newsletter_link->url, $content);
|
||||
}
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
static function getLinkRegex() {
|
||||
return sprintf(
|
||||
'/((%s|%s)(?:-\w+)?)/',
|
||||
preg_quote(self::DATA_TAG_CLICK),
|
||||
preg_quote(self::DATA_TAG_OPEN)
|
||||
);
|
||||
}
|
||||
|
||||
static function createUrlDataObject(
|
||||
$subscriber_id, $subscriber_email, $queue_id, $link_hash, $preview
|
||||
) {
|
||||
return array(
|
||||
$subscriber_id,
|
||||
Subscriber::generateToken($subscriber_email),
|
||||
$queue_id,
|
||||
$link_hash,
|
||||
$preview
|
||||
);
|
||||
}
|
||||
|
||||
static function transformUrlDataObject($data) {
|
||||
reset($data);
|
||||
if(!is_int(key($data))) return $data;
|
||||
$transformed_data = array();
|
||||
$transformed_data['subscriber_id'] = (!empty($data[0])) ? $data[0] : false;
|
||||
$transformed_data['subscriber_token'] = (!empty($data[1])) ? $data[1] : false;
|
||||
$transformed_data['queue_id'] = (!empty($data[2])) ? $data[2] : false;
|
||||
$transformed_data['link_hash'] = (!empty($data[3])) ? $data[3] : false;
|
||||
$transformed_data['preview'] = (!empty($data[4])) ? $data[4] : false;
|
||||
return $transformed_data;
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace MailPoet\Newsletter\Renderer\Blocks;
|
||||
|
||||
use MailPoet\Newsletter\Editor\PostContentManager;
|
||||
use MailPoet\Newsletter\Renderer\StylesHelper;
|
||||
|
||||
class Text {
|
||||
@ -27,16 +28,19 @@ class Text {
|
||||
$DOM_parser = new \pQuery();
|
||||
$DOM = $DOM_parser->parseStr($html);
|
||||
$blockquotes = $DOM->query('blockquote');
|
||||
if(!$blockquotes->count()) return $html;
|
||||
foreach($blockquotes as $blockquote) {
|
||||
$contents = array();
|
||||
$paragraphs = $blockquote->query('p', 0);
|
||||
$paragraphs = $blockquote->query('p, h1, h2, h3, h4', 0);
|
||||
foreach($paragraphs as $index => $paragraph) {
|
||||
$contents[] = $paragraph->html();
|
||||
if($index + 1 < $paragraphs->count()) $contents[] = '<br />';
|
||||
$paragraph->remove();
|
||||
if(preg_match('/h\d/', $paragraph->getTag())) {
|
||||
$contents[] = $paragraph->getOuterText();
|
||||
} else {
|
||||
$contents[] = $paragraph->html();
|
||||
}
|
||||
if($index + 1 < $paragraphs->count()) $contents[] = '<br />';
|
||||
$paragraph->remove();
|
||||
}
|
||||
$paragraph->remove();
|
||||
if(empty($contents)) continue;
|
||||
$blockquote->setTag('table');
|
||||
$blockquote->addClass('mailpoet_blockquote');
|
||||
$blockquote->width = '100%';
|
||||
@ -49,7 +53,7 @@ class Text {
|
||||
<td width="2" bgcolor="#565656"></td>
|
||||
<td width="10"></td>
|
||||
<td valign="top">
|
||||
<table style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0">
|
||||
<table width="100%" border="0" cellpadding="0" cellspacing="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0">
|
||||
<tr>
|
||||
<td class="mailpoet_blockquote">
|
||||
' . implode('', $contents) . '
|
||||
@ -108,9 +112,16 @@ class Text {
|
||||
'<br /><br />' :
|
||||
'';
|
||||
// if this element is followed by a list, add single line break
|
||||
$line_breaks = ($next_element && preg_match('/<li>/i', $next_element->text())) ?
|
||||
$line_breaks = ($next_element && preg_match('/<li/i', $next_element->getOuterText())) ?
|
||||
'<br />' :
|
||||
$line_breaks;
|
||||
if($paragraph->hasClass(PostContentManager::WP_POST_CLASS)) {
|
||||
$paragraph->removeClass(PostContentManager::WP_POST_CLASS);
|
||||
// if this element is followed by a paragraph, add double line breaks
|
||||
$line_breaks = ($next_element && preg_match('/<p/i', $next_element->getOuterText())) ?
|
||||
'<br /><br />' :
|
||||
$line_breaks;
|
||||
}
|
||||
$paragraph->html('
|
||||
<tr>
|
||||
<td class="mailpoet_paragraph" style="word-break:break-word;word-wrap:break-word;' . $style . '">
|
||||
|
@ -21,7 +21,7 @@ class OpenTracking {
|
||||
}
|
||||
|
||||
static function addTrackingImage() {
|
||||
add_filter(Renderer::POST_PROCESS_FILTER, function ($template) {
|
||||
add_filter(Renderer::FILTER_POST_PROCESS, function ($template) {
|
||||
return OpenTracking::process($template);
|
||||
});
|
||||
return true;
|
||||
|
@ -11,7 +11,7 @@ class Renderer {
|
||||
public $newsletter;
|
||||
public $preview;
|
||||
const NEWSLETTER_TEMPLATE = 'Template.html';
|
||||
const POST_PROCESS_FILTER = 'mailpoet_rendering_post_process';
|
||||
const FILTER_POST_PROCESS = 'mailpoet_rendering_post_process';
|
||||
|
||||
function __construct($newsletter, $preview = false) {
|
||||
// TODO: remove ternary condition, refactor to use model objects
|
||||
@ -24,7 +24,7 @@ class Renderer {
|
||||
$this->template = file_get_contents(dirname(__FILE__) . '/' . self::NEWSLETTER_TEMPLATE);
|
||||
}
|
||||
|
||||
function render() {
|
||||
function render($type = false) {
|
||||
$newsletter = $this->newsletter;
|
||||
$body = (is_array($newsletter['body']))
|
||||
? $newsletter['body']
|
||||
@ -48,10 +48,14 @@ class Renderer {
|
||||
$template = $this->inlineCSSStyles($template);
|
||||
$template = $this->postProcessTemplate($template);
|
||||
|
||||
return array(
|
||||
$rendered_newsletter = array(
|
||||
'html' => $template,
|
||||
'text' => $this->renderTextVersion($template)
|
||||
);
|
||||
|
||||
return ($type && !empty($rendered_newsletter[$type])) ?
|
||||
$rendered_newsletter[$type] :
|
||||
$rendered_newsletter;
|
||||
}
|
||||
|
||||
function renderBody($content) {
|
||||
@ -124,7 +128,7 @@ class Renderer {
|
||||
str_replace('&', '&', $template->html())
|
||||
);
|
||||
$template = apply_filters(
|
||||
self::POST_PROCESS_FILTER,
|
||||
self::FILTER_POST_PROCESS,
|
||||
$DOM->__toString()
|
||||
);
|
||||
return $template;
|
||||
|
@ -2,16 +2,25 @@
|
||||
namespace MailPoet\Newsletter\Shortcodes\Categories;
|
||||
|
||||
class Date {
|
||||
static function process($action) {
|
||||
static function process(
|
||||
$action,
|
||||
$action_argument = false,
|
||||
$action_argument_value = false
|
||||
) {
|
||||
$date = new \DateTime('now');
|
||||
$actions = array(
|
||||
$action_formats = array(
|
||||
'd' => $date->format('d'),
|
||||
'dordinal' => $date->format('dS'),
|
||||
'dtext' => $date->format('D'),
|
||||
'dtext' => $date->format('l'),
|
||||
'm' => $date->format('m'),
|
||||
'mtext' => $date->format('F'),
|
||||
'y' => $date->format('Y')
|
||||
);
|
||||
return (isset($actions[$action])) ? $actions[$action] : false;
|
||||
if(!empty($action_formats[$action])) {
|
||||
return $action_formats[$action];
|
||||
}
|
||||
return ($action === 'custom' && $action_argument === 'format') ?
|
||||
$date->format($action_argument_value) :
|
||||
false;
|
||||
}
|
||||
}
|
@ -7,19 +7,24 @@ use MailPoet\Statistics\Track\Unsubscribes;
|
||||
use MailPoet\Subscription\Url as SubscriptionUrl;
|
||||
|
||||
class Link {
|
||||
static function process($action,
|
||||
$default_value,
|
||||
static function process(
|
||||
$action,
|
||||
$action_argument,
|
||||
$action_argument_value,
|
||||
$newsletter,
|
||||
$subscriber,
|
||||
$queue
|
||||
$queue,
|
||||
$content,
|
||||
$wp_user_preview
|
||||
) {
|
||||
switch($action) {
|
||||
case 'subscription_unsubscribe':
|
||||
$action = 'subscription_unsubscribe_url';
|
||||
$url = self::processUrl(
|
||||
$action,
|
||||
esc_attr(SubscriptionUrl::getUnsubscribeUrl($subscriber)),
|
||||
$queue
|
||||
SubscriptionUrl::getUnsubscribeUrl($subscriber),
|
||||
$queue,
|
||||
$wp_user_preview
|
||||
);
|
||||
return sprintf(
|
||||
'<a target="_blank" href="%s">%s</a>',
|
||||
@ -31,14 +36,16 @@ class Link {
|
||||
return self::processUrl(
|
||||
$action,
|
||||
SubscriptionUrl::getUnsubscribeUrl($subscriber),
|
||||
$queue
|
||||
$queue,
|
||||
$wp_user_preview
|
||||
);
|
||||
|
||||
case 'subscription_manage':
|
||||
$url = self::processUrl(
|
||||
$action = 'subscription_manage_url',
|
||||
esc_attr(SubscriptionUrl::getManageUrl($subscriber)),
|
||||
$queue
|
||||
SubscriptionUrl::getManageUrl($subscriber),
|
||||
$queue,
|
||||
$wp_user_preview
|
||||
);
|
||||
return sprintf(
|
||||
'<a target="_blank" href="%s">%s</a>',
|
||||
@ -50,13 +57,20 @@ class Link {
|
||||
return self::processUrl(
|
||||
$action,
|
||||
SubscriptionUrl::getManageUrl($subscriber),
|
||||
$queue
|
||||
$queue,
|
||||
$wp_user_preview
|
||||
);
|
||||
|
||||
case 'newsletter_view_in_browser':
|
||||
$action = 'newsletter_view_in_browser_url';
|
||||
$url = esc_attr(NewsletterUrl::getViewInBrowserUrl($newsletter, $subscriber, $queue));
|
||||
$url = self::processUrl($action, $url, $queue);
|
||||
$url = NewsletterUrl::getViewInBrowserUrl(
|
||||
$type = null,
|
||||
$newsletter,
|
||||
$subscriber,
|
||||
$queue,
|
||||
$wp_user_preview
|
||||
);
|
||||
$url = self::processUrl($action, $url, $queue, $wp_user_preview);
|
||||
return sprintf(
|
||||
'<a target="_blank" href="%s">%s</a>',
|
||||
$url,
|
||||
@ -64,8 +78,14 @@ class Link {
|
||||
);
|
||||
|
||||
case 'newsletter_view_in_browser_url':
|
||||
$url = NewsletterUrl::getViewInBrowserUrl($newsletter, $subscriber, $queue);
|
||||
return self::processUrl($action, $url, $queue);
|
||||
$url = NewsletterUrl::getViewInBrowserUrl(
|
||||
$type = null,
|
||||
$newsletter,
|
||||
$subscriber,
|
||||
$queue,
|
||||
$wp_user_preview
|
||||
);
|
||||
return self::processUrl($action, $url, $queue, $wp_user_preview);
|
||||
|
||||
default:
|
||||
$shortcode = self::getShortcode($action);
|
||||
@ -74,15 +94,17 @@ class Link {
|
||||
$shortcode,
|
||||
$newsletter,
|
||||
$subscriber,
|
||||
$queue
|
||||
$queue,
|
||||
$wp_user_preview
|
||||
);
|
||||
return ($url !== $shortcode) ?
|
||||
self::processUrl($action, $url, $queue) :
|
||||
self::processUrl($action, $url, $queue, $wp_user_preview) :
|
||||
false;
|
||||
}
|
||||
}
|
||||
|
||||
static function processUrl($action, $url, $queue) {
|
||||
static function processUrl($action, $url, $queue, $wp_user_preview = false) {
|
||||
if($wp_user_preview) return '#';
|
||||
return ($queue !== false && (boolean)Setting::getValue('tracking.enabled')) ?
|
||||
self::getShortcode($action) :
|
||||
$url;
|
||||
@ -104,7 +126,12 @@ class Link {
|
||||
$url = SubscriptionUrl::getManageUrl($subscriber);
|
||||
break;
|
||||
case 'newsletter_view_in_browser_url':
|
||||
$url = NewsletterUrl::getViewInBrowserUrl($newsletter, $subscriber, $queue);
|
||||
$url = NewsletterUrl::getViewInBrowserUrl(
|
||||
$type = null,
|
||||
$newsletter,
|
||||
$subscriber,
|
||||
$queue
|
||||
);
|
||||
break;
|
||||
default:
|
||||
$shortcode = self::getShortcode($shortcode_action);
|
||||
|
@ -8,7 +8,8 @@ require_once(ABSPATH . "wp-includes/pluggable.php");
|
||||
|
||||
class Newsletter {
|
||||
static function process($action,
|
||||
$default_value,
|
||||
$action_argument,
|
||||
$action_argument_value,
|
||||
$newsletter,
|
||||
$subscriber,
|
||||
$queue,
|
||||
|
@ -10,11 +10,15 @@ require_once(ABSPATH . 'wp-includes/pluggable.php');
|
||||
class Subscriber {
|
||||
static function process(
|
||||
$action,
|
||||
$default_value,
|
||||
$action_argument,
|
||||
$action_argument_value,
|
||||
$newsletter,
|
||||
$subscriber
|
||||
) {
|
||||
if($subscriber !== false && !is_object($subscriber)) return false;
|
||||
$default_value = ($action_argument === 'default') ?
|
||||
$action_argument_value :
|
||||
'';
|
||||
switch($action) {
|
||||
case 'firstname':
|
||||
return ($subscriber) ? $subscriber->first_name : $default_value;
|
||||
|
@ -5,16 +5,19 @@ class Shortcodes {
|
||||
public $newsletter;
|
||||
public $subscriber;
|
||||
public $queue;
|
||||
public $wp_user_preview;
|
||||
const SHORTCODE_CATEGORY_NAMESPACE = 'MailPoet\Newsletter\Shortcodes\Categories\\';
|
||||
|
||||
function __construct(
|
||||
$newsletter = false,
|
||||
$subscriber = false,
|
||||
$queue = false
|
||||
$queue = false,
|
||||
$wp_user_preview = false
|
||||
) {
|
||||
$this->newsletter = $newsletter;
|
||||
$this->subscriber = $subscriber;
|
||||
$this->queue = $queue;
|
||||
$this->wp_user_preview = $wp_user_preview;
|
||||
}
|
||||
|
||||
function extract($content, $categories = false) {
|
||||
@ -34,7 +37,7 @@ class Shortcodes {
|
||||
|
||||
function match($shortcode) {
|
||||
preg_match(
|
||||
'/\[(?P<category>\w+)?:(?P<action>\w+)(?:.*?\|.*?default:(?P<default>.*?))?\]/',
|
||||
'/\[(?P<category>\w+)?:(?P<action>\w+)(?:.*?\|.*?(?P<argument>\w+):(?P<argument_value>.*?))?\]/',
|
||||
$shortcode,
|
||||
$match
|
||||
);
|
||||
@ -54,8 +57,11 @@ class Shortcodes {
|
||||
false;
|
||||
$shortcode_class =
|
||||
Shortcodes::SHORTCODE_CATEGORY_NAMESPACE . $shortcode_category;
|
||||
$shortcode_default_value = !empty($shortcode_details['default']) ?
|
||||
$shortcode_details['default'] :
|
||||
$shortcode_argument = !empty($shortcode_details['argument']) ?
|
||||
$shortcode_details['argument'] :
|
||||
false;
|
||||
$shortcode_argument_value = !empty($shortcode_details['argument_value']) ?
|
||||
$shortcode_details['argument_value'] :
|
||||
false;
|
||||
if(!class_exists($shortcode_class)) {
|
||||
$custom_shortcode = apply_filters(
|
||||
@ -64,7 +70,8 @@ class Shortcodes {
|
||||
$_this->newsletter,
|
||||
$_this->subscriber,
|
||||
$_this->queue,
|
||||
$content
|
||||
$content,
|
||||
$_this->wp_user_preview
|
||||
);
|
||||
return ($custom_shortcode === $shortcode) ?
|
||||
false :
|
||||
@ -72,11 +79,13 @@ class Shortcodes {
|
||||
}
|
||||
return $shortcode_class::process(
|
||||
$shortcode_action,
|
||||
$shortcode_default_value,
|
||||
$shortcode_argument,
|
||||
$shortcode_argument_value,
|
||||
$_this->newsletter,
|
||||
$_this->subscriber,
|
||||
$_this->queue,
|
||||
$content
|
||||
$content,
|
||||
$_this->wp_user_preview
|
||||
);
|
||||
}, $shortcodes);
|
||||
return $processed_shortcodes;
|
||||
|
@ -1,52 +1,80 @@
|
||||
<?php
|
||||
namespace MailPoet\Newsletter;
|
||||
|
||||
use MailPoet\Models\SendingQueue;
|
||||
use MailPoet\Router\Router;
|
||||
use MailPoet\Router\Endpoints\ViewInBrowser as ViewInBrowserEndpoint;
|
||||
use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Models\Newsletter as NewsletterModel;
|
||||
use MailPoet\Models\Subscriber as SubscriberModel;
|
||||
|
||||
class Url {
|
||||
const TYPE_ARCHIVE = 'display_archive';
|
||||
const TYPE_LISTING_EDITOR = 'display_listing_editor';
|
||||
|
||||
static function getViewInBrowserUrl(
|
||||
$newsletter,
|
||||
$type,
|
||||
NewsletterModel $newsletter,
|
||||
$subscriber = false,
|
||||
$queue = false,
|
||||
$preview = false
|
||||
) {
|
||||
if(is_object($newsletter)) {
|
||||
$newsletter = $newsletter->asArray();
|
||||
if($subscriber instanceof SubscriberModel) {
|
||||
$subscriber->token = SubscriberModel::generateToken($subscriber->email);
|
||||
}
|
||||
if(is_object($subscriber)) {
|
||||
$subscriber = $subscriber->asArray();
|
||||
} else if(!$subscriber) {
|
||||
$subscriber = Subscriber::getCurrentWPUser();
|
||||
$subscriber = ($subscriber) ? $subscriber->asArray() : false;
|
||||
switch($type) {
|
||||
case self::TYPE_ARCHIVE:
|
||||
// do not expose newsletter id when displaying archive newsletters
|
||||
$newsletter->id = null;
|
||||
$preview = true;
|
||||
break;
|
||||
case self::TYPE_LISTING_EDITOR:
|
||||
// enable preview and hide newsletter hash when displaying from editor or listings
|
||||
$newsletter->hash = null;
|
||||
$preview = true;
|
||||
break;
|
||||
default:
|
||||
// hide hash for all other display types
|
||||
$newsletter->hash = null;
|
||||
break;
|
||||
}
|
||||
if(is_object($queue)) {
|
||||
$queue = $queue->asArray();
|
||||
} else if(!$preview && !empty($newsletter['id'])) {
|
||||
$queue = SendingQueue::where('newsletter_id', $newsletter['id'])->findOne();
|
||||
$queue = ($queue) ? $queue->asArray() : false;
|
||||
}
|
||||
$data = array(
|
||||
'newsletter_id' => (!empty($newsletter['id'])) ?
|
||||
$newsletter['id'] :
|
||||
$newsletter,
|
||||
'subscriber_id' => (!empty($subscriber['id'])) ?
|
||||
$subscriber['id'] :
|
||||
$subscriber,
|
||||
'subscriber_token' => (!empty($subscriber['id'])) ?
|
||||
Subscriber::generateToken($subscriber['email']) :
|
||||
false,
|
||||
'queue_id' => (!empty($queue['id'])) ?
|
||||
$queue['id'] :
|
||||
$queue,
|
||||
'preview' => $preview
|
||||
);
|
||||
$data = self::createUrlDataObject($newsletter, $subscriber, $queue, $preview);
|
||||
return Router::buildRequest(
|
||||
ViewInBrowserEndpoint::ENDPOINT,
|
||||
ViewInBrowserEndpoint::ACTION_VIEW,
|
||||
$data
|
||||
);
|
||||
}
|
||||
|
||||
static function createUrlDataObject($newsletter, $subscriber, $queue, $preview) {
|
||||
return array(
|
||||
(!empty($newsletter->id)) ?
|
||||
(int)$newsletter->id :
|
||||
0,
|
||||
(!empty($newsletter->hash)) ?
|
||||
$newsletter->hash :
|
||||
0,
|
||||
(!empty($subscriber->id)) ?
|
||||
(int)$subscriber->id :
|
||||
0,
|
||||
(!empty($subscriber->token)) ?
|
||||
$subscriber->token :
|
||||
0,
|
||||
(!empty($queue->id)) ?
|
||||
(int)$queue->id :
|
||||
0,
|
||||
(int)$preview
|
||||
);
|
||||
}
|
||||
|
||||
static function transformUrlDataObject($data) {
|
||||
reset($data);
|
||||
if (!is_int(key($data))) return $data;
|
||||
$transformed_data = array();
|
||||
$transformed_data['newsletter_id'] = (!empty($data[0])) ? $data[0] : false;
|
||||
$transformed_data['newsletter_hash'] = (!empty($data[1])) ? $data[1] : false;
|
||||
$transformed_data['subscriber_id'] = (!empty($data[2])) ? $data[2] : false;
|
||||
$transformed_data['subscriber_token'] = (!empty($data[3])) ? $data[3] : false;
|
||||
$transformed_data['queue_id'] = (!empty($data[4])) ? $data[4] : false;
|
||||
$transformed_data['preview'] = (!empty($data[5])) ? $data[5] : false;
|
||||
return $transformed_data;
|
||||
}
|
||||
}
|
@ -2,13 +2,17 @@
|
||||
namespace MailPoet\Newsletter;
|
||||
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Newsletter\Links\Links;
|
||||
use MailPoet\Newsletter\Renderer\Renderer;
|
||||
use MailPoet\Newsletter\Shortcodes\Shortcodes;
|
||||
|
||||
class ViewInBrowser {
|
||||
function view($data) {
|
||||
$wp_user_preview = ($data->preview && $data->subscriber->isWPUser());
|
||||
$wp_user_preview = (
|
||||
($data->subscriber && $data->subscriber->isWPUser() && $data->preview) ||
|
||||
($data->preview && $data->newsletter_hash)
|
||||
);
|
||||
return $this->renderNewsletter(
|
||||
$data->newsletter,
|
||||
$data->subscriber,
|
||||
@ -18,25 +22,36 @@ class ViewInBrowser {
|
||||
}
|
||||
|
||||
function renderNewsletter($newsletter, $subscriber, $queue, $wp_user_preview) {
|
||||
if($queue && $queue->newsletter_rendered_body) {
|
||||
$newsletter_body = $queue->getNewsletterRenderedBody();
|
||||
if($queue && $queue->getNewsletterRenderedBody()) {
|
||||
$newsletter_body = $queue->getNewsletterRenderedBody('html');
|
||||
// rendered newsletter body has shortcodes converted to links; we need to
|
||||
// isolate "view in browser", "unsubscribe" and "manage subscription" links
|
||||
// and convert them to shortcodes, which later will be replaced with "#" when
|
||||
// newsletter is previewed
|
||||
if($wp_user_preview && preg_match(Links::getLinkRegex(), $newsletter_body)) {
|
||||
$newsletter_body = Links::convertHashedLinksToShortcodesAndUrls(
|
||||
$newsletter_body,
|
||||
$convert_all = true
|
||||
);
|
||||
// remove open tracking link
|
||||
$newsletter_body = str_replace(Links::DATA_TAG_OPEN, '', $newsletter_body);
|
||||
}
|
||||
} else {
|
||||
$renderer = new Renderer($newsletter, $wp_user_preview);
|
||||
$newsletter_body = $renderer->render();
|
||||
$newsletter_body = $renderer->render('html');
|
||||
}
|
||||
$shortcodes = new Shortcodes(
|
||||
$newsletter,
|
||||
$subscriber,
|
||||
$queue
|
||||
|
||||
$queue,
|
||||
$wp_user_preview
|
||||
);
|
||||
$rendered_newsletter = $shortcodes->replace($newsletter_body['html']);
|
||||
if($queue && (boolean)Setting::getValue('tracking.enabled')) {
|
||||
$rendered_newsletter = $shortcodes->replace($newsletter_body);
|
||||
if(!$wp_user_preview && $queue && $subscriber && (boolean)Setting::getValue('tracking.enabled')) {
|
||||
$rendered_newsletter = Links::replaceSubscriberData(
|
||||
$subscriber->id,
|
||||
$queue->id,
|
||||
$rendered_newsletter,
|
||||
$wp_user_preview
|
||||
$rendered_newsletter
|
||||
);
|
||||
}
|
||||
return $rendered_newsletter;
|
||||
|
38
lib/Router/Endpoints/CronDaemon.php
Normal file
38
lib/Router/Endpoints/CronDaemon.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace MailPoet\Router\Endpoints;
|
||||
|
||||
use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\Cron\Daemon;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class CronDaemon {
|
||||
const ENDPOINT = 'cron_daemon';
|
||||
const ACTION_RUN = 'run';
|
||||
const ACTION_PING = 'ping';
|
||||
const ACTION_PING_RESPONSE = 'pingResponse';
|
||||
public $allowed_actions = array(
|
||||
self::ACTION_RUN,
|
||||
self::ACTION_PING,
|
||||
self::ACTION_PING_RESPONSE
|
||||
);
|
||||
public $data;
|
||||
|
||||
function __construct($data) {
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
function run() {
|
||||
$queue = new Daemon($this->data);
|
||||
$queue->run();
|
||||
}
|
||||
|
||||
function ping() {
|
||||
die(CronHelper::pingDaemon());
|
||||
}
|
||||
|
||||
function pingResponse() {
|
||||
$queue = new Daemon();
|
||||
$queue->ping();
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
namespace MailPoet\Router\Endpoints;
|
||||
|
||||
use MailPoet\Cron\Daemon;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class Queue {
|
||||
const ENDPOINT = 'queue';
|
||||
const ACTION_RUN = 'run';
|
||||
public $allowed_actions = array(self::ACTION_RUN);
|
||||
public $data;
|
||||
|
||||
function __construct($data) {
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
function run() {
|
||||
$queue = new Daemon($this->data);
|
||||
$queue->run();
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ use MailPoet\Models\Newsletter;
|
||||
use MailPoet\Models\NewsletterLink;
|
||||
use MailPoet\Models\SendingQueue;
|
||||
use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Newsletter\Links\Links;
|
||||
use MailPoet\Statistics\Track\Clicks;
|
||||
use MailPoet\Statistics\Track\Opens;
|
||||
|
||||
@ -35,10 +36,10 @@ class Track {
|
||||
}
|
||||
|
||||
function _processTrackData($data) {
|
||||
$data = (object)$data;
|
||||
$data = (object)Links::transformUrlDataObject($data);
|
||||
if(empty($data->queue_id) ||
|
||||
empty($data->subscriber_id) ||
|
||||
empty($data->subscriber_token)
|
||||
empty($data->subscriber_id) ||
|
||||
empty($data->subscriber_token)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1,13 +1,17 @@
|
||||
<?php
|
||||
namespace MailPoet\Router\Endpoints;
|
||||
|
||||
use MailPoet\Config\Env;
|
||||
use MailPoet\Models\Newsletter;
|
||||
use MailPoet\Models\SendingQueue;
|
||||
use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Newsletter\Url as NewsletterUrl;
|
||||
use MailPoet\Newsletter\ViewInBrowser as NewsletterViewInBrowser;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
require_once(ABSPATH . 'wp-includes/pluggable.php');
|
||||
|
||||
class ViewInBrowser {
|
||||
const ENDPOINT = 'view_in_browser';
|
||||
const ACTION_VIEW = 'view';
|
||||
@ -24,37 +28,52 @@ class ViewInBrowser {
|
||||
}
|
||||
|
||||
function _processBrowserPreviewData($data) {
|
||||
$data = (object)$data;
|
||||
if(empty($data->subscriber_id) ||
|
||||
empty($data->subscriber_token) ||
|
||||
empty($data->newsletter_id)
|
||||
) {
|
||||
$data = (object)NewsletterUrl::transformUrlDataObject($data);
|
||||
return ($this->_validateBrowserPreviewData($data)) ?
|
||||
$data :
|
||||
$this->_abort();
|
||||
} else {
|
||||
$data->newsletter = Newsletter::findOne($data->newsletter_id);
|
||||
$data->subscriber = Subscriber::findOne($data->subscriber_id);
|
||||
$data->queue = ($data->queue_id) ?
|
||||
SendingQueue::findOne($data->queue_id) :
|
||||
false;
|
||||
return ($this->_validateBrowserPreviewData($data)) ?
|
||||
$data :
|
||||
$this->_abort();
|
||||
}
|
||||
}
|
||||
|
||||
function _validateBrowserPreviewData($data) {
|
||||
if(!$data || !$data->subscriber || !$data->newsletter) return false;
|
||||
$subscriber_token_match =
|
||||
Subscriber::verifyToken($data->subscriber->email, $data->subscriber_token);
|
||||
if(!$subscriber_token_match) return false;
|
||||
// return if this is a WP user previewing the newsletter
|
||||
if($data->subscriber->isWPUser() && $data->preview) {
|
||||
return $data;
|
||||
}
|
||||
// if queue exists, check if the newsletter was sent to the subscriber
|
||||
if($data->queue && !$data->queue->isSubscriberProcessed($data->subscriber->id)) {
|
||||
$data = false;
|
||||
// either newsletter ID or hash must be defined, and newsletter must exist
|
||||
if(empty($data->newsletter_id) && empty($data->newsletter_hash)) return false;
|
||||
$data->newsletter = (!empty($data->newsletter_hash)) ?
|
||||
Newsletter::getByHash($data->newsletter_hash) :
|
||||
Newsletter::findOne($data->newsletter_id);
|
||||
if(!$data->newsletter) return false;
|
||||
|
||||
// subscriber is optional; if exists, token must validate
|
||||
$data->subscriber = (!empty($data->subscriber_id)) ?
|
||||
Subscriber::findOne($data->subscriber_id) :
|
||||
false;
|
||||
if($data->subscriber) {
|
||||
if(empty($data->subscriber_token) ||
|
||||
!Subscriber::verifyToken($data->subscriber->email, $data->subscriber_token)
|
||||
) return false;
|
||||
}
|
||||
|
||||
// if newsletter ID is defined then subscriber must exist
|
||||
if($data->newsletter_id && !$data->subscriber) return false;
|
||||
|
||||
// queue is optional; if defined, get it
|
||||
$data->queue = (!empty($data->queue_id)) ?
|
||||
SendingQueue::findOne($data->queue_id) :
|
||||
SendingQueue::where('newsletter_id', $data->newsletter->id)->findOne();
|
||||
|
||||
// allow users with 'manage_options' permission to preview any newsletter
|
||||
if(!empty($data->preview) && current_user_can(Env::$required_permission)
|
||||
) return $data;
|
||||
|
||||
// allow others to preview newsletters only when newsletter hash is defined
|
||||
if(!empty($data->preview) && empty($data->newsletter_hash)
|
||||
) return false;
|
||||
|
||||
// if queue and subscriber exist, subscriber must have received the newsletter
|
||||
if($data->queue &&
|
||||
$data->subscriber &&
|
||||
!$data->queue->isSubscriberProcessed($data->subscriber->id)
|
||||
) return false;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,7 @@ class Router {
|
||||
if(!method_exists($endpoint, $this->action) || !in_array($this->action, $endpoint->allowed_actions)) {
|
||||
return $this->terminateRequest(self::RESPONSE_ERROR, __('Invalid router endpoint action.', 'mailpoet'));
|
||||
}
|
||||
do_action('mailpoet_conflict_resolver_router_url_query_parameters');
|
||||
return call_user_func(
|
||||
array(
|
||||
$endpoint,
|
||||
@ -57,14 +58,15 @@ class Router {
|
||||
return rtrim(base64_encode(json_encode($data)), '=');
|
||||
}
|
||||
|
||||
static function buildRequest($endpoint, $action, $data) {
|
||||
$data = self::encodeRequestData($data);
|
||||
static function buildRequest($endpoint, $action, $data = false) {
|
||||
$params = array(
|
||||
self::NAME => '',
|
||||
'endpoint' => $endpoint,
|
||||
'action' => $action,
|
||||
'data' => $data
|
||||
);
|
||||
if($data) {
|
||||
$params['data'] = self::encodeRequestData($data);
|
||||
}
|
||||
return add_query_arg($params, home_url());
|
||||
}
|
||||
|
||||
|
@ -57,11 +57,21 @@ class Pages {
|
||||
}
|
||||
|
||||
function confirm() {
|
||||
if($this->subscriber !== false) {
|
||||
$this->subscriber->status = Subscriber::STATUS_SUBSCRIBED;
|
||||
$this->subscriber->confirmed_ip = $_SERVER['REMOTE_ADDR'];
|
||||
$this->subscriber->setExpr('confirmed_at', 'NOW()');
|
||||
$this->subscriber->save();
|
||||
if($this->subscriber === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$subscriber_data = $this->subscriber->getUnconfirmedData();
|
||||
|
||||
$this->subscriber->status = Subscriber::STATUS_SUBSCRIBED;
|
||||
$this->subscriber->confirmed_ip = $_SERVER['REMOTE_ADDR'];
|
||||
$this->subscriber->setExpr('confirmed_at', 'NOW()');
|
||||
$this->subscriber->unconfirmed_data = null;
|
||||
$this->subscriber->save();
|
||||
|
||||
// update subscriber from stored data after confirmation
|
||||
if(!empty($subscriber_data)) {
|
||||
Subscriber::createOrUpdate($subscriber_data);
|
||||
}
|
||||
}
|
||||
|
||||
@ -297,6 +307,10 @@ class Pages {
|
||||
),
|
||||
'is_checked' => (
|
||||
$subscriber->status === Subscriber::STATUS_BOUNCED
|
||||
),
|
||||
'is_disabled' => true,
|
||||
'is_hidden' => (
|
||||
$subscriber->status !== Subscriber::STATUS_BOUNCED
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -47,8 +47,13 @@ class Assets
|
||||
$output = array();
|
||||
|
||||
foreach($stylesheets as $stylesheet) {
|
||||
$output[] = '<link rel="stylesheet" type="text/css"'.
|
||||
' href="'.$this->_globals['assets_url'].'/css/'.$stylesheet.'">';
|
||||
$url = $this->appendVersionToUrl(
|
||||
$this->_globals['assets_url'] . '/css/' . $stylesheet
|
||||
);
|
||||
$output[] = sprintf(
|
||||
'<link rel="stylesheet" type="text/css" href="%s">',
|
||||
$url
|
||||
);
|
||||
}
|
||||
|
||||
return join("\n", $output);
|
||||
@ -59,15 +64,25 @@ class Assets
|
||||
$output = array();
|
||||
|
||||
foreach($scripts as $script) {
|
||||
$output[] = '<script type="text/javascript"'.
|
||||
' src="'.$this->_globals['assets_url'].'/js/'.$script.'">'.
|
||||
'</script>';
|
||||
$url = $this->appendVersionToUrl(
|
||||
$this->_globals['assets_url'] . '/js/' . $script
|
||||
);
|
||||
$output[] = sprintf(
|
||||
'<script type="text/javascript" src="%s"></script>',
|
||||
$url
|
||||
);
|
||||
}
|
||||
|
||||
return join("\n", $output);
|
||||
}
|
||||
|
||||
public function generateImageUrl($path) {
|
||||
return $this->_globals['assets_url'].'/img/'.$path;
|
||||
return $this->appendVersionToUrl(
|
||||
$this->_globals['assets_url'] . '/img/' . $path
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function appendVersionToUrl($url) {
|
||||
return add_query_arg('mailpoet_version', $this->_globals['version'], $url);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
<?php
|
||||
namespace MailPoet\Util;
|
||||
use \Sunra\PhpSimple\HtmlDomParser;
|
||||
use csstidy;
|
||||
|
||||
/*
|
||||
|
85
lib/Util/ConflictResolver.php
Normal file
85
lib/Util/ConflictResolver.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
namespace MailPoet\Util;
|
||||
|
||||
class ConflictResolver {
|
||||
public $allowed_assets = array(
|
||||
'styles' => array(
|
||||
// WP default
|
||||
'admin-bar',
|
||||
'colors',
|
||||
'ie',
|
||||
'wp-auth-check',
|
||||
// third-party
|
||||
'query-monitor'
|
||||
),
|
||||
'scripts' => array(
|
||||
// WP default
|
||||
'common',
|
||||
'admin-bar',
|
||||
'utils',
|
||||
'svg-painter',
|
||||
'wp-auth-check',
|
||||
// third-party
|
||||
'query-monitor'
|
||||
)
|
||||
);
|
||||
|
||||
function init() {
|
||||
add_action(
|
||||
'mailpoet_conflict_resolver_router_url_query_parameters',
|
||||
array(
|
||||
$this,
|
||||
'resolveRouterUrlQueryParametersConflict'
|
||||
)
|
||||
);
|
||||
add_action(
|
||||
'mailpoet_conflict_resolver_styles',
|
||||
array(
|
||||
$this,
|
||||
'resolveStylesConflict'
|
||||
)
|
||||
);
|
||||
add_action(
|
||||
'mailpoet_conflict_resolver_scripts',
|
||||
array(
|
||||
$this,
|
||||
'resolveScriptsConflict'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function resolveRouterUrlQueryParametersConflict() {
|
||||
// prevents other plugins from overtaking URL query parameters 'action=' and 'endpoint='
|
||||
unset($_GET['endpoint'], $_GET['action']);
|
||||
}
|
||||
|
||||
function resolveStylesConflict() {
|
||||
// unload all styles except from the list of allowed
|
||||
$dequeue_styles = function() {
|
||||
global $wp_styles;
|
||||
foreach($wp_styles->queue as $wp_style) {
|
||||
if(!in_array($wp_style, $this->allowed_assets['styles'])) {
|
||||
wp_dequeue_style($wp_style);
|
||||
}
|
||||
}
|
||||
};
|
||||
add_action('wp_print_styles', $dequeue_styles);
|
||||
add_action('admin_print_styles', $dequeue_styles);
|
||||
add_action('admin_print_footer_scripts', $dequeue_styles);
|
||||
add_action('admin_footer', $dequeue_styles);
|
||||
}
|
||||
|
||||
function resolveScriptsConflict() {
|
||||
// unload all scripts except from the list of allowed
|
||||
$dequeue_scripts = function() {
|
||||
global $wp_scripts;
|
||||
foreach($wp_scripts->queue as $wp_script) {
|
||||
if(!in_array($wp_script, $this->allowed_assets['scripts'])) {
|
||||
wp_dequeue_script($wp_script);
|
||||
}
|
||||
}
|
||||
};
|
||||
add_action('wp_print_scripts', $dequeue_scripts);
|
||||
add_action('admin_print_footer_scripts', $dequeue_scripts);
|
||||
}
|
||||
}
|
49
lib/WP/Readme.php
Normal file
49
lib/WP/Readme.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
namespace MailPoet\WP;
|
||||
|
||||
class Readme {
|
||||
static function parseChangelog($readme_txt, $limit = null) {
|
||||
// Extract changelog section of the readme.txt
|
||||
preg_match('/== Changelog ==(.*?)(\n==|$)/is', $readme_txt, $changelog);
|
||||
|
||||
if(empty($changelog[1])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get changelog entries
|
||||
$entries = preg_split('/\n(?=\=)/', trim($changelog[1]), -1, PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
if(empty($entries)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$c = 0;
|
||||
$changelog = array();
|
||||
|
||||
foreach($entries as $entry) {
|
||||
// Locate version header and changes list
|
||||
preg_match('/=(.*?)=(.*)/s', $entry, $parts);
|
||||
|
||||
if(empty($parts[1]) || empty($parts[2])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$header = trim($parts[1]);
|
||||
$list = trim($parts[2]);
|
||||
|
||||
// Get individual items from the list
|
||||
$list = preg_split('/(^|\n)[\* ]*/', $list, -1, PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
$changelog[] = array(
|
||||
'version' => $header,
|
||||
'changes' => $list,
|
||||
);
|
||||
|
||||
if(++$c == $limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $changelog;
|
||||
}
|
||||
}
|
@ -5,13 +5,13 @@ use MailPoet\Config\Initializer;
|
||||
|
||||
/*
|
||||
* Plugin Name: MailPoet
|
||||
* Version: 3.0.0-beta.6
|
||||
* Version: 3.0.0-beta.13
|
||||
* Plugin URI: http://www.mailpoet.com
|
||||
* Description: Create and send beautiful email newsletters, autoresponders, and post notifications without leaving WordPress. This is a beta version of our brand new plugin!
|
||||
* Author: MailPoet
|
||||
* Author URI: http://www.mailpoet.com
|
||||
* Requires at least: 4.0
|
||||
* Tested up to: 4.6.1
|
||||
* Requires at least: 4.6
|
||||
* Tested up to: 4.7
|
||||
*
|
||||
* Text Domain: mailpoet
|
||||
* Domain Path: /lang/
|
||||
@ -24,7 +24,7 @@ use MailPoet\Config\Initializer;
|
||||
$mailpoet_loader = dirname(__FILE__) . '/vendor/autoload.php';
|
||||
if(file_exists($mailpoet_loader)) {
|
||||
require $mailpoet_loader;
|
||||
define('MAILPOET_VERSION', '3.0.0-beta.6');
|
||||
define('MAILPOET_VERSION', '3.0.0-beta.13');
|
||||
$initializer = new Initializer(
|
||||
array(
|
||||
'file' => __FILE__,
|
||||
|
67
readme.txt
67
readme.txt
@ -2,8 +2,8 @@
|
||||
Contributors: mailpoet, wysija
|
||||
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.6
|
||||
Tested up to: 4.7
|
||||
Stable tag: 3.0.0-beta.13
|
||||
Create and send beautiful emails and newsletters from WordPress.
|
||||
|
||||
== Description ==
|
||||
@ -83,6 +83,65 @@ Our [support site](https://docs.mailpoet.com/) has plenty of articles. You can w
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 3.0.0-beta.13 - 2017-01-17 =
|
||||
* Improved: style/script conflicts on MailPoet pages are now resolved by unloading non-default assets. Thx Michel for reporting one such case!;
|
||||
* Fixed: MySQL wait_timeout of less than 20 seconds results in errors when sending. Thx Tim!;
|
||||
* Fixed: unsubscribe URL doesn't work when BuddyPress is enabled;
|
||||
* Fixed: some form styles aren't saved. Thanks Pete!;
|
||||
* Fixed: typo in subscription management shortcode instructions. Thx Tim once more!
|
||||
|
||||
= 3.0.0-beta.12 - 2017-01-10 =
|
||||
* Improved: faster load times of Emails page with large database;
|
||||
* Improved: sender header is now set for SMTP/PHPMail method to work with MS Exchange. Thx Karsten!;
|
||||
* Improved: better asset conflict management with other plugins;
|
||||
* Fixed: newly published custom post types are now sent. Thx Jim!;
|
||||
* Fixed: post notifications now sent when ALC block is configured to display titles only. Thx Pete;
|
||||
* Fixed: shortcode "date:dtext" displays full name (e.g., Sunday) instead of abbreviated (e.g., Sun);
|
||||
* Fixed: hide mailer error on send previews. Thx Karsten again!;
|
||||
* Fixed: various minor issues.
|
||||
|
||||
= 3.0.0-beta.11 - 2016-12-31 =
|
||||
* Improved: newsletters' statistics are generated in a split second;
|
||||
* Fixed: subscribers' data is properly saved on repeat and/or multiple subscription attempts;
|
||||
* Fixed: WP posts are displayed/rendered with proper line breaks and spaces;
|
||||
* Fixed: preview-by-email works once again;
|
||||
* Wished: 2017 sees the release of the fantastic MailPoet 3 and the super-duper MailPoet Sending Service;
|
||||
* Wished: 2017 turns out to be an amazing year for all of our beloved and new users!
|
||||
|
||||
= 3.0.0-beta.10 - 2016-12-27 =
|
||||
* Improved: newsletter is saved prior to sending an email preview;
|
||||
* Improved: subscription management page conditionally displays the "bounced" status;
|
||||
* Improved: deleted lists are displayed in newsletter listings;
|
||||
* Fixed: newsletter/subscriber/list/form dates are properly formatted according to WP settings;
|
||||
* Fixed: emails' "Return-path" header is set to the bounce address configured in Settings->Advanced;
|
||||
* Fixed: archived newsletters' shortcode works for site visitors;
|
||||
* Fixed: unicode support for newsletters.
|
||||
|
||||
= 3.0.0-beta.9 - 2016-12-20 =
|
||||
* Improved: the plugin is now tested up to WP 4.7;
|
||||
* Improved: MailPoet's sending service bounce status API update;
|
||||
* Improved: change duplicate subscribers import message to be more descriptive;
|
||||
* Fixed: database character set and time zone setup;
|
||||
* Fixed: alignment of post titles inside notificaiton emails;
|
||||
* Fixed: partially generated or missing translations from .pot file.
|
||||
|
||||
= 3.0.0-beta.8 - 2016-12-13 =
|
||||
* Added: MailPoet's sending service can now sync hard bounced addresses with the plugin to keep your lists tidy and clean;
|
||||
* Improved: gracefully catch vendor library conflicts with other plugins. Thx Vikas;
|
||||
* Improved: force browsers to load the intended JS and CSS assets by adding a parameter, ie style.css?ver=x.y.z;
|
||||
* Fixed: render non paragraph elements inside a block quote. Thx Remco!;
|
||||
* Fixed a query that's gone awry in Mysql version 5.6. Dank je Pim!
|
||||
|
||||
= 3.0.0-beta.7.1 - 2016-12-06 =
|
||||
* Improved: allow user to restart sending after sending method failure;
|
||||
* Fixed: subscribers are not added to lists after import;
|
||||
* Fixed: sending should stop when newsletter is trashed;
|
||||
* Fixed: update database schema after an update which fixes an SQL error;
|
||||
* Fixed: status of sent newsletters is showing "paused" instead of "sent";
|
||||
* Fixed: dividers in Automatic Latest Posts posts are not displayed. Thx Gregor!;
|
||||
* Fixed: shortcodes (ie, first name) are not rendered when sending a preview;
|
||||
* Fixed: count of confirmed subscribers only in step 2 of import is erroneous.
|
||||
|
||||
= 3.0.0-beta.6 - 2016-11-29 =
|
||||
* Added: "bounced" status has been added to subscribers;
|
||||
* Improved: execution time enforced between individual send operations. Avoids duplicate sending on really slow servers;
|
||||
@ -126,12 +185,12 @@ Our [support site](https://docs.mailpoet.com/) has plenty of articles. You can w
|
||||
* Fixed newsletter number shortcode for notification newsletters;
|
||||
* Enhanced HelpScout support beacon report with extra support data;
|
||||
* Fixed email renderer to not throw entity warnings on earlier PHP versions;
|
||||
* Fixed newsletter preview incompatibility errors for earlier PHP versions;
|
||||
* Fixed newsletter preview incompatibility errors for earlier PHP versions.
|
||||
|
||||
= 3.0.0-beta.2 - 2016-10 =
|
||||
|
||||
* Fixed compatibility issues with PHP versions earlier than PHP 5.6;
|
||||
* Renamed 'Emails' email type to 'Newsletters';
|
||||
* Renamed 'Emails' email type to 'Newsletters'.
|
||||
|
||||
= 3.0.0-beta.1 - 2016-10 =
|
||||
|
||||
|
@ -23,10 +23,14 @@ module.exports = function (grunt) {
|
||||
cwd: '.', // base path where to look for translatable strings
|
||||
domainPath: 'lang', // where to save the .pot
|
||||
exclude: [
|
||||
'build/.*',
|
||||
'\.mp_svn/.*',
|
||||
'assets/.*',
|
||||
'lang/.*',
|
||||
'node_modules/.*',
|
||||
'plugin_repository/.*',
|
||||
'tasks/.*',
|
||||
'tests/.*',
|
||||
'vendor/.*',
|
||||
'tasks/.*'
|
||||
'vendor/.*'
|
||||
],
|
||||
mainFile: 'index.php', // Main project file.
|
||||
potFilename: 'mailpoet.pot', // Name of the POT file.
|
||||
|
48
tasks/makepot/node_modules/grunt-wp-i18n/vendor/wp-i18n-tools/extract.php
generated
vendored
48
tasks/makepot/node_modules/grunt-wp-i18n/vendor/wp-i18n-tools/extract.php
generated
vendored
@ -257,29 +257,41 @@ class StringExtractor {
|
||||
$current_argument = null;
|
||||
}
|
||||
} elseif(in_array($extension, array('html', 'hbs'))) {
|
||||
// get all translatable strings
|
||||
$functions_pattern = '/('.join('|', $function_names).')\((.*)\)/Us';
|
||||
preg_match_all($functions_pattern, $code, $matches, PREG_OFFSET_CAPTURE);
|
||||
|
||||
for($i = 0, $count = count($matches[1]); $i < $count; $i++) {
|
||||
// get match offset (position where the match was found)
|
||||
$offset = array_pop($matches[0][$i]);
|
||||
// fetches all the text before the match
|
||||
list($before) = str_split($code, $offset);
|
||||
// calculate line number
|
||||
$line_number = strlen($before) - strlen(str_replace("\n", "", $before)) + 1;
|
||||
$function_patterns = array(
|
||||
'/(__)\((([\'"]).+?\3)\)/',
|
||||
'/(_n)\(([\'"].+?[\'"],\s*[\'"].+?[\'"],\s*.+?)\)/'
|
||||
);
|
||||
|
||||
// extract arguments (potentially multiple)
|
||||
$arguments_pattern = "/([^,\\s][^\\,]*)[^,\\s]*/s";
|
||||
preg_match_all($arguments_pattern, $matches[2][$i][0], $arguments_matches);
|
||||
$matches = array();
|
||||
|
||||
$arguments = array();
|
||||
foreach($arguments_matches[1] as $arg) {
|
||||
$arguments[] = trim($arg, "'\"");
|
||||
}
|
||||
foreach($function_patterns as $pattern) {
|
||||
preg_match_all($pattern, $code, $function_matches, PREG_OFFSET_CAPTURE);
|
||||
for($i = 0; $i < count($function_matches[1]); $i += 1) {
|
||||
$matches[] = array(
|
||||
'call' => $function_matches[0][$i][0],
|
||||
'call_offset' => $function_matches[0][$i][1],
|
||||
'name' => $function_matches[1][$i][0],
|
||||
'arguments' => $function_matches[2][$i][0]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
foreach($matches as $match) {
|
||||
list($text_before_match) = str_split($code, $match['call_offset']);
|
||||
$number_of_newlines = strlen($text_before_match) - strlen(str_replace("\n", "", $text_before_match));
|
||||
$line_number = $number_of_newlines + 1;
|
||||
|
||||
$arguments_pattern = "/(?s)(?<!\\\\)(\"|')(?:[^\\\\]|\\\\.)*?\\1|[^,\\s]+/";
|
||||
preg_match_all($arguments_pattern, $match['arguments'], $arguments_matches);
|
||||
|
||||
$arguments = array();
|
||||
foreach($arguments_matches[0] as $argument) {
|
||||
$arguments[] = trim($argument, "'\"");
|
||||
}
|
||||
|
||||
$call = array(
|
||||
'name' => $matches[1][$i][0],
|
||||
'name' => $match['name'],
|
||||
'args' => $arguments,
|
||||
'line' => $line_number
|
||||
);
|
||||
|
@ -90,6 +90,16 @@ define([
|
||||
|
||||
mock.verify();
|
||||
});
|
||||
|
||||
it('provides a promise if a result container is passed to save event', function() {
|
||||
var spy = sinon.spy(module, 'save'),
|
||||
saveResult = {promise: null};
|
||||
module.saveAndProvidePromise(saveResult);
|
||||
spy.restore();
|
||||
expect(spy.calledOnce).to.be.true;
|
||||
expect(saveResult.promise).to.be.an('object');
|
||||
expect(saveResult.promise.then).to.be.a('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('view', function() {
|
||||
|
@ -1,23 +1,29 @@
|
||||
<?php
|
||||
use \MailPoet\API\Response as APIResponse;
|
||||
use \MailPoet\API\Endpoints\Newsletters;
|
||||
use \MailPoet\Models\Newsletter;
|
||||
use Codeception\Util\Fixtures;
|
||||
use Codeception\Util\Stub;
|
||||
use MailPoet\API\Endpoints\Newsletters;
|
||||
use MailPoet\API\Response as APIResponse;
|
||||
use MailPoet\Models\Newsletter;
|
||||
use MailPoet\Models\NewsletterOptionField;
|
||||
use \MailPoet\Models\NewsletterSegment;
|
||||
use \MailPoet\Models\Segment;
|
||||
use MailPoet\Models\NewsletterSegment;
|
||||
use MailPoet\Models\Segment;
|
||||
use MailPoet\Newsletter\Scheduler\Scheduler;
|
||||
|
||||
class NewslettersTest extends MailPoetTest {
|
||||
function _before() {
|
||||
$this->newsletter = Newsletter::createOrUpdate(array(
|
||||
'subject' => 'My Standard Newsletter',
|
||||
'type' => Newsletter::TYPE_STANDARD
|
||||
));
|
||||
$this->newsletter = Newsletter::createOrUpdate(
|
||||
array(
|
||||
'subject' => 'My Standard Newsletter',
|
||||
'body' => Fixtures::get('newsletter_body_template'),
|
||||
'type' => Newsletter::TYPE_STANDARD,
|
||||
));
|
||||
|
||||
$this->post_notification = Newsletter::createOrUpdate(array(
|
||||
'subject' => 'My Post Notification',
|
||||
'type' => Newsletter::TYPE_NOTIFICATION
|
||||
));
|
||||
$this->post_notification = Newsletter::createOrUpdate(
|
||||
array(
|
||||
'subject' => 'My Post Notification',
|
||||
'body' => Fixtures::get('newsletter_body_template'),
|
||||
'type' => Newsletter::TYPE_NOTIFICATION
|
||||
));
|
||||
}
|
||||
|
||||
function testItCanGetANewsletter() {
|
||||
@ -59,7 +65,8 @@ class NewslettersTest extends MailPoetTest {
|
||||
|
||||
$router = new Newsletters();
|
||||
$response = $router->save($valid_data);
|
||||
$saved_newsletter = Newsletter::filter('filterWithOptions')->findOne($response->data['id']);
|
||||
$saved_newsletter = Newsletter::filter('filterWithOptions')
|
||||
->findOne($response->data['id']);
|
||||
expect($response->status)->equals(APIResponse::STATUS_OK);
|
||||
expect($response->data)->equals($saved_newsletter->asArray());
|
||||
// newsletter option should be saved
|
||||
@ -127,7 +134,8 @@ class NewslettersTest extends MailPoetTest {
|
||||
// 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']);
|
||||
$saved_newsletter = Newsletter::filter('filterWithOptions')
|
||||
->findOne($response->data['id']);
|
||||
expect($response->status)->equals(APIResponse::STATUS_OK);
|
||||
expect($saved_newsletter->schedule)->equals('* * * * *');
|
||||
}
|
||||
@ -140,14 +148,18 @@ class NewslettersTest extends MailPoetTest {
|
||||
$newsletter_data = array(
|
||||
'id' => $this->newsletter->id,
|
||||
'subject' => 'My Updated Newsletter',
|
||||
'segments' => array($segment_1->asArray(), $fake_segment_id)
|
||||
'segments' => array(
|
||||
$segment_1->asArray(),
|
||||
$fake_segment_id
|
||||
)
|
||||
);
|
||||
|
||||
$response = $router->save($newsletter_data);
|
||||
expect($response->status)->equals(APIResponse::STATUS_OK);
|
||||
|
||||
$updated_newsletter =
|
||||
Newsletter::findOne($this->newsletter->id)->withSegments();
|
||||
Newsletter::findOne($this->newsletter->id)
|
||||
->withSegments();
|
||||
|
||||
expect(count($updated_newsletter->segments))->equals(1);
|
||||
expect($updated_newsletter->segments[0]['name'])->equals('Segment 1');
|
||||
@ -156,33 +168,41 @@ class NewslettersTest extends MailPoetTest {
|
||||
function testItCanSetANewsletterStatus() {
|
||||
$router = new Newsletters();
|
||||
// set status to sending
|
||||
$response = $router->setStatus(array(
|
||||
'id' => $this->newsletter->id,
|
||||
'status' => Newsletter::STATUS_SENDING
|
||||
));
|
||||
$response = $router->setStatus
|
||||
(array(
|
||||
'id' => $this->newsletter->id,
|
||||
'status' => Newsletter::STATUS_SENDING
|
||||
)
|
||||
);
|
||||
expect($response->status)->equals(APIResponse::STATUS_OK);
|
||||
expect($response->data['status'])->equals(Newsletter::STATUS_SENDING);
|
||||
|
||||
// set status to draft
|
||||
$response = $router->setStatus(array(
|
||||
'id' => $this->newsletter->id,
|
||||
'status' => Newsletter::STATUS_DRAFT
|
||||
));
|
||||
$response = $router->setStatus(
|
||||
array(
|
||||
'id' => $this->newsletter->id,
|
||||
'status' => Newsletter::STATUS_DRAFT
|
||||
)
|
||||
);
|
||||
expect($response->status)->equals(APIResponse::STATUS_OK);
|
||||
expect($response->data['status'])->equals(Newsletter::STATUS_DRAFT);
|
||||
|
||||
// no status specified throws an error
|
||||
$response = $router->setStatus(array(
|
||||
'id' => $this->newsletter->id,
|
||||
));
|
||||
$response = $router->setStatus(
|
||||
array(
|
||||
'id' => $this->newsletter->id,
|
||||
)
|
||||
);
|
||||
expect($response->status)->equals(APIResponse::STATUS_BAD_REQUEST);
|
||||
expect($response->errors[0]['message'])
|
||||
->equals('You need to specify a status.');
|
||||
|
||||
// invalid newsletter id throws an error
|
||||
$response = $router->setStatus(array(
|
||||
'status' => Newsletter::STATUS_DRAFT
|
||||
));
|
||||
$response = $router->setStatus(
|
||||
array(
|
||||
'status' => Newsletter::STATUS_DRAFT
|
||||
)
|
||||
);
|
||||
expect($response->status)->equals(APIResponse::STATUS_NOT_FOUND);
|
||||
expect($response->errors[0]['message'])
|
||||
->equals('This newsletter does not exist.');
|
||||
@ -198,7 +218,8 @@ class NewslettersTest extends MailPoetTest {
|
||||
$response = $router->restore(array('id' => $this->newsletter->id));
|
||||
expect($response->status)->equals(APIResponse::STATUS_OK);
|
||||
expect($response->data)->equals(
|
||||
Newsletter::findOne($this->newsletter->id)->asArray()
|
||||
Newsletter::findOne($this->newsletter->id)
|
||||
->asArray()
|
||||
);
|
||||
expect($response->data['deleted_at'])->null();
|
||||
expect($response->meta['count'])->equals(1);
|
||||
@ -209,7 +230,8 @@ class NewslettersTest extends MailPoetTest {
|
||||
$response = $router->trash(array('id' => $this->newsletter->id));
|
||||
expect($response->status)->equals(APIResponse::STATUS_OK);
|
||||
expect($response->data)->equals(
|
||||
Newsletter::findOne($this->newsletter->id)->asArray()
|
||||
Newsletter::findOne($this->newsletter->id)
|
||||
->asArray()
|
||||
);
|
||||
expect($response->data['deleted_at'])->notNull();
|
||||
expect($response->meta['count'])->equals(1);
|
||||
@ -268,24 +290,30 @@ class NewslettersTest extends MailPoetTest {
|
||||
$segment_2 = Segment::createOrUpdate(array('name' => 'Segment 2'));
|
||||
|
||||
$newsletter_segment = NewsletterSegment::create();
|
||||
$newsletter_segment->hydrate(array(
|
||||
'newsletter_id' => $this->newsletter->id,
|
||||
'segment_id' => $segment_1->id
|
||||
));
|
||||
$newsletter_segment->hydrate(
|
||||
array(
|
||||
'newsletter_id' => $this->newsletter->id,
|
||||
'segment_id' => $segment_1->id
|
||||
)
|
||||
);
|
||||
$newsletter_segment->save();
|
||||
|
||||
$newsletter_segment = NewsletterSegment::create();
|
||||
$newsletter_segment->hydrate(array(
|
||||
'newsletter_id' => $this->newsletter->id,
|
||||
'segment_id' => $segment_2->id
|
||||
));
|
||||
$newsletter_segment->hydrate(
|
||||
array(
|
||||
'newsletter_id' => $this->newsletter->id,
|
||||
'segment_id' => $segment_2->id
|
||||
)
|
||||
);
|
||||
$newsletter_segment->save();
|
||||
|
||||
$newsletter_segment = NewsletterSegment::create();
|
||||
$newsletter_segment->hydrate(array(
|
||||
'newsletter_id' => $this->post_notification->id,
|
||||
'segment_id' => $segment_2->id
|
||||
));
|
||||
$newsletter_segment->hydrate(
|
||||
array(
|
||||
'newsletter_id' => $this->post_notification->id,
|
||||
'segment_id' => $segment_2->id
|
||||
)
|
||||
);
|
||||
$newsletter_segment->save();
|
||||
|
||||
$router = new Newsletters();
|
||||
@ -321,35 +349,43 @@ class NewslettersTest extends MailPoetTest {
|
||||
|
||||
// link standard newsletter to the 2 segments
|
||||
$newsletter_segment = NewsletterSegment::create();
|
||||
$newsletter_segment->hydrate(array(
|
||||
'newsletter_id' => $this->newsletter->id,
|
||||
'segment_id' => $segment_1->id
|
||||
));
|
||||
$newsletter_segment->hydrate(
|
||||
array(
|
||||
'newsletter_id' => $this->newsletter->id,
|
||||
'segment_id' => $segment_1->id
|
||||
)
|
||||
);
|
||||
$newsletter_segment->save();
|
||||
|
||||
$newsletter_segment = NewsletterSegment::create();
|
||||
$newsletter_segment->hydrate(array(
|
||||
'newsletter_id' => $this->newsletter->id,
|
||||
'segment_id' => $segment_2->id
|
||||
));
|
||||
$newsletter_segment->hydrate
|
||||
(array(
|
||||
'newsletter_id' => $this->newsletter->id,
|
||||
'segment_id' => $segment_2->id
|
||||
)
|
||||
);
|
||||
$newsletter_segment->save();
|
||||
|
||||
// link post notification to the 2nd segment
|
||||
$newsletter_segment = NewsletterSegment::create();
|
||||
$newsletter_segment->hydrate(array(
|
||||
'newsletter_id' => $this->post_notification->id,
|
||||
'segment_id' => $segment_2->id
|
||||
));
|
||||
$newsletter_segment->hydrate(
|
||||
array(
|
||||
'newsletter_id' => $this->post_notification->id,
|
||||
'segment_id' => $segment_2->id
|
||||
)
|
||||
);
|
||||
$newsletter_segment->save();
|
||||
|
||||
$router = new Newsletters();
|
||||
|
||||
// filter by 1st segment
|
||||
$response = $router->listing(array(
|
||||
'filter' => array(
|
||||
'segment' => $segment_1->id
|
||||
$response = $router->listing(
|
||||
array(
|
||||
'filter' => array(
|
||||
'segment' => $segment_1->id
|
||||
)
|
||||
)
|
||||
));
|
||||
);
|
||||
|
||||
expect($response->status)->equals(APIResponse::STATUS_OK);
|
||||
|
||||
@ -358,11 +394,13 @@ class NewslettersTest extends MailPoetTest {
|
||||
expect($response->data[0]['subject'])->equals($this->newsletter->subject);
|
||||
|
||||
// filter by 2nd segment
|
||||
$response = $router->listing(array(
|
||||
'filter' => array(
|
||||
'segment' => $segment_2->id
|
||||
$response = $router->listing(
|
||||
array(
|
||||
'filter' => array(
|
||||
'segment' => $segment_2->id
|
||||
)
|
||||
)
|
||||
));
|
||||
);
|
||||
|
||||
expect($response->status)->equals(APIResponse::STATUS_OK);
|
||||
|
||||
@ -373,11 +411,13 @@ class NewslettersTest extends MailPoetTest {
|
||||
function testItCanLimitListing() {
|
||||
$router = new Newsletters();
|
||||
// get 1st page (limit items per page to 1)
|
||||
$response = $router->listing(array(
|
||||
'limit' => 1,
|
||||
'sort_by' => 'subject',
|
||||
'sort_order' => 'asc'
|
||||
));
|
||||
$response = $router->listing(
|
||||
array(
|
||||
'limit' => 1,
|
||||
'sort_by' => 'subject',
|
||||
'sort_order' => 'asc'
|
||||
)
|
||||
);
|
||||
|
||||
expect($response->status)->equals(APIResponse::STATUS_OK);
|
||||
|
||||
@ -388,12 +428,14 @@ class NewslettersTest extends MailPoetTest {
|
||||
);
|
||||
|
||||
// get 1st page (limit items per page to 1)
|
||||
$response = $router->listing(array(
|
||||
'limit' => 1,
|
||||
'offset' => 1,
|
||||
'sort_by' => 'subject',
|
||||
'sort_order' => 'asc'
|
||||
));
|
||||
$response = $router->listing(
|
||||
array(
|
||||
'limit' => 1,
|
||||
'offset' => 1,
|
||||
'sort_by' => 'subject',
|
||||
'sort_order' => 'asc'
|
||||
)
|
||||
);
|
||||
|
||||
expect($response->meta['count'])->equals(2);
|
||||
expect($response->data)->count(1);
|
||||
@ -409,12 +451,14 @@ class NewslettersTest extends MailPoetTest {
|
||||
);
|
||||
|
||||
$router = new Newsletters();
|
||||
$response = $router->bulkAction(array(
|
||||
'listing' => array(
|
||||
'selection' => $selection_ids
|
||||
),
|
||||
'action' => 'delete'
|
||||
));
|
||||
$response = $router->bulkAction(
|
||||
array(
|
||||
'listing' => array(
|
||||
'selection' => $selection_ids
|
||||
),
|
||||
'action' => 'delete'
|
||||
)
|
||||
);
|
||||
|
||||
expect($response->status)->equals(APIResponse::STATUS_OK);
|
||||
expect($response->meta['count'])->equals(count($selection_ids));
|
||||
@ -422,33 +466,86 @@ class NewslettersTest extends MailPoetTest {
|
||||
|
||||
function testItCanBulkDeleteNewsletters() {
|
||||
$router = new Newsletters();
|
||||
$response = $router->bulkAction(array(
|
||||
'action' => 'trash',
|
||||
'listing' => array('group' => 'all')
|
||||
));
|
||||
$response = $router->bulkAction(
|
||||
array(
|
||||
'action' => 'trash',
|
||||
'listing' => array('group' => 'all')
|
||||
)
|
||||
);
|
||||
expect($response->status)->equals(APIResponse::STATUS_OK);
|
||||
expect($response->meta['count'])->equals(2);
|
||||
|
||||
$router = new Newsletters();
|
||||
$response = $router->bulkAction(array(
|
||||
'action' => 'delete',
|
||||
'listing' => array('group' => 'trash')
|
||||
));
|
||||
$response = $router->bulkAction(
|
||||
array(
|
||||
'action' => 'delete',
|
||||
'listing' => array('group' => 'trash')
|
||||
)
|
||||
);
|
||||
expect($response->status)->equals(APIResponse::STATUS_OK);
|
||||
expect($response->meta['count'])->equals(2);
|
||||
|
||||
$response = $router->bulkAction(array(
|
||||
'action' => 'delete',
|
||||
'listing' => array('group' => 'trash')
|
||||
));
|
||||
$response = $router->bulkAction(
|
||||
array(
|
||||
'action' => 'delete',
|
||||
'listing' => array('group' => 'trash')
|
||||
)
|
||||
);
|
||||
expect($response->status)->equals(APIResponse::STATUS_OK);
|
||||
expect($response->meta['count'])->equals(0);
|
||||
}
|
||||
|
||||
function testItCanSendAPreview() {
|
||||
$subscriber = 'test@subscriber.com';
|
||||
$data = array(
|
||||
'subscriber' => $subscriber,
|
||||
'id' => $this->newsletter->id,
|
||||
'mailer' => Stub::makeEmpty(
|
||||
'\MailPoet\Mailer\Mailer',
|
||||
array(
|
||||
'send' => function($newsletter, $subscriber) {
|
||||
expect(is_array($newsletter))->true();
|
||||
expect($newsletter['body']['text'])->contains('Hello test');
|
||||
expect($subscriber)->equals($subscriber);
|
||||
return array('response' => true);
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
$router = new Newsletters();
|
||||
$response = $router->sendPreview($data);
|
||||
expect($response->status)->equals(APIResponse::STATUS_OK);
|
||||
}
|
||||
|
||||
function testItReturnsMaillerErrorWhenSendingFailed() {
|
||||
$subscriber = 'test@subscriber.com';
|
||||
$data = array(
|
||||
'subscriber' => $subscriber,
|
||||
'id' => $this->newsletter->id,
|
||||
'mailer' => Stub::makeEmpty(
|
||||
'\MailPoet\Mailer\Mailer',
|
||||
array(
|
||||
'send' => function($newsletter, $subscriber) {
|
||||
expect(is_array($newsletter))->true();
|
||||
expect($newsletter['body']['text'])->contains('Hello test');
|
||||
expect($subscriber)->equals($subscriber);
|
||||
return array(
|
||||
'response' => false,
|
||||
'error_message' => 'failed'
|
||||
);
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
$router = new Newsletters();
|
||||
$response = $router->sendPreview($data);
|
||||
expect($response->errors[0]['message'])->equals('The email could not be sent: failed');
|
||||
}
|
||||
|
||||
function _after() {
|
||||
Newsletter::deleteMany();
|
||||
NewsletterSegment::deleteMany();
|
||||
NewsletterOptionField::deleteMany();
|
||||
Segment::deleteMany();
|
||||
}
|
||||
}
|
||||
}
|
58
tests/unit/Config/DatabaseTest.php
Normal file
58
tests/unit/Config/DatabaseTest.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
use MailPoet\Config\Database;
|
||||
use MailPoet\Config\Env;
|
||||
|
||||
class DatabaseTestTest extends MailPoetTest {
|
||||
public $database;
|
||||
|
||||
function __construct() {
|
||||
$this->database = new Database();
|
||||
}
|
||||
|
||||
function _before() {
|
||||
\ORM::set_db(null);
|
||||
}
|
||||
|
||||
function testItDefinesTables() {
|
||||
expect(defined('MP_SETTINGS_TABLE'))->true();
|
||||
}
|
||||
|
||||
function testItConfiguresLogging() {
|
||||
expect(\ORM::get_config('logging'))->equals(WP_DEBUG);
|
||||
}
|
||||
|
||||
function testItSetsUpConnection() {
|
||||
expect(\ORM::get_config('username'))->equals(Env::$db_username);
|
||||
expect(\ORM::get_config('password'))->equals(Env::$db_password);
|
||||
}
|
||||
|
||||
function testItSelectivelyUpdatesDriverTimeoutOption() {
|
||||
$current_setting = ORM::for_table("")
|
||||
->raw_query('SELECT @@session.wait_timeout as wait_timeout')
|
||||
->findOne();
|
||||
expect($current_setting)->notEquals(99999);
|
||||
$database = $this->database;
|
||||
$database->driver_option_wait_timeout = 99999;
|
||||
$database->setupDriverOptions();
|
||||
$current_setting = ORM::for_table("")
|
||||
->raw_query('SELECT @@session.wait_timeout as wait_timeout')
|
||||
->findOne();
|
||||
expect($current_setting->wait_timeout)->equals(99999);
|
||||
}
|
||||
|
||||
function testItSetsDBDriverOptions() {
|
||||
$this->database->init();
|
||||
$result = ORM::for_table("")
|
||||
->raw_query(
|
||||
'SELECT ' .
|
||||
'@@sql_mode as sql_mode, ' .
|
||||
'@@session.time_zone as time_zone'
|
||||
)
|
||||
->findOne();
|
||||
// disable ONLY_FULL_GROUP_BY
|
||||
expect($result->sql_mode)->notContains('ONLY_FULL_GROUP_BY');
|
||||
// time zone should be set based on WP's time zone
|
||||
expect($result->time_zone)->equals(Env::$db_timezone_offset);
|
||||
}
|
||||
}
|
@ -59,4 +59,10 @@ class EnvTest extends MailPoetTest {
|
||||
ENV::$db_host . ';port=' . ENV::$db_port . ';dbname=' . DB_NAME;
|
||||
expect(Env::$db_source_name)->equals($source_name);
|
||||
}
|
||||
}
|
||||
|
||||
function testItCanGetDbTimezoneOffset() {
|
||||
expect(Env::getDbTimezoneOffset('+1.5'))->equals("+01:30");
|
||||
expect(Env::getDbTimezoneOffset('+11'))->equals("+11:00");
|
||||
expect(Env::getDbTimezoneOffset('-5.5'))->equals("-05:30");
|
||||
}
|
||||
}
|
10
tests/unit/Config/HooksTest.php
Normal file
10
tests/unit/Config/HooksTest.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
class HooksTest extends MailPoetTest {
|
||||
function testItHooksSchedulerToMultiplePostTypes() {
|
||||
$post_types = get_post_types();
|
||||
foreach($post_types as $post_type) {
|
||||
expect(has_filter('publish_' . $post_type, '\MailPoet\Newsletter\Scheduler\Scheduler::schedulePostNotification'))->notEmpty();
|
||||
}
|
||||
}
|
||||
}
|
13
tests/unit/Config/InitializerTest.php
Normal file
13
tests/unit/Config/InitializerTest.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
class InitializerTest extends MailPoetTest {
|
||||
function testItConfiguresHooks() {
|
||||
global $wp_filter;
|
||||
$is_hooked = false;
|
||||
// mailpoet should hook to 'wp_loaded' with priority of 10
|
||||
foreach($wp_filter['wp_loaded'][10] as $name => $hook) {
|
||||
if(preg_match('/setupHooks/', $name)) $is_hooked = true;
|
||||
}
|
||||
expect($is_hooked)->true();
|
||||
}
|
||||
}
|
38
tests/unit/Config/ShortcodesTest.php
Normal file
38
tests/unit/Config/ShortcodesTest.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use MailPoet\Config\Shortcodes;
|
||||
use MailPoet\Models\Newsletter;
|
||||
use MailPoet\Models\SendingQueue;
|
||||
use MailPoet\Newsletter\Url;
|
||||
use MailPoet\Router\Router;
|
||||
|
||||
class ConfigShortcodesTest extends MailPoetTest {
|
||||
function _before() {
|
||||
$newsletter = Newsletter::create();
|
||||
$newsletter->type = Newsletter::TYPE_STANDARD;
|
||||
$newsletter->status = Newsletter::STATUS_SENT;
|
||||
$this->newsletter = $newsletter->save();
|
||||
$queue = SendingQueue::create();
|
||||
$queue->newsletter_id = $newsletter->id;
|
||||
$queue->status = SendingQueue::STATUS_COMPLETED;
|
||||
$this->queue = $queue->save();
|
||||
}
|
||||
|
||||
function testItGetsArchives() {
|
||||
$shortcodes = new Shortcodes();
|
||||
// result contains a link pointing to the "view in browser" router endpoint
|
||||
$result = $shortcodes->getArchive($params = false);
|
||||
$dom = pQuery::parseStr($result);
|
||||
$link = $dom->query('a');
|
||||
$link = $link->attr('href');
|
||||
expect($link)->contains('endpoint=view_in_browser');
|
||||
// request data object contains newsletter hash but not newsletter id
|
||||
$parsed_link = parse_url($link);
|
||||
parse_str(html_entity_decode($parsed_link['query']), $data);
|
||||
$request_data = Url::transformUrlDataObject(
|
||||
Router::decodeRequestData($data['data'])
|
||||
);
|
||||
expect($request_data['newsletter_id'])->isEmpty();
|
||||
expect($request_data['newsletter_hash'])->equals($this->newsletter->hash);
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ class CronHelperTest extends MailPoetTest {
|
||||
expect(CronHelper::DAEMON_SETTING)->equals('cron_daemon');
|
||||
}
|
||||
|
||||
function testItCanCreateDaemon() {
|
||||
function testItCreatesDaemon() {
|
||||
$token = 'create_token';
|
||||
$time = time();
|
||||
CronHelper::createDaemon($token);
|
||||
@ -26,7 +26,7 @@ class CronHelperTest extends MailPoetTest {
|
||||
);
|
||||
}
|
||||
|
||||
function testItCanRestartDaemon() {
|
||||
function testItRestartsDaemon() {
|
||||
$token = 'restart_token';
|
||||
$time = time();
|
||||
CronHelper::restartDaemon($token);
|
||||
@ -39,7 +39,7 @@ class CronHelperTest extends MailPoetTest {
|
||||
);
|
||||
}
|
||||
|
||||
function testItLoadDaemon() {
|
||||
function testItLoadsDaemon() {
|
||||
$daemon = array(
|
||||
'token' => 'some_token',
|
||||
'updated_at' => '12345678'
|
||||
@ -51,7 +51,7 @@ class CronHelperTest extends MailPoetTest {
|
||||
expect(CronHelper::getDaemon())->equals($daemon);
|
||||
}
|
||||
|
||||
function testItCanSaveDaemon() {
|
||||
function testItSavesDaemon() {
|
||||
// when saving daemon, 'updated_at' value should change
|
||||
$daemon = array(
|
||||
'token' => 'some_token',
|
||||
@ -67,7 +67,7 @@ class CronHelperTest extends MailPoetTest {
|
||||
expect(CronHelper::getDaemon())->equals($daemon);
|
||||
}
|
||||
|
||||
function testItCanCreateRandomToken() {
|
||||
function testItCreatesRandomToken() {
|
||||
// random token is a string of 5 characters
|
||||
$token1 = CronHelper::createToken();
|
||||
$token2 = CronHelper::createToken();
|
||||
@ -76,7 +76,7 @@ class CronHelperTest extends MailPoetTest {
|
||||
expect(strlen($token1))->equals(5);
|
||||
}
|
||||
|
||||
function testItCanGetSiteUrl() {
|
||||
function testItGetsSiteUrl() {
|
||||
// 1. do nothing when the url does not contain port
|
||||
$site_url = 'http://example.com';
|
||||
expect(CronHelper::getSiteUrl($site_url))->equals($site_url);
|
||||
@ -100,7 +100,7 @@ class CronHelperTest extends MailPoetTest {
|
||||
}
|
||||
}
|
||||
|
||||
function testItCanEnforceExecutionLimit() {
|
||||
function testItEnforcesExecutionLimit() {
|
||||
$time = microtime(true);
|
||||
expect(CronHelper::enforceExecutionLimit($time))->null();
|
||||
try {
|
||||
@ -111,6 +111,11 @@ class CronHelperTest extends MailPoetTest {
|
||||
}
|
||||
}
|
||||
|
||||
function testItPingsDaemon() {
|
||||
if(getenv('WP_TEST_ENABLE_NETWORK_TESTS') !== 'true') return;
|
||||
expect(CronHelper::pingDaemon())->true();
|
||||
}
|
||||
|
||||
function _after() {
|
||||
ORM::raw_execute('TRUNCATE ' . Setting::$_table);
|
||||
}
|
||||
|
@ -195,6 +195,15 @@ class DaemonTest extends MailPoetTest {
|
||||
expect(ignore_user_abort())->equals(1);
|
||||
}
|
||||
|
||||
function testItRespondsToPingRequest() {
|
||||
$daemon = Stub::make(new Daemon(true), array(
|
||||
'terminateRequest' => Stub::exactly(1, function($message) {
|
||||
expect($message)->equals('pong');
|
||||
})
|
||||
), $this);
|
||||
$daemon->ping();
|
||||
}
|
||||
|
||||
function _after() {
|
||||
ORM::raw_execute('TRUNCATE ' . Setting::$_table);
|
||||
}
|
||||
|
208
tests/unit/Cron/Workers/BounceTest.php
Normal file
208
tests/unit/Cron/Workers/BounceTest.php
Normal file
@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Codeception\Util\Stub;
|
||||
use MailPoet\API\Endpoints\Cron;
|
||||
use MailPoet\Cron\CronHelper;
|
||||
use MailPoet\Cron\Workers\Bounce;
|
||||
use MailPoet\Cron\Workers\Bounce\API;
|
||||
use MailPoet\Mailer\Mailer;
|
||||
use MailPoet\Models\SendingQueue;
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Util\Helpers;
|
||||
|
||||
require_once('BounceTestMockAPI.php');
|
||||
|
||||
class BounceTest extends MailPoetTest {
|
||||
function _before() {
|
||||
$this->emails = array(
|
||||
'soft_bounce@example.com',
|
||||
'hard_bounce@example.com',
|
||||
'good_address@example.com'
|
||||
);
|
||||
|
||||
foreach ($this->emails as $email) {
|
||||
Subscriber::createOrUpdate(array(
|
||||
'status' => Subscriber::STATUS_SUBSCRIBED,
|
||||
'email' => $email
|
||||
));
|
||||
}
|
||||
|
||||
$this->bounce = new Bounce(microtime(true));
|
||||
|
||||
$api =
|
||||
|
||||
$this->bounce->api = new MailPoet\Cron\Workers\Bounce\MockAPI('key');
|
||||
}
|
||||
|
||||
function testItConstructs() {
|
||||
expect($this->bounce->timer)->notEmpty();
|
||||
}
|
||||
|
||||
function testItDefinesConstants() {
|
||||
expect(Bounce::BATCH_SIZE)->equals(100);
|
||||
}
|
||||
|
||||
function testItChecksIfCurrentSendingMethodIsMailpoet() {
|
||||
expect(Bounce::checkBounceSyncAvailable())->false();
|
||||
$this->setMailPoetSendingMethod();
|
||||
expect(Bounce::checkBounceSyncAvailable())->true();
|
||||
}
|
||||
|
||||
function testItThrowsExceptionWhenExecutionLimitIsReached() {
|
||||
try {
|
||||
$bounce = new Bounce(microtime(true) - CronHelper::DAEMON_EXECUTION_LIMIT);
|
||||
self::fail('Maximum execution time limit exception was not thrown.');
|
||||
} catch(\Exception $e) {
|
||||
expect($e->getMessage())->equals('Maximum execution time has been reached.');
|
||||
}
|
||||
}
|
||||
|
||||
function testItSchedulesBounceSync() {
|
||||
expect(SendingQueue::where('type', 'bounce')->findMany())->isEmpty();
|
||||
Bounce::scheduleBounceSync();
|
||||
expect(SendingQueue::where('type', 'bounce')->findMany())->notEmpty();
|
||||
}
|
||||
|
||||
function testItDoesNotScheduleBounceSyncTwice() {
|
||||
expect(count(SendingQueue::where('type', 'bounce')->findMany()))->equals(0);
|
||||
Bounce::scheduleBounceSync();
|
||||
expect(count(SendingQueue::where('type', 'bounce')->findMany()))->equals(1);
|
||||
Bounce::scheduleBounceSync();
|
||||
expect(count(SendingQueue::where('type', 'bounce')->findMany()))->equals(1);
|
||||
}
|
||||
|
||||
function testItCanGetScheduledQueues() {
|
||||
expect(Bounce::getScheduledQueues())->isEmpty();
|
||||
$this->createScheduledQueue();
|
||||
expect(Bounce::getScheduledQueues())->notEmpty();
|
||||
}
|
||||
|
||||
function testItCanGetRunningQueues() {
|
||||
expect(Bounce::getRunningQueues())->isEmpty();
|
||||
$this->createRunningQueue();
|
||||
expect(Bounce::getRunningQueues())->notEmpty();
|
||||
}
|
||||
|
||||
function testItCanGetAllDueQueues() {
|
||||
expect(Bounce::getAllDueQueues())->isEmpty();
|
||||
|
||||
// scheduled for now
|
||||
$this->createScheduledQueue();
|
||||
|
||||
// running
|
||||
$this->createRunningQueue();
|
||||
|
||||
// scheduled in the future (should not be retrieved)
|
||||
$queue = $this->createScheduledQueue();
|
||||
$queue->scheduled_at = Carbon::createFromTimestamp(current_time('timestamp'))->addDays(7);
|
||||
$queue->save();
|
||||
|
||||
// completed (should not be retrieved)
|
||||
$queue = $this->createRunningQueue();
|
||||
$queue->status = SendingQueue::STATUS_COMPLETED;
|
||||
$queue->save();
|
||||
|
||||
expect(count(Bounce::getAllDueQueues()))->equals(2);
|
||||
}
|
||||
|
||||
function testItCanGetFutureQueues() {
|
||||
expect(Bounce::getFutureQueues())->isEmpty();
|
||||
$queue = $this->createScheduledQueue();
|
||||
$queue->scheduled_at = Carbon::createFromTimestamp(current_time('timestamp'))->addDays(7);
|
||||
$queue->save();
|
||||
expect(count(Bounce::getFutureQueues()))->notEmpty();
|
||||
}
|
||||
|
||||
function testItFailsToProcessWithoutMailPoetMethodSetUp() {
|
||||
expect($this->bounce->process())->false();
|
||||
}
|
||||
|
||||
function testItFailsToProcessWithoutQueues() {
|
||||
$this->setMailPoetSendingMethod();
|
||||
expect($this->bounce->process())->false();
|
||||
}
|
||||
|
||||
function testItProcesses() {
|
||||
$this->setMailPoetSendingMethod();
|
||||
$this->createScheduledQueue();
|
||||
$this->createRunningQueue();
|
||||
expect($this->bounce->process())->true();
|
||||
}
|
||||
|
||||
function testItPreparesBounceQueue() {
|
||||
$queue = $this->createScheduledQueue();
|
||||
expect(empty($queue->subscribers['to_process']))->true();
|
||||
$this->bounce->prepareBounceQueue($queue);
|
||||
expect($queue->status)->null();
|
||||
expect(!empty($queue->subscribers['to_process']))->true();
|
||||
}
|
||||
|
||||
function testItProcessesBounceQueue() {
|
||||
$queue = $this->createRunningQueue();
|
||||
$this->bounce->prepareBounceQueue($queue);
|
||||
expect(!empty($queue->subscribers['to_process']))->true();
|
||||
$this->bounce->processBounceQueue($queue);
|
||||
expect(!empty($queue->subscribers['processed']))->true();
|
||||
}
|
||||
|
||||
function testItSetsSubscriberStatusAsBounced() {
|
||||
$emails = Subscriber::select('email')->findArray();
|
||||
$emails = Helpers::arrayColumn($emails, 'email');
|
||||
|
||||
$this->bounce->processEmails($emails);
|
||||
|
||||
$subscribers = Subscriber::findMany();
|
||||
|
||||
expect($subscribers[0]->status)->equals(Subscriber::STATUS_SUBSCRIBED);
|
||||
expect($subscribers[1]->status)->equals(Subscriber::STATUS_BOUNCED);
|
||||
expect($subscribers[2]->status)->equals(Subscriber::STATUS_SUBSCRIBED);
|
||||
}
|
||||
|
||||
function testItCalculatesNextRunDateWithinNextWeekBoundaries() {
|
||||
$current_date = Carbon::createFromTimestamp(current_time('timestamp'));
|
||||
$next_run_date = Bounce::getNextRunDate();
|
||||
$difference = $next_run_date->diffInDays($current_date);
|
||||
// Subtract days left in the current week
|
||||
$difference -= (Carbon::DAYS_PER_WEEK - $current_date->format('N'));
|
||||
expect($difference)->lessOrEquals(7);
|
||||
expect($difference)->greaterOrEquals(0);
|
||||
}
|
||||
|
||||
private function setMailPoetSendingMethod() {
|
||||
Setting::setValue(
|
||||
Mailer::MAILER_CONFIG_SETTING_NAME,
|
||||
array(
|
||||
'method' => 'MailPoet',
|
||||
'mailpoet_api_key' => 'some_key',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function createScheduledQueue() {
|
||||
$queue = SendingQueue::create();
|
||||
$queue->type = 'bounce';
|
||||
$queue->status = SendingQueue::STATUS_SCHEDULED;
|
||||
$queue->scheduled_at = Carbon::createFromTimestamp(current_time('timestamp'));
|
||||
$queue->newsletter_id = 0;
|
||||
$queue->save();
|
||||
return $queue;
|
||||
}
|
||||
|
||||
private function createRunningQueue() {
|
||||
$queue = SendingQueue::create();
|
||||
$queue->type = 'bounce';
|
||||
$queue->status = null;
|
||||
$queue->scheduled_at = Carbon::createFromTimestamp(current_time('timestamp'));
|
||||
$queue->newsletter_id = 0;
|
||||
$queue->save();
|
||||
return $queue;
|
||||
}
|
||||
|
||||
function _after() {
|
||||
ORM::raw_execute('TRUNCATE ' . Setting::$_table);
|
||||
ORM::raw_execute('TRUNCATE ' . SendingQueue::$_table);
|
||||
ORM::raw_execute('TRUNCATE ' . Subscriber::$_table);
|
||||
}
|
||||
}
|
18
tests/unit/Cron/Workers/BounceTestMockAPI.php
Normal file
18
tests/unit/Cron/Workers/BounceTestMockAPI.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace MailPoet\Cron\Workers\Bounce;
|
||||
|
||||
if(!defined('ABSPATH')) exit;
|
||||
|
||||
class MockAPI {
|
||||
function check(array $emails) {
|
||||
return array_map(
|
||||
function ($email) {
|
||||
return array(
|
||||
'address' => $email,
|
||||
'bounce' => preg_match('/(hard|soft)/', $email, $m) ? $m[1] : null,
|
||||
);
|
||||
},
|
||||
$emails
|
||||
);
|
||||
}
|
||||
}
|
@ -28,8 +28,9 @@ class DateTest extends MailPoetTest {
|
||||
}
|
||||
|
||||
function testItCanConvertMonthToDatetime() {
|
||||
$current_year = date('Y');
|
||||
expect(Date::convertDateToDatetime('05', 'MM'))
|
||||
->equals('2016-05-01 00:00:00');
|
||||
->equals(sprintf('%s-05-01 00:00:00', $current_year));
|
||||
}
|
||||
|
||||
function testItCanConvertYearToDatetime() {
|
||||
@ -54,4 +55,4 @@ class DateTest extends MailPoetTest {
|
||||
expect(Date::convertDateToDatetime('0', 'MM'))
|
||||
->equals('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
64
tests/unit/Form/Block/SelectTest.php
Normal file
64
tests/unit/Form/Block/SelectTest.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
use MailPoet\Form\Block\Select;
|
||||
use \MailPoet\Models\Subscriber;
|
||||
|
||||
class SelectTest extends MailPoetTest {
|
||||
function _before() {
|
||||
$this->block = array(
|
||||
'id' => 'status',
|
||||
'type' => 'select',
|
||||
'params' => array(
|
||||
'required' => true,
|
||||
'label' => 'Status',
|
||||
'values' => array(
|
||||
array(
|
||||
'value' => array(
|
||||
Subscriber::STATUS_SUBSCRIBED => Subscriber::STATUS_SUBSCRIBED
|
||||
),
|
||||
'is_checked' => false
|
||||
),
|
||||
array(
|
||||
'value' => array(
|
||||
Subscriber::STATUS_UNSUBSCRIBED => Subscriber::STATUS_UNSUBSCRIBED
|
||||
),
|
||||
'is_checked' => false
|
||||
),
|
||||
array(
|
||||
'value' => array(
|
||||
Subscriber::STATUS_BOUNCED => Subscriber::STATUS_BOUNCED
|
||||
),
|
||||
'is_checked' => false,
|
||||
'is_disabled' => false,
|
||||
'is_hidden' => false
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function testItRendersSelectBlock() {
|
||||
$rendered = Select::render($this->block);
|
||||
expect($rendered)->contains(Subscriber::STATUS_SUBSCRIBED);
|
||||
expect($rendered)->contains(Subscriber::STATUS_UNSUBSCRIBED);
|
||||
expect($rendered)->contains(Subscriber::STATUS_BOUNCED);
|
||||
}
|
||||
|
||||
function testItRendersSelectedOption() {
|
||||
$this->block['params']['values'][0]['is_checked'] = true;
|
||||
$rendered = Select::render($this->block);
|
||||
expect($rendered)->contains('selected="selected"');
|
||||
}
|
||||
|
||||
function testItRendersDisabledOptions() {
|
||||
$this->block['params']['values'][2]['is_disabled'] = true;
|
||||
$rendered = Select::render($this->block);
|
||||
expect($rendered)->contains('disabled="disabled"');
|
||||
}
|
||||
|
||||
function testItDoesNotRenderHiddenOptions() {
|
||||
$this->block['params']['values'][2]['is_hidden'] = true;
|
||||
$rendered = Select::render($this->block);
|
||||
expect($rendered)->notContains(Subscriber::STATUS_BOUNCED);
|
||||
}
|
||||
}
|
26
tests/unit/Form/Util/StylesTest.php
Normal file
26
tests/unit/Form/Util/StylesTest.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use MailPoet\Form\Util\Styles;
|
||||
|
||||
class StylesTest extends MailPoetTest {
|
||||
function testItSetsDefaultCSSStyles() {
|
||||
expect(property_exists('MailPoet\Form\Util\Styles', 'default_styles'))->true();
|
||||
expect(Styles::$default_styles)->notEmpty();
|
||||
}
|
||||
|
||||
function testItProcessesAndRendersStyles() {
|
||||
$stylesheet = '
|
||||
/* some comment */
|
||||
input[name=first_name] , input.some_class, .some_class { color: red ; background: blue; } .another_style { fonT-siZe: 20px }
|
||||
';
|
||||
$style_processer = new Styles($stylesheet);
|
||||
$extracted_and_prefixed_styles = $style_processer->render($prefix = 'mailpoet');
|
||||
// 1. comments should be stripped
|
||||
// 2. each selector should be refixed
|
||||
// 3. multiple spaces, missing semicolons should be fixed
|
||||
// 4. each style should be on a separate line
|
||||
$expected_result = "mailpoet input[name=first_name], mailpoet input.some_class, mailpoet .some_class { color: red; background: blue; }" . PHP_EOL
|
||||
. "mailpoet .another_style { font-size: 20px; }";
|
||||
expect($extracted_and_prefixed_styles)->equals($expected_result);
|
||||
}
|
||||
}
|
@ -40,6 +40,7 @@ class MailerTest extends MailPoetTest {
|
||||
'name' => 'Reply To',
|
||||
'address' => 'staff@mailinator.com'
|
||||
);
|
||||
$this->return_path = 'bounce@test.com';
|
||||
$this->mailer = array(
|
||||
'method' => 'MailPoet',
|
||||
'mailpoet_api_key' => getenv('WP_TEST_MAILER_MAILPOET_API') ?
|
||||
@ -59,7 +60,6 @@ class MailerTest extends MailPoetTest {
|
||||
function testItRequiresMailerMethod() {
|
||||
// reset mta settings so that we have no default mailer
|
||||
Setting::setValue('mta', null);
|
||||
|
||||
try {
|
||||
$mailer = new Mailer();
|
||||
$this->fail('Mailer did not throw an exception');
|
||||
@ -78,11 +78,12 @@ class MailerTest extends MailPoetTest {
|
||||
}
|
||||
|
||||
function testItCanConstruct() {
|
||||
$mailer = new Mailer($this->mailer, $this->sender, $this->reply_to);
|
||||
$mailer = new Mailer($this->mailer, $this->sender, $this->reply_to, $this->return_path);
|
||||
expect($mailer->sender['from_name'])->equals($this->sender['name']);
|
||||
expect($mailer->sender['from_email'])->equals($this->sender['address']);
|
||||
expect($mailer->reply_to['reply_to_name'])->equals($this->reply_to['name']);
|
||||
expect($mailer->reply_to['reply_to_email'])->equals($this->reply_to['address']);
|
||||
expect($mailer->return_path)->equals($this->return_path);
|
||||
}
|
||||
|
||||
function testItCanBuildKnownMailerInstances() {
|
||||
@ -103,7 +104,6 @@ class MailerTest extends MailPoetTest {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function testItSetsReplyToAddressWhenOnlyNameIsAvailable() {
|
||||
$reply_to = array('name' => 'test');
|
||||
$mailer = new Mailer($this->mailer, $this->sender, $reply_to);
|
||||
@ -111,6 +111,15 @@ class MailerTest extends MailPoetTest {
|
||||
expect($reply_to['reply_to_email'])->equals($this->sender['address']);
|
||||
}
|
||||
|
||||
function testItGetsReturnPathAddress() {
|
||||
$mailer = new Mailer($this->mailer, $this->sender, $this->reply_to);
|
||||
$return_path = $mailer->getReturnPathAddress('bounce@test.com');
|
||||
expect($return_path)->equals('bounce@test.com');
|
||||
Setting::setValue('bounce', array('address' => 'settngs_bounce@test.com'));
|
||||
$return_path = $mailer->getReturnPathAddress($return_path = false);
|
||||
expect($return_path)->equals('settngs_bounce@test.com');
|
||||
}
|
||||
|
||||
function testItCanTransformSubscriber() {
|
||||
$mailer = new Mailer($this->mailer, $this->sender, $this->reply_to);
|
||||
expect($mailer->formatSubscriberNameAndEmailAddress('test@email.com'))
|
||||
|
@ -26,12 +26,14 @@ class AmazonSESTest extends MailPoetTest {
|
||||
'reply_to_email' => 'reply-to@mailpoet.com',
|
||||
'reply_to_name_email' => 'Reply To <reply-to@mailpoet.com>'
|
||||
);
|
||||
$this->return_path = 'bounce@mailpoet.com';
|
||||
$this->mailer = new AmazonSES(
|
||||
$this->settings['region'],
|
||||
$this->settings['access_key'],
|
||||
$this->settings['secret_key'],
|
||||
$this->sender,
|
||||
$this->reply_to
|
||||
$this->reply_to,
|
||||
$this->return_path
|
||||
);
|
||||
$this->subscriber = 'Recipient <mailpoet-phoenix-test@mailinator.com>';
|
||||
$this->newsletter = array(
|
||||
@ -56,6 +58,18 @@ class AmazonSESTest extends MailPoetTest {
|
||||
expect(preg_match('!^\d{8}$!', $this->mailer->date_without_time))->equals(1);
|
||||
}
|
||||
|
||||
function testWhenReturnPathIsNullItIsSetToSenderEmail() {
|
||||
$mailer = new AmazonSES(
|
||||
$this->settings['region'],
|
||||
$this->settings['access_key'],
|
||||
$this->settings['secret_key'],
|
||||
$this->sender,
|
||||
$this->reply_to,
|
||||
$return_path = false
|
||||
);
|
||||
expect($mailer->return_path)->equals($this->sender['from_email']);
|
||||
}
|
||||
|
||||
function testItChecksForValidRegion() {
|
||||
try {
|
||||
$mailer = new AmazonSES(
|
||||
@ -63,7 +77,8 @@ class AmazonSESTest extends MailPoetTest {
|
||||
$this->settings['access_key'],
|
||||
$this->settings['secret_key'],
|
||||
$this->sender,
|
||||
$this->reply_to
|
||||
$this->reply_to,
|
||||
$this->return_path
|
||||
);
|
||||
$this->fail('Unsupported region exception was not thrown');
|
||||
} catch(\Exception $e) {
|
||||
@ -86,7 +101,7 @@ class AmazonSESTest extends MailPoetTest {
|
||||
->equals($this->newsletter['body']['html']);
|
||||
expect($body['Message.Body.Text.Data'])
|
||||
->equals($this->newsletter['body']['text']);
|
||||
expect($body['ReturnPath'])->equals($this->sender['from_name_email']);
|
||||
expect($body['ReturnPath'])->equals($this->return_path);
|
||||
}
|
||||
|
||||
function testItCanCreateRequest() {
|
||||
|
@ -14,9 +14,11 @@ class PHPMailTest extends MailPoetTest {
|
||||
'reply_to_email' => 'reply-to@mailpoet.com',
|
||||
'reply_to_name_email' => 'Reply To <reply-to@mailpoet.com>'
|
||||
);
|
||||
$this->return_path = 'bounce@mailpoet.com';
|
||||
$this->mailer = new PHPMail(
|
||||
$this->sender,
|
||||
$this->reply_to
|
||||
$this->reply_to,
|
||||
$this->return_path
|
||||
);
|
||||
$this->subscriber = 'Recipient <mailpoet-phoenix-test@mailinator.com>';
|
||||
$this->newsletter = array(
|
||||
@ -33,12 +35,23 @@ class PHPMailTest extends MailPoetTest {
|
||||
expect($mailer->getTransport() instanceof \Swift_MailTransport)->true();
|
||||
}
|
||||
|
||||
function testWhenReturnPathIsNullItIsSetToSenderEmail() {
|
||||
$mailer = new PHPMail(
|
||||
$this->sender,
|
||||
$this->reply_to,
|
||||
$return_path = false
|
||||
);
|
||||
expect($mailer->return_path)->equals($this->sender['from_email']);
|
||||
}
|
||||
|
||||
function testItCanCreateMessage() {
|
||||
$message = $this->mailer->createMessage($this->newsletter, $this->subscriber);
|
||||
expect($message->getTo())
|
||||
->equals(array('mailpoet-phoenix-test@mailinator.com' => 'Recipient'));
|
||||
expect($message->getFrom())
|
||||
->equals(array($this->sender['from_email'] => $this->sender['from_name']));
|
||||
expect($message->getSender())
|
||||
->equals(array($this->sender['from_email'] => null));
|
||||
expect($message->getReplyTo())
|
||||
->equals(array($this->reply_to['reply_to_email'] => $this->reply_to['reply_to_name']));
|
||||
expect($message->getSubject())
|
||||
|
@ -29,6 +29,7 @@ class SMTPTest extends MailPoetTest {
|
||||
'reply_to_email' => 'reply-to@mailpoet.com',
|
||||
'reply_to_name_email' => 'Reply To <reply-to@mailpoet.com>'
|
||||
);
|
||||
$this->return_path = 'bounce@mailpoet.com';
|
||||
$this->mailer = new SMTP(
|
||||
$this->settings['host'],
|
||||
$this->settings['port'],
|
||||
@ -37,7 +38,8 @@ class SMTPTest extends MailPoetTest {
|
||||
$this->settings['password'],
|
||||
$this->settings['encryption'],
|
||||
$this->sender,
|
||||
$this->reply_to
|
||||
$this->reply_to,
|
||||
$this->return_path
|
||||
);
|
||||
$this->subscriber = 'Recipient <mailpoet-phoenix-test@mailinator.com>';
|
||||
$this->newsletter = array(
|
||||
@ -63,12 +65,29 @@ class SMTPTest extends MailPoetTest {
|
||||
->equals($this->settings['encryption']);
|
||||
}
|
||||
|
||||
function testWhenReturnPathIsNullItIsSetToSenderEmail() {
|
||||
$mailer = new SMTP(
|
||||
$this->settings['host'],
|
||||
$this->settings['port'],
|
||||
$this->settings['authentication'],
|
||||
$this->settings['login'],
|
||||
$this->settings['password'],
|
||||
$this->settings['encryption'],
|
||||
$this->sender,
|
||||
$this->reply_to,
|
||||
$return_path = false
|
||||
);
|
||||
expect($mailer->return_path)->equals($this->sender['from_email']);
|
||||
}
|
||||
|
||||
function testItCanCreateMessage() {
|
||||
$message = $this->mailer->createMessage($this->newsletter, $this->subscriber);
|
||||
expect($message->getTo())
|
||||
->equals(array('mailpoet-phoenix-test@mailinator.com' => 'Recipient'));
|
||||
expect($message->getFrom())
|
||||
->equals(array($this->sender['from_email'] => $this->sender['from_name']));
|
||||
expect($message->getSender())
|
||||
->equals(array($this->sender['from_email'] => null));
|
||||
expect($message->getReplyTo())
|
||||
->equals(array($this->reply_to['reply_to_email'] => $this->reply_to['reply_to_name']));
|
||||
expect($message->getSubject())
|
||||
|
@ -8,6 +8,8 @@ use MailPoet\Models\NewsletterSegment;
|
||||
use MailPoet\Models\NewsletterOptionField;
|
||||
use MailPoet\Models\NewsletterOption;
|
||||
use MailPoet\Models\StatisticsOpens;
|
||||
use MailPoet\Models\StatisticsClicks;
|
||||
use MailPoet\Models\StatisticsUnsubscribes;
|
||||
|
||||
class NewsletterTest extends MailPoetTest {
|
||||
function _before() {
|
||||
@ -106,6 +108,17 @@ class NewsletterTest extends MailPoetTest {
|
||||
expect($newsletter_segments[1]['name'])->equals('Segment 2');
|
||||
}
|
||||
|
||||
function testItCanHaveDeletedSegments() {
|
||||
$this->segment_2->delete();
|
||||
$this->newsletter->withSegments(true);
|
||||
$newsletter_segments = $this->newsletter->segments;
|
||||
expect($newsletter_segments)->count(2);
|
||||
expect($newsletter_segments[0]['id'])->equals($this->segment_1->id);
|
||||
expect($newsletter_segments[0]['name'])->equals('Segment 1');
|
||||
expect($newsletter_segments[1]['id'])->equals($this->segment_2->id);
|
||||
expect($newsletter_segments[1]['name'])->contains('Deleted');
|
||||
}
|
||||
|
||||
function testItCanHaveStatistics() {
|
||||
$newsletter = $this->newsletter;
|
||||
$sending_queue = SendingQueue::create();
|
||||
@ -124,11 +137,25 @@ class NewsletterTest extends MailPoetTest {
|
||||
$opens->queue_id = $sending_queue->id;
|
||||
$opens->save();
|
||||
|
||||
$clicks = StatisticsClicks::create();
|
||||
$clicks->subscriber_id = $subscriber->id;
|
||||
$clicks->newsletter_id = $this->newsletter->id;
|
||||
$clicks->queue_id = $sending_queue->id;
|
||||
$clicks->link_id = 0;
|
||||
$clicks->count = 0;
|
||||
$clicks->save();
|
||||
|
||||
$unsubscribes = StatisticsUnsubscribes::create();
|
||||
$unsubscribes->subscriber_id = $subscriber->id;
|
||||
$unsubscribes->newsletter_id = $this->newsletter->id;
|
||||
$unsubscribes->queue_id = $sending_queue->id;
|
||||
$unsubscribes->save();
|
||||
|
||||
$newsletter->queue = $newsletter->getQueue()->asArray();
|
||||
$statistics = $newsletter->getStatistics( $sending_queue->id);
|
||||
expect($statistics->opened)->equals(1);
|
||||
expect($statistics->clicked)->equals(0);
|
||||
expect($statistics->unsubscribed)->equals(0);
|
||||
expect($statistics['opened'])->equals(1);
|
||||
expect($statistics['clicked'])->equals(1);
|
||||
expect($statistics['unsubscribed'])->equals(1);
|
||||
}
|
||||
|
||||
function testItCanCreateOrUpdate() {
|
||||
@ -245,6 +272,118 @@ class NewsletterTest extends MailPoetTest {
|
||||
expect($newsletter->Event)->equals($association->value);
|
||||
}
|
||||
|
||||
function testItGetsArchiveNewslettersForSegments() {
|
||||
// clear the DB
|
||||
$this->_after();
|
||||
|
||||
$types = array(
|
||||
Newsletter::TYPE_STANDARD,
|
||||
Newsletter::TYPE_NOTIFICATION_HISTORY
|
||||
);
|
||||
$newsletters = array();
|
||||
$sending_queues[] = array();
|
||||
for($i = 0; $i < count($types); $i++) {
|
||||
$newsletters[$i] = Newsletter::createOrUpdate(
|
||||
array(
|
||||
'subject' => 'My Standard Newsletter',
|
||||
'preheader' => 'Pre Header',
|
||||
'type' => $types[$i]
|
||||
)
|
||||
);
|
||||
$sending_queues[$i] = SendingQueue::create();
|
||||
$sending_queues[$i]->newsletter_id = $newsletters[$i]->id;
|
||||
$sending_queues[$i]->status = SendingQueue::STATUS_COMPLETED;
|
||||
$sending_queues[$i]->save();
|
||||
}
|
||||
// set segment association for the last newsletter
|
||||
$newsletter_segment = NewsletterSegment::create();
|
||||
$newsletter_segment->newsletter_id = end($newsletters[1])->id;
|
||||
$newsletter_segment->segment_id = 123;
|
||||
$newsletter_segment->save();
|
||||
|
||||
expect(Newsletter::findMany())->count(2);
|
||||
|
||||
// return archives in segment 123
|
||||
$results = Newsletter::getArchives(array(123));
|
||||
expect($results)->count(1);
|
||||
expect($results[0]->id)->equals($newsletters[1]->id);
|
||||
expect($results[0]->type)->equals(Newsletter::TYPE_NOTIFICATION_HISTORY);
|
||||
}
|
||||
|
||||
function testItGetsAllArchiveNewsletters() {
|
||||
// clear the DB
|
||||
$this->_after();
|
||||
|
||||
$types = array(
|
||||
Newsletter::TYPE_STANDARD,
|
||||
Newsletter::TYPE_STANDARD, // should be returned
|
||||
Newsletter::TYPE_WELCOME,
|
||||
Newsletter::TYPE_NOTIFICATION,
|
||||
Newsletter::TYPE_NOTIFICATION_HISTORY, // should be returned
|
||||
Newsletter::TYPE_NOTIFICATION_HISTORY
|
||||
);
|
||||
$newsletters = array();
|
||||
$sending_queues[] = array();
|
||||
for($i = 0; $i < count($types); $i++) {
|
||||
$newsletters[$i] = Newsletter::createOrUpdate(
|
||||
array(
|
||||
'subject' => 'My Standard Newsletter',
|
||||
'preheader' => 'Pre Header',
|
||||
'type' => $types[$i]
|
||||
)
|
||||
);
|
||||
$sending_queues[$i] = SendingQueue::create();
|
||||
$sending_queues[$i]->newsletter_id = $newsletters[$i]->id;
|
||||
$sending_queues[$i]->status = SendingQueue::STATUS_COMPLETED;
|
||||
$sending_queues[$i]->save();
|
||||
}
|
||||
// set teh sending queue status of the first newsletter to null
|
||||
$sending_queues[0]->status = null;
|
||||
$sending_queues[0]->save();
|
||||
|
||||
// trash the last newsletter
|
||||
end($newsletters)->trash();
|
||||
|
||||
expect(Newsletter::findMany())->count(6);
|
||||
|
||||
// archives return only:
|
||||
// 1. STANDARD and NOTIFICATION HISTORY newsletters
|
||||
// 2. active newsletters (i.e., not trashed)
|
||||
// 3. with sending queue records that are COMPLETED
|
||||
$results = Newsletter::getArchives();
|
||||
expect($results)->count(2);
|
||||
expect($results[0]->id)->equals($newsletters[1]->id);
|
||||
expect($results[0]->type)->equals(Newsletter::TYPE_STANDARD);
|
||||
expect($results[1]->id)->equals($newsletters[4]->id);
|
||||
expect($results[1]->type)->equals(Newsletter::TYPE_NOTIFICATION_HISTORY);
|
||||
}
|
||||
|
||||
function testItGeneratesHashOnNewsletterSave() {
|
||||
expect(strlen($this->newsletter->hash))
|
||||
->equals(Newsletter::NEWSLETTER_HASH_LENGTH);
|
||||
}
|
||||
|
||||
function testItRegeneratesHashOnNewsletterDuplication() {
|
||||
$duplicate_newsletter = $this->newsletter->duplicate();
|
||||
expect($duplicate_newsletter->hash)->notEquals($this->newsletter->hash);
|
||||
expect(strlen($duplicate_newsletter->hash))
|
||||
->equals(Newsletter::NEWSLETTER_HASH_LENGTH);
|
||||
}
|
||||
|
||||
function testItRegeneratesHashOnNotificationHistoryCreation() {
|
||||
$notification_history = $this->newsletter->createNotificationHistory();
|
||||
expect($notification_history->hash)->notEquals($this->newsletter->hash);
|
||||
expect(strlen($notification_history->hash))
|
||||
->equals(Newsletter::NEWSLETTER_HASH_LENGTH);
|
||||
}
|
||||
|
||||
function testItGetsQueueFromNewsletter() {
|
||||
$queue = SendingQueue::create();
|
||||
$queue->newsletter_id = $this->newsletter->id;
|
||||
$queue->save();
|
||||
expect($this->newsletter->queue()->findOne()->id)->equals($queue->id);
|
||||
}
|
||||
|
||||
function _after() {
|
||||
ORM::raw_execute('TRUNCATE ' . NewsletterOption::$_table);
|
||||
ORM::raw_execute('TRUNCATE ' . Newsletter::$_table);
|
||||
@ -253,5 +392,7 @@ class NewsletterTest extends MailPoetTest {
|
||||
ORM::raw_execute('TRUNCATE ' . NewsletterSegment::$_table);
|
||||
ORM::raw_execute('TRUNCATE ' . SendingQueue::$_table);
|
||||
ORM::raw_execute('TRUNCATE ' . StatisticsOpens::$_table);
|
||||
ORM::raw_execute('TRUNCATE ' . StatisticsClicks::$_table);
|
||||
ORM::raw_execute('TRUNCATE ' . StatisticsUnsubscribes::$_table);
|
||||
}
|
||||
}
|
||||
}
|
@ -4,12 +4,12 @@ use Carbon\Carbon;
|
||||
use Codeception\Util\Fixtures;
|
||||
use MailPoet\Models\CustomField;
|
||||
use MailPoet\Models\Segment;
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Models\SubscriberCustomField;
|
||||
use MailPoet\Models\SubscriberSegment;
|
||||
|
||||
class SubscriberTest extends MailPoetTest {
|
||||
|
||||
function _before() {
|
||||
$this->data = array(
|
||||
'first_name' => 'John',
|
||||
@ -365,6 +365,122 @@ class SubscriberTest extends MailPoetTest {
|
||||
expect($subscriber->deleted_at)->equals(null);
|
||||
}
|
||||
|
||||
function testItOverwritesSubscriberDataWhenConfirmationIsDisabled() {
|
||||
$original_setting_value = Setting::getValue('signup_confirmation.enabled');
|
||||
Setting::setValue('signup_confirmation.enabled', false);
|
||||
|
||||
$segment = Segment::create();
|
||||
$segment->hydrate(array('name' => 'List #1'));
|
||||
$segment->save();
|
||||
|
||||
$segment2 = Segment::create();
|
||||
$segment2->hydrate(array('name' => 'List #2'));
|
||||
$segment2->save();
|
||||
|
||||
$data = array(
|
||||
'email' => 'some@example.com',
|
||||
'first_name' => 'Some',
|
||||
'last_name' => 'Example',
|
||||
);
|
||||
|
||||
$subscriber = Subscriber::subscribe(
|
||||
$data,
|
||||
array($segment->id())
|
||||
);
|
||||
|
||||
expect($subscriber->id() > 0)->equals(true);
|
||||
expect($subscriber->segments()->count())->equals(1);
|
||||
expect($subscriber->email)->equals($data['email']);
|
||||
expect($subscriber->first_name)->equals($data['first_name']);
|
||||
expect($subscriber->last_name)->equals($data['last_name']);
|
||||
|
||||
$data2 = $data;
|
||||
$data2['first_name'] = 'Aaa';
|
||||
$data2['last_name'] = 'Bbb';
|
||||
|
||||
$subscriber = Subscriber::subscribe(
|
||||
$data2,
|
||||
array($segment->id(), $segment2->id())
|
||||
);
|
||||
|
||||
expect($subscriber->id() > 0)->equals(true);
|
||||
expect($subscriber->segments()->count())->equals(2);
|
||||
expect($subscriber->email)->equals($data2['email']);
|
||||
expect($subscriber->first_name)->equals($data2['first_name']);
|
||||
expect($subscriber->last_name)->equals($data2['last_name']);
|
||||
|
||||
Setting::setValue('signup_confirmation.enabled', $original_setting_value);
|
||||
}
|
||||
|
||||
function testItStoresUnconfirmedSubscriberDataWhenConfirmationIsEnabled() {
|
||||
$original_setting_value = Setting::getValue('signup_confirmation.enabled');
|
||||
Setting::setValue('signup_confirmation.enabled', true);
|
||||
|
||||
$segment = Segment::create();
|
||||
$segment->hydrate(array('name' => 'List #1'));
|
||||
$segment->save();
|
||||
|
||||
$segment2 = Segment::create();
|
||||
$segment2->hydrate(array('name' => 'List #2'));
|
||||
$segment2->save();
|
||||
|
||||
$data = array(
|
||||
'email' => 'some@example.com',
|
||||
'first_name' => 'Some',
|
||||
'last_name' => 'Example',
|
||||
);
|
||||
|
||||
$subscriber = Subscriber::subscribe(
|
||||
$data,
|
||||
array($segment->id())
|
||||
);
|
||||
|
||||
expect($subscriber->id() > 0)->equals(true);
|
||||
expect($subscriber->segments()->count())->equals(1);
|
||||
expect($subscriber->email)->equals($data['email']);
|
||||
expect($subscriber->first_name)->equals($data['first_name']);
|
||||
expect($subscriber->last_name)->equals($data['last_name']);
|
||||
|
||||
expect($subscriber->unconfirmed_data)->isEmpty();
|
||||
|
||||
$data2 = $data;
|
||||
$data2['first_name'] = 'Aaa';
|
||||
$data2['last_name'] = 'Bbb';
|
||||
|
||||
$subscriber = Subscriber::subscribe(
|
||||
$data2,
|
||||
array($segment->id(), $segment2->id())
|
||||
);
|
||||
|
||||
expect($subscriber->id() > 0)->equals(true);
|
||||
expect($subscriber->segments()->count())->equals(2);
|
||||
// fields should be left intact
|
||||
expect($subscriber->email)->equals($data['email']);
|
||||
expect($subscriber->first_name)->equals($data['first_name']);
|
||||
expect($subscriber->last_name)->equals($data['last_name']);
|
||||
|
||||
expect($subscriber->unconfirmed_data)->notEmpty();
|
||||
expect($subscriber->unconfirmed_data)->equals(json_encode($data2));
|
||||
|
||||
// Unconfirmed data should be wiped after any direct update
|
||||
// during confirmation, manual admin editing
|
||||
$subscriber = Subscriber::createOrUpdate($data2);
|
||||
expect($subscriber->unconfirmed_data)->isEmpty();
|
||||
// during import
|
||||
$subscriber->unconfirmed_data = json_encode($data2);
|
||||
$subscriber->save();
|
||||
expect($subscriber->isDirty('unconfirmed_data'))->false();
|
||||
expect($subscriber->unconfirmed_data)->notEmpty();
|
||||
Subscriber::updateMultiple(
|
||||
array_keys($data2),
|
||||
array(array_values($data2))
|
||||
);
|
||||
$subscriber = Subscriber::where('email', $data2['email'])->findOne();
|
||||
expect($subscriber->unconfirmed_data)->isEmpty();
|
||||
|
||||
Setting::setValue('signup_confirmation.enabled', $original_setting_value);
|
||||
}
|
||||
|
||||
function testItCanBeUpdatedByEmail() {
|
||||
$subscriber_updated = Subscriber::createOrUpdate(array(
|
||||
'email' => $this->data['email'],
|
||||
@ -555,6 +671,23 @@ class SubscriberTest extends MailPoetTest {
|
||||
expect($total)->equals(1);
|
||||
}
|
||||
|
||||
function testItGeneratesSubscriberToken() {
|
||||
$token = Subscriber::generateToken($this->data['email']);
|
||||
expect(strlen($token))->equals(Subscriber::SUBSCRIBER_TOKEN_LENGTH);
|
||||
}
|
||||
|
||||
function testItVerifiesSubscriberToken() {
|
||||
$token = Subscriber::generateToken($this->data['email']);
|
||||
expect(Subscriber::verifyToken($this->data['email'], $token))->true();
|
||||
expect(Subscriber::verifyToken('fake@email.com', $token))->false();
|
||||
}
|
||||
|
||||
function testVerifiedTokensOfDifferentLengths() {
|
||||
$token = md5(AUTH_KEY . $this->data['email']);
|
||||
expect(strlen($token))->notEquals(Subscriber::SUBSCRIBER_TOKEN_LENGTH);
|
||||
expect(Subscriber::verifyToken($this->data['email'], $token))->true();
|
||||
}
|
||||
|
||||
function _after() {
|
||||
ORM::raw_execute('TRUNCATE ' . Subscriber::$_table);
|
||||
ORM::raw_execute('TRUNCATE ' . Segment::$_table);
|
||||
@ -562,4 +695,4 @@ class SubscriberTest extends MailPoetTest {
|
||||
ORM::raw_execute('TRUNCATE ' . CustomField::$_table);
|
||||
ORM::raw_execute('TRUNCATE ' . SubscriberCustomField::$_table);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ use MailPoet\Models\NewsletterLink;
|
||||
use MailPoet\Models\SendingQueue;
|
||||
use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Newsletter\Links\Links;
|
||||
use MailPoet\Newsletter\Shortcodes\Categories\Link;
|
||||
use MailPoet\Router\Router;
|
||||
|
||||
class LinksTest extends MailPoetTest {
|
||||
@ -46,6 +47,29 @@ class LinksTest extends MailPoetTest {
|
||||
expect($updated_content)->notContains('link');
|
||||
}
|
||||
|
||||
function testItCreatesAndTransformsUrlDataObject() {
|
||||
$subscriber_email = 'test@example.com';
|
||||
$data = array(
|
||||
'subscriber_id' => 1,
|
||||
'subscriber_token' => Subscriber::generateToken($subscriber_email),
|
||||
'queue_id' => 2,
|
||||
'link_hash' => 'hash',
|
||||
'preview' => false
|
||||
);
|
||||
$url_data_object = Links::createUrlDataObject(
|
||||
$data['subscriber_id'],
|
||||
$subscriber_email,
|
||||
$data['queue_id'],
|
||||
$data['link_hash'],
|
||||
$data['preview']
|
||||
);
|
||||
// URL data object should be an indexed array
|
||||
expect($url_data_object)->equals(array_values($data));
|
||||
// transformed URL object should be an associative array
|
||||
$transformed_url_data_object = Links::transformUrlDataObject($url_data_object);
|
||||
expect($transformed_url_data_object)->equals($data);
|
||||
}
|
||||
|
||||
function testItReplacesHashedLinksWithSubscriberData() {
|
||||
$subscriber = Subscriber::create();
|
||||
$subscriber->hydrate(Fixtures::get('subscriber_template'));
|
||||
@ -70,6 +94,7 @@ class LinksTest extends MailPoetTest {
|
||||
preg_match_all('/data=(?P<data>.*?)"/', $result, $result);
|
||||
foreach($result['data'] as $data) {
|
||||
$data = Router::decodeRequestData($data);
|
||||
$data = Links::transformUrlDataObject($data);
|
||||
expect($data['subscriber_id'])->equals($subscriber->id);
|
||||
expect($data['queue_id'])->equals($queue->id);
|
||||
expect(isset($data['subscriber_token']))->true();
|
||||
@ -97,6 +122,52 @@ class LinksTest extends MailPoetTest {
|
||||
expect($newsltter_link->url)->equals('http://example.com');
|
||||
}
|
||||
|
||||
function testItMatchesHashedLinks() {
|
||||
$regex = Links::getLinkRegex();
|
||||
expect((boolean)preg_match($regex, '[some_tag]-123'))->false();
|
||||
expect((boolean)preg_match($regex, '[some_tag]'))->false();
|
||||
expect((boolean)preg_match($regex, '[mailpoet_click_data]-123'))->true();
|
||||
expect((boolean)preg_match($regex, '[mailpoet_open_data]'))->true();
|
||||
}
|
||||
|
||||
function testItCanConvertOnlyHashedLinkShortcodes() {
|
||||
// create newsletter link association
|
||||
$newsletter_link = NewsletterLink::create();
|
||||
$newsletter_link->newsletter_id = 1;
|
||||
$newsletter_link->queue_id = 1;
|
||||
$newsletter_link->hash = '90e56';
|
||||
$newsletter_link->url = '[link:newsletter_view_in_browser_url]';
|
||||
$newsletter_link = $newsletter_link->save();
|
||||
$content = '
|
||||
<a href="[mailpoet_click_data]-90e56">View in browser</a>
|
||||
<a href="[mailpoet_click_data]-123">Some link</a>';
|
||||
$result = Links::convertHashedLinksToShortcodesAndUrls($content);
|
||||
expect($result)->contains($newsletter_link->url);
|
||||
expect($result)->contains('[mailpoet_click_data]-123');
|
||||
}
|
||||
|
||||
function testItCanConvertAllHashedLinksToUrls() {
|
||||
// create newsletter link associations
|
||||
$newsletter_link_1 = NewsletterLink::create();
|
||||
$newsletter_link_1->newsletter_id = 1;
|
||||
$newsletter_link_1->queue_id = 1;
|
||||
$newsletter_link_1->hash = '90e56';
|
||||
$newsletter_link_1->url = '[link:newsletter_view_in_browser_url]';
|
||||
$newsletter_link_1 = $newsletter_link_1->save();
|
||||
$newsletter_link_2 = NewsletterLink::create();
|
||||
$newsletter_link_2->newsletter_id = 1;
|
||||
$newsletter_link_2->queue_id = 1;
|
||||
$newsletter_link_2->hash = '123';
|
||||
$newsletter_link_2->url = 'http://google.com';
|
||||
$newsletter_link_2 = $newsletter_link_2->save();
|
||||
$content = '
|
||||
<a href="[mailpoet_click_data]-90e56">View in browser</a>
|
||||
<a href="[mailpoet_click_data]-123">Some link</a>';
|
||||
$result = Links::convertHashedLinksToShortcodesAndUrls($content, $convert_all = true);
|
||||
expect($result)->contains($newsletter_link_1->url);
|
||||
expect($result)->contains($newsletter_link_2->url);
|
||||
}
|
||||
|
||||
function _after() {
|
||||
ORM::raw_execute('TRUNCATE ' . Subscriber::$_table);
|
||||
ORM::raw_execute('TRUNCATE ' . SendingQueue::$_table);
|
||||
|
@ -212,6 +212,13 @@ class NewsletterRendererTest extends MailPoetTest {
|
||||
expect(
|
||||
!empty($DOM('tr > td > table > tr > td.mailpoet_blockquote', 0)->html()
|
||||
))->true();
|
||||
// blockquote should contain heading elements but not paragraphs
|
||||
expect(
|
||||
$DOM('tr > td > table > tr > td.mailpoet_blockquote', 0)->html()
|
||||
)->contains('<h2');
|
||||
expect(
|
||||
$DOM('tr > td > table > tr > td.mailpoet_blockquote', 0)->html()
|
||||
)->notContains('<p');
|
||||
// ul/ol/li should have mailpoet_paragraph class added & styles applied
|
||||
expect(
|
||||
!empty(
|
||||
@ -366,4 +373,4 @@ class NewsletterRendererTest extends MailPoetTest {
|
||||
expect(preg_match('/mailpoet_template.*?important/s', $template['html']))
|
||||
->equals(0);
|
||||
}
|
||||
}
|
||||
}
|
@ -55,7 +55,7 @@
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "<h1 style=\"text-align: left;\">1/1 Column</h1>\n<h1 style=\"text-align: center;\">1/1 Column</h1>\n<h1 style=\"text-align: right;\">1/1 Column</h1>\n<h1><a href=\"http://www.example.com\">1/1 Column</a></h1>\n<h2>Heading (size 2)</h2>\n<p>Paragraph under heading to test line-height.</p>\n<h3>Heading (size 3)</h3>\n<h3>Heading (size 3) <a href=\"http://www.example.org\">with link</a></h3>\n<h3><strong>Heading</strong> (size 3)</h3>\n<p>Paragraph under heading to test line-height.</p>\n<p style=\"text-align: left;\"><strong>Bacon ipsum dolor amet short ribs shank cow, ribeye corned beef short loin t-bone kielbasa meatloaf ball tip rump venison boudin brisket beef ribs. Fatback landjaeger frankfurter, meatloaf picanha andouille leberkas.</strong> <em>Tail beef ribs boudin salami, kevin cupim landjaeger pork loin tenderloin ham filet mignon drumstick short loin. Biltong frankfurter shank pork belly picanha prosciutto meatloaf tail hamburger landjaeger pancetta shankle pig.</em> Pig tri-tip tenderloin ground round ribeye alcatra turkey salami turducken sausage pork loin kielbasa hamburger meatloaf strip steak.</p>\n<p style=\"text-align: left;\"><strong>Second paragraph, same text-alignment goes in the same table, other text alignments go into new tables, see?</strong> <em>Tail beef ribs boudin salami, kevin cupim landjaeger pork loin tenderloin ham filet mignon drumstick short loin. Ribeye boudin cow, beef ribs t-bone pig short ribs tri-tip pork loin rump shank hamburger short loin. Salami pastrami meatball shoulder cupim.</em></p>\n<p style=\"text-align: center;\">Bacon kevin shank ball tip shoulder. Jowl leberkas fatback, short loin chuck beef beef ribs short ribs ribeye turducken pork chop brisket filet mignon cow. Turkey ball tip rump bacon filet mignon sausage jowl shoulder chicken ground round kielbasa shankle. <em>Drumstick pancetta corned beef kielbasa porchetta jerky swine leberkas kevin boudin chicken shoulder bacon tri-tip venison.</em> Ham hock ball tip beef ribs spare ribs tail pork ground round, biltong doner t-bone pork chop rump hamburger pancetta brisket.</p>\n<p style=\"text-align: center;\"><strong>Second paragraph, same text-alignment goes in the same table, other text alignments go into new tables, see?</strong> <em>Tail beef ribs boudin salami, <span style=\"color: #ffff00;\">kevin cupim landjaeger pork loin tenderloin</span> ham filet mignon drumstick short loin. Ribeye boudin cow, beef ribs t-bone pig short ribs tri-tip pork loin rump shank hamburger short loin. Salami pastrami meatball shoulder cupim.</em></p>\n<p style=\"text-align: right;\">Brisket beef kielbasa jowl hamburger, doner flank. Shoulder ham hock sausage t-bone pork belly chicken picanha pork loin ham bresaola tri-tip ground round kevin. <em>Chicken sirloin shankle fatback boudin t-bone pig tri-tip bresaola doner cow short loin pancetta short ribs andouille. Cupim doner short ribs, andouille cow t-bone ground round pork porchetta beef capicola.</em> Rump drumstick biltong shank kielbasa bacon ball tip pancetta meatloaf shankle fatback.</p>\n<p style=\"text-align: right;\"><strong>Second paragraph, same text-alignment goes in the same table, other text alignments go into new tables, see?</strong> <em>Tail beef ribs boudin salami, kevin cupim landjaeger pork loin tenderloin ham filet mignon drumstick short loin. Ribeye boudin cow, beef ribs t-bone pig short ribs tri-tip pork loin rump shank hamburger short loin. Salami pastrami meatball shoulder cupim.</em></p>\n<p style=\"text-align: justify;\">Kielbasa jowl flank biltong. Pork loin fatback chicken ham prosciutto sausage cow short loin porchetta kielbasa. <em>Bresaola ham hock pancetta, cow ham tenderloin flank turducken fatback beef jowl short loin pig.</em> Picanha turkey spare ribs capicola andouille, tongue short loin sausage corned beef kevin meatball venison kielbasa pastrami. Beef ribs ground round tenderloin flank.</p>\n<p style=\"text-align: justify;\"><strong>Second paragraph, same text-alignment goes in the same table, other text alignments go into new tables, see?</strong> <em>Tail beef ribs boudin salami, kevin cupim landjaeger pork loin tenderloin ham filet mignon drumstick short loin. Ribeye boudin cow, beef ribs t-bone pig short ribs tri-tip pork loin rump shank hamburger short loin. Salami pastrami meatball shoulder cupim.</em> <a href=\"http://www.example.org\">Alcatra hamburger</a><br /><br /></p>\n<ul>\n<li>One</li>\n<li>Two</li>\n<li>Three</li>\n</ul>\n<ol>\n<li>One</li>\n<li>Two</li>\n<li>Three</li>\n</ol>\n<blockquote>\n<p>Bacon ipsum dolor amet shoulder turkey meatball pork chop porchetta, filet mignon shankle. Sausage meatloaf flank picanha jowl chuck capicola tri-tip. Meatloaf andouille kielbasa beef ribs.</p>\n</blockquote>"
|
||||
"text": "<h1 style=\"text-align: left;\">1/1 Column</h1>\n<h1 style=\"text-align: center;\">1/1 Column</h1>\n<h1 style=\"text-align: right;\">1/1 Column</h1>\n<h1><a href=\"http://www.example.com\">1/1 Column</a></h1>\n<h2>Heading (size 2)</h2>\n<p>Paragraph under heading to test line-height.</p>\n<h3>Heading (size 3)</h3>\n<h3>Heading (size 3) <a href=\"http://www.example.org\">with link</a></h3>\n<h3><strong>Heading</strong> (size 3)</h3>\n<p>Paragraph under heading to test line-height.</p>\n<p style=\"text-align: left;\"><strong>Bacon ipsum dolor amet short ribs shank cow, ribeye corned beef short loin t-bone kielbasa meatloaf ball tip rump venison boudin brisket beef ribs. Fatback landjaeger frankfurter, meatloaf picanha andouille leberkas.</strong> <em>Tail beef ribs boudin salami, kevin cupim landjaeger pork loin tenderloin ham filet mignon drumstick short loin. Biltong frankfurter shank pork belly picanha prosciutto meatloaf tail hamburger landjaeger pancetta shankle pig.</em> Pig tri-tip tenderloin ground round ribeye alcatra turkey salami turducken sausage pork loin kielbasa hamburger meatloaf strip steak.</p>\n<p style=\"text-align: left;\"><strong>Second paragraph, same text-alignment goes in the same table, other text alignments go into new tables, see?</strong> <em>Tail beef ribs boudin salami, kevin cupim landjaeger pork loin tenderloin ham filet mignon drumstick short loin. Ribeye boudin cow, beef ribs t-bone pig short ribs tri-tip pork loin rump shank hamburger short loin. Salami pastrami meatball shoulder cupim.</em></p>\n<p style=\"text-align: center;\">Bacon kevin shank ball tip shoulder. Jowl leberkas fatback, short loin chuck beef beef ribs short ribs ribeye turducken pork chop brisket filet mignon cow. Turkey ball tip rump bacon filet mignon sausage jowl shoulder chicken ground round kielbasa shankle. <em>Drumstick pancetta corned beef kielbasa porchetta jerky swine leberkas kevin boudin chicken shoulder bacon tri-tip venison.</em> Ham hock ball tip beef ribs spare ribs tail pork ground round, biltong doner t-bone pork chop rump hamburger pancetta brisket.</p>\n<p style=\"text-align: center;\"><strong>Second paragraph, same text-alignment goes in the same table, other text alignments go into new tables, see?</strong> <em>Tail beef ribs boudin salami, <span style=\"color: #ffff00;\">kevin cupim landjaeger pork loin tenderloin</span> ham filet mignon drumstick short loin. Ribeye boudin cow, beef ribs t-bone pig short ribs tri-tip pork loin rump shank hamburger short loin. Salami pastrami meatball shoulder cupim.</em></p>\n<p style=\"text-align: right;\">Brisket beef kielbasa jowl hamburger, doner flank. Shoulder ham hock sausage t-bone pork belly chicken picanha pork loin ham bresaola tri-tip ground round kevin. <em>Chicken sirloin shankle fatback boudin t-bone pig tri-tip bresaola doner cow short loin pancetta short ribs andouille. Cupim doner short ribs, andouille cow t-bone ground round pork porchetta beef capicola.</em> Rump drumstick biltong shank kielbasa bacon ball tip pancetta meatloaf shankle fatback.</p>\n<p style=\"text-align: right;\"><strong>Second paragraph, same text-alignment goes in the same table, other text alignments go into new tables, see?</strong> <em>Tail beef ribs boudin salami, kevin cupim landjaeger pork loin tenderloin ham filet mignon drumstick short loin. Ribeye boudin cow, beef ribs t-bone pig short ribs tri-tip pork loin rump shank hamburger short loin. Salami pastrami meatball shoulder cupim.</em></p>\n<p style=\"text-align: justify;\">Kielbasa jowl flank biltong. Pork loin fatback chicken ham prosciutto sausage cow short loin porchetta kielbasa. <em>Bresaola ham hock pancetta, cow ham tenderloin flank turducken fatback beef jowl short loin pig.</em> Picanha turkey spare ribs capicola andouille, tongue short loin sausage corned beef kevin meatball venison kielbasa pastrami. Beef ribs ground round tenderloin flank.</p>\n<p style=\"text-align: justify;\"><strong>Second paragraph, same text-alignment goes in the same table, other text alignments go into new tables, see?</strong> <em>Tail beef ribs boudin salami, kevin cupim landjaeger pork loin tenderloin ham filet mignon drumstick short loin. Ribeye boudin cow, beef ribs t-bone pig short ribs tri-tip pork loin rump shank hamburger short loin. Salami pastrami meatball shoulder cupim.</em> <a href=\"http://www.example.org\">Alcatra hamburger</a><br /><br /></p>\n<ul>\n<li>One</li>\n<li>Two</li>\n<li>Three</li>\n</ul>\n<ol>\n<li>One</li>\n<li>Two</li>\n<li>Three</li>\n</ol>\n<blockquote><h2>test header</h2>\n<p>Bacon ipsum dolor amet shoulder turkey meatball pork chop porchetta, filet mignon shankle. Sausage meatloaf flank picanha jowl chuck capicola tri-tip. Meatloaf andouille kielbasa beef ribs.</p>\n</blockquote>"
|
||||
},
|
||||
{
|
||||
"type": "divider",
|
||||
|
@ -55,7 +55,8 @@ class ShortcodesTest extends MailPoetTest {
|
||||
$details = $shortcodes_object->match($content);
|
||||
expect($details['category'])->equals('category');
|
||||
expect($details['action'])->equals('action');
|
||||
expect($details['default'])->equals('default_value');
|
||||
expect($details['argument'])->equals('default');
|
||||
expect($details['argument_value'])->equals('default_value');
|
||||
$content = '[category:action|default]';
|
||||
$details = $shortcodes_object->match($content);
|
||||
expect($details)->isEmpty();
|
||||
@ -81,10 +82,12 @@ class ShortcodesTest extends MailPoetTest {
|
||||
$date = new \DateTime('now');
|
||||
expect(Date::process('d'))->equals($date->format('d'));
|
||||
expect(Date::process('dordinal'))->equals($date->format('dS'));
|
||||
expect(Date::process('dtext'))->equals($date->format('D'));
|
||||
expect(Date::process('dtext'))->equals($date->format('l'));
|
||||
expect(Date::process('m'))->equals($date->format('m'));
|
||||
expect(Date::process('mtext'))->equals($date->format('F'));
|
||||
expect(Date::process('y'))->equals($date->format('Y'));
|
||||
// allow custom date formats (http://php.net/manual/en/function.date.php)
|
||||
expect(Date::process('custom', 'format', 'U'))->equals($date->format('U'));
|
||||
}
|
||||
|
||||
function testItCanProcessNewsletterShortcodes() {
|
||||
@ -94,13 +97,13 @@ class ShortcodesTest extends MailPoetTest {
|
||||
'<a data-post-id="10" href="#">another post</a>' .
|
||||
'<a href="#">not post</a>';
|
||||
$result =
|
||||
$shortcodes_object->process(array('[newsletter:subject]'));
|
||||
$shortcodes_object->process(array('[newsletter:subject]'), $content);
|
||||
expect($result[0])->equals($this->newsletter->subject);
|
||||
$result =
|
||||
$shortcodes_object->process(array('[newsletter:total]'), $content);
|
||||
expect($result[0])->equals(2);
|
||||
$result =
|
||||
$shortcodes_object->process(array('[newsletter:post_title]'));
|
||||
$shortcodes_object->process(array('[newsletter:post_title]'), $content);
|
||||
$wp_post = get_post($this->WP_post);
|
||||
expect($result['0'])->equals($wp_post->post_title);
|
||||
}
|
||||
@ -269,6 +272,30 @@ class ShortcodesTest extends MailPoetTest {
|
||||
}
|
||||
}
|
||||
|
||||
function testItReturnsHashInsteadofLinksWhenInPreviewIsEnabled() {
|
||||
$shortcodes_object = $this->shortcodes_object;
|
||||
$shortcodes_object->wp_user_preview = true;
|
||||
$shortcodes = array(
|
||||
'[link:subscription_unsubscribe_url]',
|
||||
'[link:subscription_manage_url]',
|
||||
'[link:newsletter_view_in_browser_url]',
|
||||
);
|
||||
$result = $shortcodes_object->process($shortcodes);
|
||||
// hash is returned
|
||||
foreach($result as $index => $transformed_shortcode) {
|
||||
expect($transformed_shortcode)->equals('#');
|
||||
}
|
||||
$shortcodes = array(
|
||||
'[link:subscription_unsubscribe]',
|
||||
'[link:subscription_manage]',
|
||||
'[link:newsletter_view_in_browser]',
|
||||
);
|
||||
$result = $shortcodes_object->process($shortcodes);
|
||||
foreach($result as $index => $transformed_shortcode) {
|
||||
expect($transformed_shortcode)->regExp('/href="#"/');
|
||||
}
|
||||
}
|
||||
|
||||
function testItCanProcessCustomLinkShortcodes() {
|
||||
$shortcodes_object = $this->shortcodes_object;
|
||||
$shortcode = '[link:shortcode]';
|
||||
|
@ -1,17 +1,20 @@
|
||||
<?php
|
||||
|
||||
use MailPoet\Models\Newsletter;
|
||||
use MailPoet\Models\NewsletterLink;
|
||||
use MailPoet\Models\SendingQueue;
|
||||
use MailPoet\Models\Setting;
|
||||
use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Newsletter\Links\Links;
|
||||
use MailPoet\Newsletter\ViewInBrowser;
|
||||
use MailPoet\Router\Router;
|
||||
|
||||
class ViewInBrowserTest extends MailPoetTest {
|
||||
function __construct() {
|
||||
$this->newsletter = array(
|
||||
'body' => json_decode(
|
||||
'{
|
||||
$this->newsletter =
|
||||
array(
|
||||
'body' => json_decode(
|
||||
'{
|
||||
"content": {
|
||||
"type": "container",
|
||||
"orientation": "vertical",
|
||||
@ -41,7 +44,7 @@ class ViewInBrowserTest extends MailPoetTest {
|
||||
"blocks": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "<p>Rendered newsletter. Hello, [subscriber:firstname | default:reader] & [link:newsletter_view_in_browser_url]</p>"
|
||||
"text": "<p>Rendered newsletter. Hello, [subscriber:firstname | default:reader]. <a href=\"[link:newsletter_view_in_browser_url]\">Unsubscribe</a> or visit <a href=\"http://google.com\">Google</a></p>"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -50,22 +53,18 @@ class ViewInBrowserTest extends MailPoetTest {
|
||||
]
|
||||
}
|
||||
}', true),
|
||||
'id' => 1,
|
||||
'subject' => 'Some subject',
|
||||
'preheader' => 'Some preheader',
|
||||
'type' => 'standard',
|
||||
'status' => 'active'
|
||||
);
|
||||
'id' => 1,
|
||||
'subject' => 'Some subject',
|
||||
'preheader' => 'Some preheader',
|
||||
'type' => 'standard',
|
||||
'status' => 'active'
|
||||
);
|
||||
$this->queue_rendered_newsletter_without_tracking = array(
|
||||
'html' => 'Newsletter from queue. Hello, [subscriber:firstname] &
|
||||
[link:newsletter_view_in_browser_url]'
|
||||
'html' => '<p>Newsletter from queue. Hello, [subscriber:firstname | default:reader]. <a href="[link:newsletter_view_in_browser_url]">Unsubscribe</a> or visit <a href="http://google.com">Google</a></p>'
|
||||
);
|
||||
$this->queue_rendered_newsletter_with_tracking = array(
|
||||
'html' => 'Newsletter from queue. Hello, [subscriber:firstname] &
|
||||
[mailpoet_click_data]-90e56'
|
||||
'html' => '<p>Newsletter from queue. Hello, [subscriber:firstname | default:reader]. <a href="' . Links::DATA_TAG_CLICK . '-90e56">Unsubscribe</a> or visit <a href="' . Links::DATA_TAG_CLICK . '-i1893">Google</a><img alt="" class="" src="' . Links::DATA_TAG_OPEN . '"></p>'
|
||||
);
|
||||
// instantiate class
|
||||
$this->view_in_browser = new ViewInBrowser();
|
||||
}
|
||||
|
||||
function _before() {
|
||||
@ -85,13 +84,19 @@ class ViewInBrowserTest extends MailPoetTest {
|
||||
$queue->newsletter_rendered_body = $this->queue_rendered_newsletter_without_tracking;
|
||||
$queue->subscribers = array('processed' => array($subscriber->id));
|
||||
$this->queue = $queue->save();
|
||||
// build browser preview data
|
||||
$this->browser_preview_data = (object)array(
|
||||
'queue' => $this->queue,
|
||||
'subscriber' => $this->subscriber,
|
||||
'newsletter' => $this->newsletter,
|
||||
'preview' => false
|
||||
);
|
||||
// create newsletter link associations
|
||||
$newsletter_link_1 = NewsletterLink::create();
|
||||
$newsletter_link_1->hash = '90e56';
|
||||
$newsletter_link_1->url = '[link:newsletter_view_in_browser_url]';
|
||||
$newsletter_link_1->newsletter_id = $this->newsletter->id;
|
||||
$newsletter_link_1->queue_id = $this->queue->id;
|
||||
$this->newsletter_link_1 = $newsletter_link_1->save();
|
||||
$newsletter_link_2 = NewsletterLink::create();
|
||||
$newsletter_link_2->hash = 'i1893';
|
||||
$newsletter_link_2->url = 'http://google.com';
|
||||
$newsletter_link_2->newsletter_id = $this->newsletter->id;
|
||||
$newsletter_link_2->queue_id = $this->queue->id;
|
||||
$this->newsletter_link_2 = $newsletter_link_2->save();
|
||||
}
|
||||
|
||||
function testItRendersNewsletter() {
|
||||
@ -99,7 +104,7 @@ class ViewInBrowserTest extends MailPoetTest {
|
||||
$this->newsletter,
|
||||
$this->subscriber,
|
||||
$queue = false,
|
||||
$preview = true
|
||||
$preview = false
|
||||
);
|
||||
expect($rendered_body)->regExp('/Rendered newsletter/');
|
||||
}
|
||||
@ -109,7 +114,7 @@ class ViewInBrowserTest extends MailPoetTest {
|
||||
$this->newsletter,
|
||||
$this->subscriber,
|
||||
$this->queue,
|
||||
$preview = true
|
||||
$preview = false
|
||||
);
|
||||
expect($rendered_body)->regExp('/Newsletter from queue/');
|
||||
}
|
||||
@ -120,14 +125,26 @@ class ViewInBrowserTest extends MailPoetTest {
|
||||
$this->newsletter,
|
||||
$this->subscriber,
|
||||
$this->queue,
|
||||
$preview = true
|
||||
$preview = false
|
||||
);
|
||||
expect($rendered_body)->contains('Hello, First');
|
||||
expect($rendered_body)->contains(Router::NAME . '&endpoint=view_in_browser');
|
||||
}
|
||||
|
||||
function testItProcessesLinksWhenTrackingIsEnabled() {
|
||||
function testItRewritesLinksToRouterEndpointWhenTrackingIsEnabled() {
|
||||
Setting::setValue('tracking.enabled', true);
|
||||
$queue = $this->queue;
|
||||
$queue->newsletter_rendered_body = $this->queue_rendered_newsletter_with_tracking;
|
||||
$rendered_body = ViewInBrowser::renderNewsletter(
|
||||
$this->newsletter,
|
||||
$this->subscriber,
|
||||
$queue,
|
||||
$preview = false
|
||||
);
|
||||
expect($rendered_body)->contains(Router::NAME . '&endpoint=track');
|
||||
}
|
||||
|
||||
function testItConvertsHashedLinksToUrlsWhenPreviewIsEnabledAndNewsletterWasSent() {
|
||||
$queue = $this->queue;
|
||||
$queue->newsletter_rendered_body = $this->queue_rendered_newsletter_with_tracking;
|
||||
$rendered_body = ViewInBrowser::renderNewsletter(
|
||||
@ -136,11 +153,42 @@ class ViewInBrowserTest extends MailPoetTest {
|
||||
$queue,
|
||||
$preview = true
|
||||
);
|
||||
expect($rendered_body)->contains(Router::NAME . '&endpoint=track');
|
||||
// hashed link should be replaced with a URL
|
||||
expect($rendered_body)->notContains('[mailpoet_click_data]');
|
||||
expect($rendered_body)->contains('<a href="http://google.com">');
|
||||
}
|
||||
|
||||
function testReplacesLinkShortcodesWithUrlHashWhenPreviewIsEnabledAndNewsletterWasSent() {
|
||||
$queue = $this->queue;
|
||||
$queue->newsletter_rendered_body = $this->queue_rendered_newsletter_with_tracking;
|
||||
$rendered_body = ViewInBrowser::renderNewsletter(
|
||||
$this->newsletter,
|
||||
$this->subscriber,
|
||||
$queue,
|
||||
$preview = true
|
||||
);
|
||||
// link shortcodes should be replaced with a hash (#)
|
||||
expect($rendered_body)->notContains('[mailpoet_click_data]');
|
||||
expect($rendered_body)->contains('<a href="#">');
|
||||
}
|
||||
|
||||
function testRemovesOpenTrackingTagWhenPreviewIsEnabledAndNewsletterWasSent() {
|
||||
$queue = $this->queue;
|
||||
$queue->newsletter_rendered_body = $this->queue_rendered_newsletter_with_tracking;
|
||||
$rendered_body = ViewInBrowser::renderNewsletter(
|
||||
$this->newsletter,
|
||||
$this->subscriber,
|
||||
$queue,
|
||||
$preview = true
|
||||
);
|
||||
// open tracking data tag should be removed
|
||||
expect($rendered_body)->notContains('[mailpoet_open_data]');
|
||||
expect($rendered_body)->contains('<img alt="" class="" src="">');
|
||||
}
|
||||
|
||||
function _after() {
|
||||
ORM::raw_execute('TRUNCATE ' . Newsletter::$_table);
|
||||
ORM::raw_execute('TRUNCATE ' . NewsletterLink::$_table);
|
||||
ORM::raw_execute('TRUNCATE ' . Subscriber::$_table);
|
||||
ORM::raw_execute('TRUNCATE ' . SendingQueue::$_table);
|
||||
}
|
||||
|
@ -37,17 +37,13 @@ class ViewInBrowserRouterTest extends MailPoetTest {
|
||||
|
||||
function testItAbortsWhenBrowserPreviewDataIsMissing() {
|
||||
$view_in_browser = Stub::make($this->view_in_browser, array(
|
||||
'_abort' => Stub::exactly(3, function() { })
|
||||
'_abort' => Stub::exactly(2, function() { })
|
||||
), $this);
|
||||
// newsletter ID is required
|
||||
$data = $this->browser_preview_data;
|
||||
unset($data['newsletter_id']);
|
||||
$view_in_browser->_processBrowserPreviewData($data);
|
||||
// subscriber ID is required
|
||||
$data = $this->browser_preview_data;
|
||||
unset($data['subscriber_id']);
|
||||
$view_in_browser->_processBrowserPreviewData($data);
|
||||
// subscriber token is required
|
||||
// subscriber token is required if subscriber is provided
|
||||
$data = $this->browser_preview_data;
|
||||
unset($data['subscriber_token']);
|
||||
$view_in_browser->_processBrowserPreviewData($data);
|
||||
@ -61,43 +57,71 @@ class ViewInBrowserRouterTest extends MailPoetTest {
|
||||
$data = $this->browser_preview_data;
|
||||
$data['newsletter_id'] = 99;
|
||||
$view_in_browser->_processBrowserPreviewData($data);
|
||||
// subscriber ID is invalid
|
||||
// subscriber token is invalid
|
||||
$data = $this->browser_preview_data;
|
||||
$data['subscriber_id'] = 99;
|
||||
$data['subscriber_token'] = false;
|
||||
$view_in_browser->_processBrowserPreviewData($data);
|
||||
// subscriber token is invalid
|
||||
$data = $this->browser_preview_data;
|
||||
$data['subscriber_token'] = 'invalid';
|
||||
$view_in_browser->_processBrowserPreviewData($data);
|
||||
// subscriber has not received the newsletter
|
||||
}
|
||||
|
||||
function testItFailsValidationWhenSubscriberTokenDoesNotMatch() {
|
||||
$subscriber = $this->subscriber;
|
||||
$subscriber->email = 'random@email.com';
|
||||
$subscriber->save();
|
||||
$data = (object)array_merge(
|
||||
$this->browser_preview_data,
|
||||
array(
|
||||
'queue' => $this->queue,
|
||||
'subscriber' => $this->subscriber,
|
||||
'subscriber' => $subscriber,
|
||||
'newsletter' => $this->newsletter
|
||||
)
|
||||
);
|
||||
$data->subscriber->email = 'random@email.com';
|
||||
expect($this->view_in_browser->_validateBrowserPreviewData($data))->false();
|
||||
}
|
||||
|
||||
function testItFailsValidationWhenNewsletterIdIsProvidedButSubscriberDoesNotExist() {
|
||||
$data = (object)$this->browser_preview_data;
|
||||
$data->subscriber_id = false;
|
||||
expect($this->view_in_browser->_validateBrowserPreviewData($data))->false();
|
||||
}
|
||||
|
||||
function testItValidatesThatNewsletterExistsByCheckingHashFirst() {
|
||||
$newsletter_1 = $this->newsletter;
|
||||
$newsletter_2 = Newsletter::create();
|
||||
$newsletter_2->type = 'type';
|
||||
$newsletter_2 = $newsletter_2->save();
|
||||
$data = (object)$this->browser_preview_data;
|
||||
$data->newsletter_hash = $newsletter_2->hash;
|
||||
$result = $this->view_in_browser->_validateBrowserPreviewData($data);
|
||||
expect($result->newsletter->id)->equals($newsletter_2->id);
|
||||
$data->newsletter_hash = false;
|
||||
$result = $this->view_in_browser->_validateBrowserPreviewData($data);
|
||||
expect($result->newsletter->id)->equals($newsletter_1->id);
|
||||
}
|
||||
|
||||
function testItFailsValidationWhenPreviewIsEnabledButNewsletterHashNotProvided() {
|
||||
$data = (object)$this->browser_preview_data;
|
||||
$data->newsletter_hash = false;
|
||||
$data->preview = true;
|
||||
expect($this->view_in_browser->_validateBrowserPreviewData($data))->false();
|
||||
}
|
||||
|
||||
function testItFailsValidationWhenSubscriberIsNotOnProcessedList() {
|
||||
$data = (object)array_merge(
|
||||
$this->browser_preview_data,
|
||||
array(
|
||||
'queue' => $this->queue,
|
||||
'subscriber' => $this->subscriber,
|
||||
'newsletter' => $this->newsletter
|
||||
)
|
||||
);
|
||||
$data->subscriber->id = 99;
|
||||
expect($this->view_in_browser->_validateBrowserPreviewData($data))->false();
|
||||
$data = (object)$this->browser_preview_data;
|
||||
$result = $this->view_in_browser->_validateBrowserPreviewData($data);
|
||||
expect($result)->notEmpty();
|
||||
$queue = $this->queue;
|
||||
$queue->subscribers = array('processed' => array());
|
||||
$queue->save();
|
||||
$result = $this->view_in_browser->_validateBrowserPreviewData($data);
|
||||
expect($result)->false();
|
||||
}
|
||||
|
||||
function testItDoesNotRequireWpUsersToBeOnProcessedListWhenPreviewIsEnabled() {
|
||||
function testItDoesNotRequireWpAdministratorToBeOnProcessedListWhenPreviewIsEnabled() {
|
||||
$data = (object)array_merge(
|
||||
$this->browser_preview_data,
|
||||
array(
|
||||
@ -106,8 +130,16 @@ class ViewInBrowserRouterTest extends MailPoetTest {
|
||||
'newsletter' => $this->newsletter
|
||||
)
|
||||
);
|
||||
$data->subscriber->wp_user_id = 99;
|
||||
$data->preview = true;
|
||||
// when WP user is not logged, false should be returned
|
||||
expect($this->view_in_browser->_validateBrowserPreviewData($data))->false();
|
||||
// when WP user is logged in but does not have 'manage options' permission, false should be returned
|
||||
wp_set_current_user(1);
|
||||
$wp_user = wp_get_current_user();
|
||||
$wp_user->remove_role('administrator');
|
||||
expect($this->view_in_browser->_validateBrowserPreviewData($data))->false();
|
||||
// when WP user is logged and has 'manage options' permission, data should be returned
|
||||
$wp_user->add_role('administrator');
|
||||
expect($this->view_in_browser->_validateBrowserPreviewData($data))->equals($data);
|
||||
}
|
||||
|
||||
@ -130,5 +162,8 @@ class ViewInBrowserRouterTest extends MailPoetTest {
|
||||
ORM::raw_execute('TRUNCATE ' . Newsletter::$_table);
|
||||
ORM::raw_execute('TRUNCATE ' . Subscriber::$_table);
|
||||
ORM::raw_execute('TRUNCATE ' . SendingQueue::$_table);
|
||||
// reset WP user role
|
||||
$wp_user = wp_get_current_user();
|
||||
$wp_user->add_role('administrator');
|
||||
}
|
||||
}
|
@ -96,6 +96,12 @@ class FrontRouterTest extends MailPoetTest {
|
||||
expect($result)->equals($data);
|
||||
}
|
||||
|
||||
function testItExecutesUrlParameterConflictResolverAction() {
|
||||
$data = array('data' => 'dummy data');
|
||||
$result = $this->router->init();
|
||||
expect((boolean) did_action('mailpoet_conflict_resolver_router_url_query_parameters'))->true();
|
||||
}
|
||||
|
||||
function testItCanEncodeRequestData() {
|
||||
$data = array('data' => 'dummy data');
|
||||
$result = Router::encodeRequestData($data);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user