Compare commits

...

94 Commits

Author SHA1 Message Date
e126278e32 Bump up the release version to 3.0.0-beta.17 2017-02-02 17:47:21 +03:00
9d2651083d Merge pull request #808 from mailpoet/translation_plugin
Whitelist translation plugin [MAILPOET-805]
2017-02-02 15:48:02 +03:00
1e1ae4c3cf Whitelist translation handling plugin's admin assets on MP pages
[MAILPOET-805]
2017-02-02 14:10:57 +02:00
385f5ab535 Merge pull request #807 from mailpoet/sending_svc_subscriber_count
Add subscriber count reporting on Sending Service key validation [MAILPOET-804]
2017-02-02 13:19:39 +02:00
dc371d76ca Rework subscriber count reporting after a code review [MAILPOET-804]
Move updateSubscriberCount() out of checkKey()
Move wp_remote_post() to request()
Simplify a response check
2017-02-01 21:04:11 +03:00
be0c9b71d8 Add subscriber count reporting on Sending Service key validation [MAILPOET-804] 2017-02-01 15:58:49 +03:00
6e250d9317 Merge pull request #806 from mailpoet/update_sending_methods_strings
Update sending method descriptions for the new shop [MAILPOET-794]
2017-02-01 13:54:52 +02:00
afeaa00fc7 Merge branch 'master' into update_sending_methods_strings 2017-02-01 14:33:05 +03:00
6575d1579d Merge pull request #796 from mailpoet/sending_svc_api_consolidation
Consolidate Sending Service API calls in one class [MAILPOET-795]
2017-02-01 13:29:34 +02:00
282199d362 Merge pull request #784 from mailpoet/sending_svc_key_validation
Add sending service key validation [MAILPOET-743]
2017-02-01 13:29:08 +02:00
3e5c46e8f3 Cut the text to fit in a smaller box [MAILPOET-794] 2017-02-01 13:11:48 +03:00
71515f3ff0 Updated changelog to fix WP plugin repo parsing 2017-01-31 17:24:35 +02:00
934a8d5bf8 Update changelog 2017-01-31 16:44:14 +02:00
6be2464c86 Fix build script to include mailpoet_initializer.php in the build 2017-01-31 14:54:39 +02:00
ace8a52262 Update release information for 3.0.0-beta.16 2017-01-31 14:20:30 +02:00
7d37d279cd Merge pull request #805 from mailpoet/translations
Translations update
2017-01-30 18:18:22 -05:00
5525a959a8 Fix remaining unit tests after translation changes 2017-01-30 21:50:22 +02:00
a421dbd674 Fix tests 2017-01-30 21:35:49 +02:00
bedfc4f80c Add context to 'Select' translation strings 2017-01-30 21:35:49 +02:00
af2a6b2559 Fix parsing of starting and ending translation string quotes 2017-01-30 21:35:49 +02:00
cb8c32e171 Add 'Move to trash' to separate noun from verb for certain languages 2017-01-30 21:35:49 +02:00
8abec208fc Rewritten some translation strings 2017-01-30 21:35:49 +02:00
5264cb1cf4 Split firstname and lastname into two words 2017-01-30 21:33:59 +02:00
47e0e1a836 Add one and remove two spaces 2017-01-30 21:33:59 +02:00
e602612cd6 Add missing hyphens 2017-01-30 21:33:59 +02:00
9f7ae122e3 Add missing periods to translation strings 2017-01-30 21:33:59 +02:00
22caba31e3 Update sending method strings for the new shop [MAILPOET-794] 2017-01-30 13:44:26 +03:00
82ab4acb8d Improve key validation messages handling [MAILPOET-743]
Hide error messages when the sending method is changed
Verify key and display a message when saving settings
2017-01-30 13:32:43 +03:00
9466be4793 Merge pull request #804 from mailpoet/requirement_check_update
Fixes PDO extension check condition [MAILPOET-800 ]
2017-01-30 11:04:09 +03:00
a2e2090cbb Update KB link in the main plugin file 2017-01-30 10:41:11 +03:00
d100d61403 Updates KB links 2017-01-28 11:07:41 -05:00
d781ef6d01 Fixes scheduler test that fails on last Saturday 2017-01-28 11:07:26 -05:00
1dafc4da04 Adds CSS Parser to the list of vendor classes 2017-01-27 19:05:44 -05:00
063c271e40 Fixes PDO extension check condition 2017-01-27 18:49:33 -05:00
a53007e30b Merge pull request #803 from mailpoet/conflict_resolver_fix
Fixes fatal error on PHP 5.3 [MAILPOET-798]
2017-01-27 20:17:10 +03:00
c616b3299a Reverts to the use of public class property and introduces ugliness of
$_this
2017-01-27 11:51:26 -05:00
86eab0d8f8 Merge pull request #802 from mailpoet/conflict_resolver_fix
Fixes fatal error on PHP 5.3 [MAILPOET-798]
2017-01-27 19:38:12 +03:00
bfd35b1cdc Changes class variable to static; prevents
Fixed 'using $this when not in object contex' error on PHP 5.3
2017-01-27 09:12:31 -05:00
461203279b Consolidate Sending Service API calls in one class [MAILPOET-795] 2017-01-27 16:35:12 +03:00
b9c45b46ba Remove a duplicate function [MAILPOET-743] 2017-01-27 16:28:58 +03:00
1a42ae4cca Fix a unit test [MAILPOET-743] 2017-01-27 16:22:12 +03:00
f2ad7ee34c Fix code style [MAILPOET-743] 2017-01-27 16:22:12 +03:00
37017ef69d Fix appearance and logic of some key validation messages [MAILPOET-743] 2017-01-27 16:22:12 +03:00
157725c300 Fix a string for an expiring key [MAILPOET-743] 2017-01-27 16:22:12 +03:00
98d6f55a6e Tweak Sending Service key validation after a code review [MAILPOET-743]
* Abstract key state to unbound it from the API response codes
* Rename SendingServiceKeyCheck task for clarity
* Add a setter for the API key in the Bridge API class
* Make some smaller fixes
2017-01-27 16:22:11 +03:00
425d45a862 Remove unused imports from Cron tests [MAILPOET-743] 2017-01-27 16:22:11 +03:00
438b4fb1ec Add unit tests [MAILPOET-743] 2017-01-27 16:22:09 +03:00
1f91d40def Add sending service key validation [MAILPOET-743] 2017-01-27 16:20:26 +03:00
c5e1def2f9 Merge pull request #801 from mailpoet/list-unsubscribe_header
Add List-Unsubscribe header to emails [MAILPOET-793]
2017-01-26 17:51:33 -05:00
65ba834742 Merge pull request #800 from mailpoet/forms_listing_copy_fix
Fix a string displayed when there are no forms [MAILPOET-738]
2017-01-26 17:40:12 -05:00
19dc048858 Remove unused imports [MAILPOET-793] 2017-01-26 21:11:39 +03:00
938279bf8f Remove unsubscribe link generation from Mailer - to be solved in another task [MAILPOET-793] 2017-01-26 21:07:03 +03:00
dd2df429ef Add List-Unsubscribe header to emails [MAILPOET-793]
Amazon SES supports custom headers only via 'SendRawEmail' action
MailPoet Sending Service doesn't support custom headers yet
2017-01-26 15:38:23 +03:00
c4e05912ff Fix a string displayed when there are no forms [MAILPOET-738] 2017-01-26 11:19:23 +03:00
bb34e8477f Merge pull request #799 from mailpoet/welcome_email_preview_fix
Prevents previewing of sent welcome emails [MAILPOET-796]
2017-01-26 11:10:11 +03:00
32f7d7771f Prevents previewing of sent welcome emails 2017-01-25 19:06:35 -05:00
e77717c4c2 Merge pull request #798 from mailpoet/form_segments_fix
Filter lists that can be subscribed to using any particular form [MAILPOET-777]
2017-01-25 18:34:30 -05:00
d27d5ae5dd Merge pull request #797 from mailpoet/form_fields_filter
Fix fubscription form failing when some fields are absent or don't ex…
2017-01-25 17:48:48 -05:00
168263f0ea Fix code style [MAILPOET-777] 2017-01-25 16:29:23 +03:00
f1ced11809 Remove leading slashes in unit test imports [MAILPOET-764] 2017-01-25 16:28:36 +03:00
c2546e8aed Filter lists that can be subscribed to using any particular form [MAILPOET-777] 2017-01-25 16:15:42 +03:00
b7ef191641 Remove leading slashes in imports [MAILPOET-764] 2017-01-25 10:22:27 +03:00
2220a13399 Remove unused imports [MAILPOET-764] 2017-01-25 10:14:38 +03:00
31ec7475c8 - Bumps up release version to Beta 15
- Updates changelog
- Cleans up code comments in the main mailpoet.php file
2017-01-24 13:20:08 -05:00
bfdc13a8d1 Fix fubscription form failing when some fields are absent or don't exist [MAILPOET-764] 2017-01-24 21:12:56 +03:00
9a3c4ff7de Merge pull request #794 from mailpoet/initial_requirements_check
Updated plugin initialization logic [MAILPOET-790]
2017-01-23 18:45:41 +02:00
25410eb09c - Prefixes functions 2017-01-23 11:08:36 -05:00
122f88668a - Removes anonymous functions not supported on PHP 5.2 2017-01-23 11:07:29 -05:00
9c35eb9723 - Moves plugin initialization into a separate file that's included after
PHP version and core dependency checks are run
2017-01-23 09:05:21 -05:00
fa528ed1ff Merge pull request #795 from mailpoet/preview_link_update
Allows sharing of newsletter preview links [MAILPOET-791]
2017-01-23 15:52:11 +03:00
1a7623bc4a - Adds unit tests 2017-01-20 09:24:45 -05:00
3a4a37e1af - Sets subscriber to the logged in WP user when subscriber is not found 2017-01-20 09:16:43 -05:00
888a566dda - Includes newsletter hash in preview URLs thus not requiring user
validation
2017-01-20 09:16:43 -05:00
3567779faf Merge pull request #792 from mailpoet/remove_all_lists_fix
Fix 'Subscribers without a list' filter not showing unsubscribed subscribers [MAILPOET-789]
2017-01-19 20:47:58 -05:00
cb5b0bd753 - Removes PHP version check that's been moved to the main plugin file 2017-01-19 20:29:03 -05:00
88d0511adb - Loads autoloader class and initializes the plugin 2017-01-19 20:29:02 -05:00
a4a654cfd5 - Removes the use of namespace to ensure PHP 5.2 compatibility
- Adds a PHP version check and disabled the plugin if the check fails
- Adds a check for Initializer and autoload core files
- Moves plugin initialization logic outside to the Initializer class
2017-01-19 20:28:57 -05:00
4d3c90ce0d Exclude trashed and multiple-list subscribers [MAILPOET-789] 2017-01-19 23:04:29 +03:00
f51aba4dbd Bumps up release version to Beta 14
Updates changelog
2017-01-19 12:30:17 -05:00
f651c06cb9 Merge pull request #793 from mailpoet/conflict_resolver_fix
Replaces reliance on style/script names with asset location [MAILPOET-723]
2017-01-19 18:35:20 +02:00
940328c608 - Updates assets locations to more precise names 2017-01-19 11:26:32 -05:00
ce85600753 Merge pull request #789 from mailpoet/build_sh_update
Update tests and risky files removal in build.sh
2017-01-19 10:24:27 -05:00
5666116645 - Replaces reliance on style/script names with asset location
- Updates unit tests
2017-01-19 10:20:22 -05:00
815461a851 Add a unit test [MAILPOET-789] 2017-01-19 17:37:01 +03:00
1102467e39 Fix 'Subscribers without a list' filter not showing unsubscribed subscribers [MAILPOET-789] 2017-01-19 17:36:28 +03:00
a5ee865271 Merge pull request #790 from mailpoet/remove_all_lists_fix
Fix all lists removal when editing subscribers [MAILPOET-726]
2017-01-18 20:25:25 -05:00
59bda6cf6c Merge pull request #791 from mailpoet/first_last_names_fix
Fix MySQL strict mode error when saving a subscriber without first or…
2017-01-18 20:14:42 -05:00
a4d9d55b09 Fix code style [MAILPOET-780] 2017-01-18 19:37:16 +03:00
8cf918013d Fix MySQL strict mode error when saving a subscriber without first or last name [MAILPOET-780] 2017-01-18 19:06:33 +03:00
7789a10026 Fix code style [MAILPOET-726] 2017-01-18 15:47:16 +03:00
ce0ad33c32 Fix all lists removal when editing subscribers [MAILPOET-726] 2017-01-18 15:33:20 +03:00
63d1fe17a9 Merge pull request #788 from mailpoet/mysql_timeout_fix
Updates MySQL unit test condition
2017-01-17 21:30:57 +03:00
da92795635 Update tests and risky files removal in build.sh 2017-01-17 21:17:02 +03:00
915f8b5865 - Updates test condition 2017-01-17 13:03:02 -05:00
163 changed files with 2360 additions and 680 deletions

View File

@ -15,7 +15,7 @@
padding 15px 15px 0 15px
margin 0 25px 25px 0
width 300px
height 250px
height 300px
border 1px solid #dedede
background-color #fff
h3

View File

@ -77,7 +77,7 @@ const messages = {
const bulk_actions = [
{
name: 'trash',
label: MailPoet.I18n.t('trash'),
label: MailPoet.I18n.t('moveToTrash'),
onSuccess: messages.onTrash
}
];

View File

@ -82,7 +82,7 @@ const ListingItem = React.createClass({
null,
this.props.item.id
) }>
{MailPoet.I18n.t('trash')}
{MailPoet.I18n.t('moveToTrash')}
</a>
</span>
);
@ -873,4 +873,4 @@ const Listing = React.createClass({
}
});
module.exports = Listing;
module.exports = Listing;

View File

@ -98,7 +98,7 @@ const columns = [
const bulk_actions = [
{
name: 'trash',
label: MailPoet.I18n.t('trash'),
label: MailPoet.I18n.t('moveToTrash'),
onSuccess: messages.onTrash
}
];
@ -340,4 +340,4 @@ const NewsletterListNotification = React.createClass({
}
});
module.exports = NewsletterListNotification;
module.exports = NewsletterListNotification;

View File

@ -93,7 +93,7 @@ const columns = [
const bulk_actions = [
{
name: 'trash',
label: MailPoet.I18n.t('trash'),
label: MailPoet.I18n.t('moveToTrash'),
onSuccess: messages.onTrash
}
];
@ -223,4 +223,4 @@ const NewsletterListStandard = React.createClass({
}
});
module.exports = NewsletterListStandard;
module.exports = NewsletterListStandard;

View File

@ -95,7 +95,7 @@ const columns = [
const bulk_actions = [
{
name: 'trash',
label: MailPoet.I18n.t('trash'),
label: MailPoet.I18n.t('moveToTrash'),
onSuccess: messages.onTrash
}
];
@ -369,4 +369,4 @@ const NewsletterListWelcome = React.createClass({
}
});
module.exports = NewsletterListWelcome;
module.exports = NewsletterListWelcome;

View File

@ -90,7 +90,7 @@ const messages = {
const bulk_actions = [
{
name: 'trash',
label: MailPoet.I18n.t('trash'),
label: MailPoet.I18n.t('moveToTrash'),
onSuccess: messages.onTrash
}
];

View File

@ -200,7 +200,7 @@ const bulk_actions = [
},
{
name: 'trash',
label: MailPoet.I18n.t('trash'),
label: MailPoet.I18n.t('moveToTrash'),
onSuccess: messages.onTrash
}
];

View File

@ -50,17 +50,20 @@ rm -rf $plugin_name/vendor/soundasleep/html2text/tests
rm -rf $plugin_name/vendor/mtdowling/cron-expression/tests
rm -rf $plugin_name/vendor/swiftmailer/swiftmailer/tests
rm -rf $plugin_name/vendor/cerdic/css-tidy/testing
rm -rf $plugin_name/vendor/sabberworm/php-css-parser/tests
# Remove risky files from 3rd party extensions
echo '[BUILD] Removing risky and demo files from vendor libraries'
rm -f $plugin_name/vendor/j4mie/idiorm/demo.php
rm -f $plugin_name/vendor/cerdic/css-tidy/css_optimiser.php
rm -f $plugin_name/assets/js/lib/tinymce/package.json
# Copy release files.
echo '[BUILD] Copying release files'
cp license.txt $plugin_name
cp index.php $plugin_name
cp $plugin_name.php $plugin_name
cp mailpoet_initializer.php $plugin_name
cp readme.txt $plugin_name
cp uninstall.php $plugin_name

View File

@ -1,6 +1,6 @@
<?php
namespace MailPoet\API;
use \MailPoet\Util\Security;
use MailPoet\Util\Security;
if(!defined('ABSPATH')) exit;
@ -38,7 +38,7 @@ class API {
if($this->checkToken() === false) {
$error_response = new ErrorResponse(
array(
Error::UNAUTHORIZED => __('Invalid request.', 'mailpoet')
Error::UNAUTHORIZED => __('Invalid request', 'mailpoet')
),
array(),
Response::STATUS_UNAUTHORIZED
@ -64,7 +64,7 @@ class API {
// throw exception bad request
$error_response = new ErrorResponse(
array(
Error::BAD_REQUEST => __('Invalid request.', 'mailpoet')
Error::BAD_REQUEST => __('Invalid request', 'mailpoet')
),
array(),
Response::STATUS_BAD_REQUEST
@ -149,4 +149,4 @@ class API {
$global .= '</script>';
echo $global;
}
}
}

View File

@ -27,9 +27,9 @@ abstract class Endpoint {
function badRequest($errors = array(), $meta = array()) {
if(empty($errors)) {
$errors = array(
Error::BAD_REQUEST => __('Invalid request parameters.', 'mailpoet')
Error::BAD_REQUEST => __('Invalid request parameters', 'mailpoet')
);
}
return new ErrorResponse($errors, $meta, Response::STATUS_BAD_REQUEST);
}
}
}

View File

@ -1,7 +1,6 @@
<?php
namespace MailPoet\API\Endpoints;
use \MailPoet\API\Endpoint as APIEndpoint;
use \MailPoet\API\Error as APIError;
use MailPoet\API\Endpoint as APIEndpoint;
if(!defined('ABSPATH')) exit;

View File

@ -1,8 +1,8 @@
<?php
namespace MailPoet\API\Endpoints;
use \MailPoet\API\Endpoint as APIEndpoint;
use \MailPoet\API\Error as APIError;
use \MailPoet\Models\CustomField;
use MailPoet\API\Endpoint as APIEndpoint;
use MailPoet\API\Error as APIError;
use MailPoet\Models\CustomField;
if(!defined('ABSPATH')) exit;

View File

@ -3,11 +3,11 @@ namespace MailPoet\API\Endpoints;
use MailPoet\API\Endpoint as APIEndpoint;
use MailPoet\API\Error as APIError;
use \MailPoet\Models\Form;
use \MailPoet\Models\StatisticsForms;
use \MailPoet\Form\Renderer as FormRenderer;
use \MailPoet\Listing;
use \MailPoet\Form\Util;
use MailPoet\Models\Form;
use MailPoet\Models\StatisticsForms;
use MailPoet\Form\Renderer as FormRenderer;
use MailPoet\Listing;
use MailPoet\Form\Util;
if(!defined('ABSPATH')) exit;

View File

@ -1,10 +1,8 @@
<?php
namespace MailPoet\API\Endpoints;
use \MailPoet\API\Endpoint as APIEndpoint;
use \MailPoet\API\Error as APIError;
use MailPoet\API\Endpoint as APIEndpoint;
use MailPoet\Subscribers\ImportExport\Import\MailChimp;
use MailPoet\Models\CustomField;
use MailPoet\Models\Segment;
if(!defined('ABSPATH')) exit;

View File

@ -1,7 +1,7 @@
<?php
namespace MailPoet\API\Endpoints;
use \MailPoet\API\Endpoint as APIEndpoint;
use \MailPoet\API\Error as APIError;
use MailPoet\API\Endpoint as APIEndpoint;
use MailPoet\API\Error as APIError;
use MailPoet\Models\NewsletterTemplate;

View File

@ -340,11 +340,10 @@ class Newsletters extends APIEndpoint {
}
// get preview url
$subscriber = Subscriber::getCurrentWPUser();
$newsletter->preview_url = NewsletterUrl::getViewInBrowserUrl(
NewsletterUrl::TYPE_LISTING_EDITOR,
$newsletter,
$subscriber,
$subscriber = null,
$queue
);
@ -435,4 +434,4 @@ class Newsletters extends APIEndpoint {
);
}
}
}
}

View File

@ -1,13 +1,11 @@
<?php
namespace MailPoet\API\Endpoints;
use \MailPoet\API\Endpoint as APIEndpoint;
use \MailPoet\API\Error as APIError;
use MailPoet\API\Endpoint as APIEndpoint;
use MailPoet\API\Error as APIError;
use \MailPoet\Models\Segment;
use \MailPoet\Models\SubscriberSegment;
use \MailPoet\Models\SegmentFilter;
use \MailPoet\Listing;
use \MailPoet\Segments\WP;
use MailPoet\Models\Segment;
use MailPoet\Listing;
use MailPoet\Segments\WP;
if(!defined('ABSPATH')) exit;

View File

@ -5,11 +5,7 @@ use MailPoet\API\Error as APIError;
use MailPoet\Mailer\Mailer;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterOption;
use MailPoet\Models\NewsletterOptionField;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Models\SubscriberSegment;
use MailPoet\Newsletter\Scheduler\Scheduler;
use MailPoet\Models\SendingQueue as SendingQueueModel;
use MailPoet\Util\Helpers;

View File

@ -0,0 +1,67 @@
<?php
namespace MailPoet\API\Endpoints;
use Carbon\Carbon;
use MailPoet\API\Endpoint as APIEndpoint;
use MailPoet\API\Error as APIError;
use MailPoet\Services\Bridge;
if(!defined('ABSPATH')) exit;
class Services extends APIEndpoint {
public $bridge;
function __construct() {
$this->bridge = new Bridge();
}
function verifyMailPoetKey($data = array()) {
$key = isset($data['key']) ? trim($data['key']) : null;
if(!$key) {
return $this->badRequest(array(
APIError::BAD_REQUEST => __('Please specify a key.', 'mailpoet')
));
}
try {
$result = $this->bridge->checkKey($key);
} catch(\Exception $e) {
return $this->errorResponse(array(
$e->getCode() => $e->getMessage()
));
}
$state = !empty($result['state']) ? $result['state'] : null;
$success_message = null;
if($state == Bridge::MAILPOET_KEY_VALID) {
$success_message = __('Your MailPoet API key is valid!', 'mailpoet');
} elseif($state == Bridge::MAILPOET_KEY_EXPIRING) {
$success_message = sprintf(
__('Your MailPoet key expires on %s!', 'mailpoet'),
Carbon::createFromTimestamp(strtotime($result['data']['expire_at']))
->format('Y-m-d')
);
}
if($success_message) {
return $this->successResponse(array('message' => $success_message));
}
switch($state) {
case Bridge::MAILPOET_KEY_INVALID:
$error = __('Your MailPoet key is invalid!', 'mailpoet');
break;
default:
$code = !empty($result['code']) ? $result['code'] : Bridge::CHECK_ERROR_UNKNOWN;
$error = sprintf(
__('Error validating API key, please try again later (code: %s)', 'mailpoet'),
$code
);
break;
}
return $this->errorResponse(array(APIError::BAD_REQUEST => $error));
}
}

View File

@ -1,8 +1,9 @@
<?php
namespace MailPoet\API\Endpoints;
use \MailPoet\API\Endpoint as APIEndpoint;
use \MailPoet\API\Error as APIError;
use \MailPoet\Models\Setting;
use MailPoet\API\Endpoint as APIEndpoint;
use MailPoet\API\Error as APIError;
use MailPoet\Models\Setting;
use MailPoet\Services\Bridge;
if(!defined('ABSPATH')) exit;
@ -21,6 +22,13 @@ class Settings extends APIEndpoint {
foreach($settings as $name => $value) {
Setting::setValue($name, $value);
}
if(!empty($settings['mta']['mailpoet_api_key'])
&& Bridge::isMPSendingServiceEnabled()
) {
$bridge = new Bridge();
$result = $bridge->checkKey($settings['mta']['mailpoet_api_key']);
$bridge->updateSubscriberCount($result);
}
return $this->successResponse(Setting::getAll());
}
}

View File

@ -1,7 +1,7 @@
<?php
namespace MailPoet\API\Endpoints;
use \MailPoet\API\Endpoint as APIEndpoint;
use \MailPoet\Config\Activator;
use MailPoet\API\Endpoint as APIEndpoint;
use MailPoet\Config\Activator;
if(!defined('ABSPATH')) exit;

View File

@ -1,15 +1,11 @@
<?php
namespace MailPoet\API\Endpoints;
use \MailPoet\API\Endpoint as APIEndpoint;
use \MailPoet\API\Error as APIError;
use \MailPoet\API\Access as APIAccess;
use MailPoet\API\Endpoint as APIEndpoint;
use MailPoet\API\Error as APIError;
use MailPoet\API\Access as APIAccess;
use MailPoet\Listing;
use MailPoet\Models\Subscriber;
use MailPoet\Models\SubscriberSegment;
use MailPoet\Models\SubscriberCustomField;
use MailPoet\Models\Segment;
use MailPoet\Models\Setting;
use MailPoet\Models\Form;
use MailPoet\Models\StatisticsForms;
@ -65,10 +61,17 @@ class Subscribers extends APIEndpoint {
$form = Form::findOne($form_id);
unset($data['form_id']);
if(!$form) {
return $this->badRequest(array(
APIError::BAD_REQUEST => __('Please specify a valid form ID.', 'mailpoet')
));
}
$segment_ids = (!empty($data['segments'])
? (array)$data['segments']
: array()
);
$segment_ids = $form->filterSegments($segment_ids);
unset($data['segments']);
if(empty($segment_ids)) {
@ -77,6 +80,10 @@ class Subscribers extends APIEndpoint {
));
}
// only accept fields defined in the form
$form_fields = $form->getFieldList();
$data = array_intersect_key($data, array_flip($form_fields));
$subscriber = Subscriber::subscribe($data, $segment_ids);
$errors = $subscriber->getErrors();
@ -91,11 +98,13 @@ class Subscribers extends APIEndpoint {
$form = $form->asArray();
if($form['settings']['on_success'] === 'page') {
// redirect to a page on a success, pass the page url in the meta
$meta['redirect_url'] = get_permalink($form['settings']['success_page']);
} else if($form['settings']['on_success'] === 'url') {
$meta['redirect_url'] = $form['settings']['success_url'];
if(!empty($form['settings']['on_success'])) {
if($form['settings']['on_success'] === 'page') {
// redirect to a page on a success, pass the page url in the meta
$meta['redirect_url'] = get_permalink($form['settings']['success_page']);
} else if($form['settings']['on_success'] === 'url') {
$meta['redirect_url'] = $form['settings']['success_url'];
}
}
}
@ -107,6 +116,9 @@ class Subscribers extends APIEndpoint {
}
function save($data = array()) {
if(empty($data['segments'])) {
$data['segments'] = array();
}
$subscriber = Subscriber::createOrUpdate($data);
$errors = $subscriber->getErrors();

View File

@ -1,7 +1,7 @@
<?php
namespace MailPoet\Config;
use \MailPoet\Analytics\Reporter;
use \MailPoet\Models\Setting;
use MailPoet\Analytics\Reporter;
use MailPoet\Models\Setting;
if(!defined('ABSPATH')) exit;

View File

@ -1,7 +1,7 @@
<?php
namespace MailPoet\Config;
use \MailPoet\Models\Setting;
use \MailPoet\Util\Url;
use MailPoet\Models\Setting;
use MailPoet\Util\Url;
class Changelog {
function __construct() {

View File

@ -1,8 +1,6 @@
<?php
namespace MailPoet\Config;
use MailPoet\Cron\Workers\Scheduler;
use MailPoet\Cron\Workers\SendingQueue;
use \MailPoet\Models\Setting;
use MailPoet\Models\Setting;
class Hooks {
function __construct() {

View File

@ -27,6 +27,7 @@ class Menu {
$this->assets_url = $assets_url;
$subscribers_feature = new SubscribersFeature();
$this->subscribers_over_limit = $subscribers_feature->check();
$this->checkMailPoetAPIKey();
}
function init() {
@ -302,6 +303,7 @@ class Menu {
'settings' => $settings,
'segments' => Segment::getSegmentsWithSubscriberCount(),
'cron_trigger' => CronTrigger::getAvailableMethods(),
'total_subscribers' => Subscriber::getTotalSubscribers(),
'pages' => Pages::getAll(),
'flags' => $flags,
'current_user' => wp_get_current_user(),
@ -387,6 +389,9 @@ class Menu {
function newsletters() {
if($this->subscribers_over_limit) return $this->displaySubscriberLimitExceededTemplate();
if(isset($this->mp_api_key_valid) && $this->mp_api_key_valid === false) {
return $this->displayMailPoetAPIKeyInvalidTemplate();
}
global $wp_roles;
@ -483,6 +488,39 @@ class Menu {
exit;
}
function displayMailPoetAPIKeyInvalidTemplate() {
$this->displayPage('invalidkey.html', array(
'subscriber_count' => Subscriber::getTotalSubscribers()
));
exit;
}
static function isOnMailPoetAdminPage(array $exclude = null, $screen_id = null) {
if(is_null($screen_id)) {
if(empty($_REQUEST['page'])) {
return false;
}
$screen_id = $_REQUEST['page'];
}
if(!empty($exclude)) {
foreach($exclude as $slug) {
if(stripos($screen_id, $slug) !== false) {
return false;
}
}
}
return (stripos($screen_id, 'mailpoet-') !== false);
}
function checkMailPoetAPIKey(ServicesChecker $checker = null) {
if(self::isOnMailPoetAdminPage()) {
$show_notices = isset($_REQUEST['page'])
&& stripos($_REQUEST['page'], 'mailpoet-newsletters') === false;
$checker = $checker ?: new ServicesChecker();
$this->mp_api_key_valid = $checker->checkMailPoetAPIKeyValid($show_notices);
}
}
private function getLimitPerPage($model = null) {
if($model === null) {
return Listing\Handler::DEFAULT_LIMIT_PER_PAGE;
@ -504,8 +542,4 @@ class Menu {
$notice->displayWPNotice();
}
}
static function isOnMailPoetAdminPage() {
return (!empty($_REQUEST['page']) && stripos($_REQUEST['page'], 'mailpoet-') !== false);
}
}

View File

@ -106,7 +106,7 @@ class Migrator {
function sendingQueues() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'type varchar(12) NULL DEFAULT NULL,',
'type varchar(90) NULL DEFAULT NULL,',
'newsletter_id mediumint(9) NOT NULL,',
'newsletter_rendered_body longtext,',
'newsletter_rendered_subject varchar(250) NULL DEFAULT NULL,',

View File

@ -3,11 +3,11 @@ namespace MailPoet\Config;
use MailPoet\Cron\CronTrigger;
use MailPoet\Mailer\MailerLog;
use \MailPoet\Models\Segment;
use \MailPoet\Segments\WP;
use \MailPoet\Models\Setting;
use \MailPoet\Settings\Pages;
use \MailPoet\Util\Helpers;
use MailPoet\Models\Segment;
use MailPoet\Segments\WP;
use MailPoet\Models\Setting;
use MailPoet\Settings\Pages;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
@ -138,7 +138,7 @@ class Populator {
$default_segment->hydrate(array(
'name' => __('My First List', 'mailpoet'),
'description' =>
__('This list is automatically created when you install MailPoet', 'mailpoet')
__('This list is automatically created when you install MailPoet.', 'mailpoet')
));
$default_segment->save();
}

View File

@ -52,7 +52,7 @@ class NewsletterBlank121Column {
"blocks" => array(
array(
"type" => "header",
"text" => __("Display problems? <a href=\"[link:newsletter_view_in_browser_url]\">Open this email in your web browser</a>", 'mailpoet'),
"text" => __("Display problems? <a href=\"[link:newsletter_view_in_browser_url]\">Open this email in your web browser.</a>", 'mailpoet'),
"styles" => array(
"block" => array(
"backgroundColor" => "transparent"
@ -116,7 +116,7 @@ class NewsletterBlank121Column {
),
array(
"type" => "text",
"text" => __("<h1 style=\"text-align: center;\"><strong>Let's Get Started!</strong></h1>\n<p>It's time to design your newsletter! On the right sidebar, you'll find four menu items that will help you customize your newsletter:</p>\n<ol>\n<li>Content</li>\n<li>Layout</li>\n<li>Styles</li>\n<li>Preview</li>\n</ol>", 'mailpoet')
"text" => __("<h1 style=\"text-align: center;\"><strong>Let's Get Started!</strong></h1>\n<p>It's time to design your newsletter! In the right sidebar, you'll find four menu items that will help you customize your newsletter:</p>\n<ol>\n<li>Content</li>\n<li>Layout</li>\n<li>Styles</li>\n<li>Preview</li>\n</ol>", 'mailpoet')
),
array(
"type" => "divider",

View File

@ -52,7 +52,7 @@ class NewsletterBlank12Column {
"blocks" => array(
array(
"type" => "header",
"text" => __("Display problems? <a href=\"[link:newsletter_view_in_browser_url]\">Open this email in your web browser</a>", 'mailpoet'),
"text" => __("Display problems? <a href=\"[link:newsletter_view_in_browser_url]\">Open this email in your web browser.</a>", 'mailpoet'),
"styles" => array(
"block" => array(
"backgroundColor" => "transparent"

View File

@ -52,7 +52,7 @@ class NewsletterBlank13Column {
"blocks" => array(
array(
"type" => "header",
"text" => __("Display problems? <a href=\"[link:newsletter_view_in_browser_url]\">Open this email in your web browser</a>", 'mailpoet'),
"text" => __("Display problems? <a href=\"[link:newsletter_view_in_browser_url]\">Open this email in your web browser.</a>", 'mailpoet'),
"styles" => array(
"block" => array(
"backgroundColor" => "transparent"
@ -116,7 +116,7 @@ class NewsletterBlank13Column {
),
array(
"type" => "text",
"text" => __("<h1 style=\"text-align: center;\"><strong>Let's Get Started! </strong></h1>\n<p></p>\n<p>It's time to design your newsletter! On the right sidebar, you'll find four menu items that will help you customize your newsletter:</p>\n<ol>\n<li>Content</li>\n<li>Layout</li>\n<li>Styles</li>\n<li>Preview</li>\n</ol>", 'mailpoet')
"text" => __("<h1 style=\"text-align: center;\"><strong>Let's Get Started! </strong></h1>\n<p></p>\n<p>It's time to design your newsletter! In the right sidebar, you'll find four menu items that will help you customize your newsletter:</p>\n<ol>\n<li>Content</li>\n<li>Layout</li>\n<li>Styles</li>\n<li>Preview</li>\n</ol>", 'mailpoet')
),
array(
"type" => "divider",

View File

@ -52,7 +52,7 @@ class NewsletterBlank1Column {
"blocks" => array(
array(
"type" => "header",
"text" => __("Display problems? <a href=\"[link:newsletter_view_in_browser_url]\">Open this email in your web browser</a>", 'mailpoet'),
"text" => __("Display problems? <a href=\"[link:newsletter_view_in_browser_url]\">Open this email in your web browser.</a>", 'mailpoet'),
"styles" => array(
"block" => array(
"backgroundColor" => "transparent"

View File

@ -52,7 +52,7 @@ class PostNotificationsBlank1Column {
"blocks" => array(
array(
"type" => "header",
"text" => __("Display problems? <a href=\"[link:newsletter_view_in_browser_url]\">Open this email in your web browser</a>", 'mailpoet'),
"text" => __("Display problems? <a href=\"[link:newsletter_view_in_browser_url]\">Open this email in your web browser.</a>", 'mailpoet'),
"styles" => array(
"block" => array(
"backgroundColor" => "transparent"
@ -116,7 +116,7 @@ class PostNotificationsBlank1Column {
),
array(
"type" => "text",
"text" => __("<h1 style=\"text-align: center;\"><strong>Check Out Our New Blog Posts! </strong></h1>\n<p></p>\n<p>MailPoet can <span style=\"line-height: 1.6em; background-color: inherit;\"><em>automatically</em> </span><span style=\"line-height: 1.6em; background-color: inherit;\">send your new blog posts to your subscribers.</span></p>\n<p><span style=\"line-height: 1.6em; background-color: inherit;\"></span></p>\n<p><span style=\"line-height: 1.6em; background-color: inherit;\">Below, you'll find three recent posts, which are displayed automatically, thanks to the <em>Automatic Latest Content</em> widget, which can be found on the right sidebar, under <em>Content</em>.</span></p>\n<p><span style=\"line-height: 1.6em; background-color: inherit;\"></span></p>\n<p><span style=\"line-height: 1.6em; background-color: inherit;\">To edit the settings and styles of your post, simply click on a post below.</span></p>", 'mailpoet')
"text" => __("<h1 style=\"text-align: center;\"><strong>Check Out Our New Blog Posts! </strong></h1>\n<p></p>\n<p>MailPoet can <span style=\"line-height: 1.6em; background-color: inherit;\"><em>automatically</em> </span><span style=\"line-height: 1.6em; background-color: inherit;\">send your new blog posts to your subscribers.</span></p>\n<p><span style=\"line-height: 1.6em; background-color: inherit;\"></span></p>\n<p><span style=\"line-height: 1.6em; background-color: inherit;\">Below, you'll find three recent posts, which are displayed automatically, thanks to the <em>Automatic Latest Content</em> widget, which can be found in the right sidebar, under <em>Content</em>.</span></p>\n<p><span style=\"line-height: 1.6em; background-color: inherit;\"></span></p>\n<p><span style=\"line-height: 1.6em; background-color: inherit;\">To edit the settings and styles of your post, simply click on a post below.</span></p>", 'mailpoet')
),
array(
"type" => "divider",

View File

@ -70,7 +70,7 @@ class StoreDiscount {
)
), array(
"type" => "text",
"text" => __("<p></p>\n<p>Hi [subscriber:firstname | default:reader]</p>\n<p class=\"\"></p>\n<p>Fancy 15% off your next order? Use this coupon on all your favourite products from our store until Wednesday. Just enter the code on the payments page and your discount will applied.</p>", 'mailpoet')
"text" => __("<p></p>\n<p>Hi [subscriber:firstname | default:reader]</p>\n<p class=\"\"></p>\n<p>Fancy 15% off your next order? Use this coupon on any product in our store. Expires Wednesday! To apply the discount, enter the code on the payments page.</p>", 'mailpoet')
), array(
"type" => "spacer",
"styles" => array(
@ -110,7 +110,7 @@ class StoreDiscount {
)
), array(
"type" => "text",
"text" => __("<h1 style=\"text-align: center;\"><em><strong>Get 15% discount off your next order</strong></em></h1>", 'mailpoet')
"text" => __("<h1 style=\"text-align: center;\"><em><strong>Get a 15% off your next order</strong></em></h1>", 'mailpoet')
), array(
"type" => "text",
"text" => __("<h2 style=\"text-align: center;\"><strong>USE CODE: WELOVEMAILPOET</strong></h2>", 'mailpoet')
@ -204,7 +204,7 @@ class StoreDiscount {
"type" => "image",
"link" => "",
"src" => $this->template_image_url . "/red-icycle.jpg",
"alt" => __("red-icycle", 'mailpoet'),
"alt" => __("red-bicycle", 'mailpoet'),
"fullWidth" => false,
"width" => "558px",
"height" => "399px",

View File

@ -70,7 +70,7 @@ class TravelEmail {
)
), array(
"type" => "text",
"text" => __("<h1 style=\"text-align: center;\">Hi [subscriber:firstname | default:reader]!</h1>\n<p></p>\n<p>Greetings from New Zealand, we're here enjoying the sights and sounds (and bad smells!) of Rotarua. Yesterday we took advantage of the local amenities and visited the hot springs! </p>\n<p>Don't forget to stay up-to-date via twitter!</p>", 'mailpoet')
"text" => __("<h1 style=\"text-align: center;\">Hi [subscriber:firstname | default:reader]!</h1>\n<p></p>\n<p>Greetings from New Zealand! We're here enjoying the sights, sounds, and smells of Rotarua! Yesterday, we visited the local hot springs, and today, we're going swimming.</p>\n<p>Don't forget to stay updated with Twitter!</p>", 'mailpoet')
), array(
"type" => "social",
"iconSet" => "circles",
@ -324,7 +324,7 @@ class TravelEmail {
"type" => "image",
"link" => "",
"src" => $this->template_image_url . "/glow-worms.jpg",
"alt" => __("Glow worms, Waitomo Caves", 'mailpoet'),
"alt" => __("Glowworms, Waitomo Caves", 'mailpoet'),
"fullWidth" => true,
"width" => "640px",
"height" => "428px",
@ -335,7 +335,7 @@ class TravelEmail {
)
), array(
"type" => "text",
"text" => __("<p><em><a href=\"http://www.waitomo.com/Waitomo-Glowworm-Caves/Pages/default.aspx\"><strong>Waitomo Glow Worm Caves</strong></a></em></p>", 'mailpoet')
"text" => __("<p><em><a href=\"http://www.waitomo.com/Waitomo-Glowworm-Caves/Pages/default.aspx\"><strong>Waitomo GlowWorm Caves</strong></a></em></p>", 'mailpoet')
))
), array(
"type" => "container",
@ -446,7 +446,7 @@ class TravelEmail {
)
), array(
"type" => "header",
"text" => ("Display problems? <a href=\"[link:newsletter_view_in_browser_url]\">Open this email in your web browser</a>"),
"text" => ("Display problems? <a href=\"[link:newsletter_view_in_browser_url]\">Open this email in your web browser.</a>"),
"styles" => array(
"block" => array(
"backgroundColor" => "transparent"

View File

@ -52,7 +52,7 @@ class WelcomeBlank12Column {
"blocks" => array(
array(
"type" => "header",
"text" => __("Display problems? <a href=\"[link:newsletter_view_in_browser_url]\">Open this email in your web browser</a>", 'mailpoet'),
"text" => __("Display problems? <a href=\"[link:newsletter_view_in_browser_url]\">Open this email in your web browser.</a>", 'mailpoet'),
"styles" => array(
"block" => array(
"backgroundColor" => "transparent"

View File

@ -52,7 +52,7 @@ class WelcomeBlank1Column {
"blocks" => array(
array(
"type" => "header",
"text" => __("Display problems? <a href=\"[link:newsletter_view_in_browser_url]\">Open this email in your web browser</a>", 'mailpoet'),
"text" => __("Display problems? <a href=\"[link:newsletter_view_in_browser_url]\">Open this email in your web browser.</a>", 'mailpoet'),
"styles" => array(
"block" => array(
"backgroundColor" => "transparent"

View File

@ -1,9 +1,9 @@
<?php
namespace MailPoet\Config;
use \Twig_Loader_Filesystem as TwigFileSystem;
use \Twig_Environment as TwigEnv;
use \Twig_Lexer as TwigLexer;
use \MailPoet\Twig;
use Twig_Loader_Filesystem as TwigFileSystem;
use Twig_Environment as TwigEnv;
use Twig_Lexer as TwigLexer;
use MailPoet\Twig;
if(!defined('ABSPATH')) exit;

View File

@ -1,14 +1,12 @@
<?php
namespace MailPoet\Config;
use MailPoet\Config\Env;
use MailPoet\Util\Helpers;
use MailPoet\WP\Notice as WPNotice;
if(!defined('ABSPATH')) exit;
class RequirementsChecker {
const TEST_PHP_VERSION = 'PHPVersion';
const TEST_FOLDER_PERMISSIONS = 'TempAndCacheFolderCreation';
const TEST_PDO_EXTENSION = 'PDOExtension';
const TEST_MBSTRING_EXTENSION = 'MbstringExtension';
@ -16,25 +14,26 @@ class RequirementsChecker {
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'
'\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',
'\Sabberworm\CSS\Parser'
);
function __construct($display_error_notice = true) {
@ -44,7 +43,6 @@ class RequirementsChecker {
function checkAllRequirements() {
$available_tests = array(
self::TEST_PDO_EXTENSION,
self::TEST_PHP_VERSION,
self::TEST_FOLDER_PERMISSIONS,
self::TEST_MBSTRING_EXTENSION,
self::TEST_VENDOR_SOURCE
@ -56,17 +54,6 @@ class RequirementsChecker {
return $results;
}
function checkPHPVersion() {
if(version_compare(phpversion(), '5.3.0', '<')) {
$error = Helpers::replaceLinkTags(
__('This plugin requires PHP version 5.3 or newer. Please read our [link]instructions[/link] on how to resolve this issue.', 'mailpoet'),
'//docs.mailpoet.com/article/152-minimum-requirements-for-mailpoet-3#php_version'
);
return $this->processError($error);
}
return true;
}
function checkTempAndCacheFolderCreation() {
$paths = array(
'temp_path' => Env::$temp_path,
@ -75,7 +62,7 @@ class RequirementsChecker {
if(!is_dir($paths['cache_path']) && !wp_mkdir_p($paths['cache_path'])) {
$error = Helpers::replaceLinkTags(
__('This plugin requires write permissions inside the /wp-content/uploads folder. Please read our [link]instructions[/link] on how to resolve this issue.', 'mailpoet'),
'//docs.mailpoet.com/article/152-minimum-requirements-for-mailpoet-3#folder_permissions'
'//beta.docs.mailpoet.com/article/152-minimum-requirements-for-mailpoet-3#folder_permissions'
);
return $this->processError($error);
}
@ -92,14 +79,12 @@ class RequirementsChecker {
}
function checkPDOExtension() {
if(!extension_loaded('pdo') && !extension_loaded('pdo_mysql')) {
$error = Helpers::replaceLinkTags(
__('This plugin requires PDO_MYSQL PHP extension. Please read our [link]instructions[/link] on how to resolve this issue.', 'mailpoet'),
'//docs.mailpoet.com/article/152-minimum-requirements-for-mailpoet-3#php_extension'
);
return $this->processError($error);
}
return true;
if(extension_loaded('pdo') && extension_loaded('pdo_mysql')) return true;
$error = Helpers::replaceLinkTags(
__('This plugin requires the PDO_MYSQL PHP extension. Please read our [link]instructions[/link] on how to resolve this issue.', 'mailpoet'),
'//beta.docs.mailpoet.com/article/152-minimum-requirements-for-mailpoet-3#php_extension'
);
return $this->processError($error);
}
function checkMbstringExtension() {

View File

@ -0,0 +1,49 @@
<?php
namespace MailPoet\Config;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Services\Bridge;
use MailPoet\Util\Helpers;
use MailPoet\WP\Notice as WPNotice;
if(!defined('ABSPATH')) exit;
class ServicesChecker {
function checkMailPoetAPIKeyValid($display_error_notice = true) {
if(!Bridge::isMPSendingServiceEnabled()) {
return null;
}
$result = Setting::getValue(Bridge::API_KEY_STATE_SETTING_NAME);
if(empty($result['state']) || $result['state'] == Bridge::MAILPOET_KEY_VALID) {
return true;
}
if($result['state'] == Bridge::MAILPOET_KEY_INVALID) {
$error = Helpers::replaceLinkTags(
__('All sending is currently paused! Your key to send with MailPoet is invalid. [link]Visit MailPoet.com to purchase a key[/link]', 'mailpoet'),
'https://account.mailpoet.com?s=' . Subscriber::getTotalSubscribers()
);
if($display_error_notice) {
WPNotice::displayError($error);
}
return false;
} elseif($result['state'] == Bridge::MAILPOET_KEY_EXPIRING
&& !empty($result['data']['expire_at'])
) {
$date = date('Y-m-d', strtotime($result['data']['expire_at']));
$error = Helpers::replaceLinkTags(
__('Your newsletters are awesome! Don\'t forget to [link]upgrade your MailPoet email plan[/link] by %s to keep sending them to your subscribers.', 'mailpoet'),
'https://account.mailpoet.com?s=' . Subscriber::getTotalSubscribers()
);
$error = sprintf($error, $date);
if($display_error_notice) {
WPNotice::displayWarning($error);
}
return true;
}
return true;
}
}

View File

@ -1,8 +1,8 @@
<?php
namespace MailPoet\Config;
use \MailPoet\Models\Newsletter;
use \MailPoet\Models\Subscriber;
use \MailPoet\Models\SubscriberSegment;
use MailPoet\Models\Newsletter;
use MailPoet\Models\Subscriber;
use MailPoet\Models\SubscriberSegment;
use MailPoet\Newsletter\Url as NewsletterUrl;
class Shortcodes {

View File

@ -1,7 +1,6 @@
<?php
namespace MailPoet\Config;
use \MailPoet\Util\Security;
use \MailPoet\Models\Form;
use MailPoet\Models\Form;
if(!defined('ABSPATH')) exit;

View File

@ -3,6 +3,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;
use MailPoet\Cron\Workers\SendingServiceKeyCheck as SendingServiceKeyCheckWorker;
if(!defined('ABSPATH')) exit;
require_once(ABSPATH . 'wp-includes/pluggable.php');
@ -48,6 +49,7 @@ class Daemon {
try {
$this->executeScheduleWorker();
$this->executeQueueWorker();
$this->executeSendingServiceKeyCheckWorker();
$this->executeBounceWorker();
} catch(\Exception $e) {
// continue processing, no need to handle errors
@ -80,6 +82,11 @@ class Daemon {
return $queue->process();
}
function executeSendingServiceKeyCheckWorker() {
$worker = new SendingServiceKeyCheckWorker($this->timer);
return $worker->process();
}
function executeBounceWorker() {
$bounce = new BounceWorker($this->timer);
return $bounce->process();

View File

@ -5,7 +5,9 @@ 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\Cron\Workers\SendingServiceKeyCheck as SendingServiceKeyCheckWorker;
use MailPoet\Mailer\MailerLog;
use MailPoet\Services\Bridge;
if(!defined('ABSPATH')) exit;
@ -17,14 +19,24 @@ class WordPress {
}
static function checkExecutionRequirements() {
// sending queue
$scheduled_queues = SchedulerWorker::getScheduledQueues();
$running_queues = SendingQueueWorker::getRunningQueues();
$sending_limit_reached = MailerLog::isSendingLimitReached();
$bounce_sync_available = BounceWorker::checkBounceSyncAvailable();
// sending service
$mp_sending_enabled = Bridge::isMPSendingServiceEnabled();
// bounce sync
$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));
// sending service key check
$sskeycheck_due_queues = SendingServiceKeyCheckWorker::getAllDueQueues();
$sskeycheck_future_queues = SendingServiceKeyCheckWorker::getFutureQueues();
// check requirements for each worker
$sending_queue_active = (($scheduled_queues || $running_queues) && !$sending_limit_reached);
$bounce_sync_active = ($mp_sending_enabled && ($bounce_due_queues || !$bounce_future_queues));
$sending_service_key_check_active = ($mp_sending_enabled && ($sskeycheck_due_queues || !$sskeycheck_future_queues));
return ($sending_queue_active || $bounce_sync_active || $sending_service_key_check_active);
}
static function cleanup() {

View File

@ -6,15 +6,19 @@ use MailPoet\Cron\CronHelper;
use MailPoet\Mailer\Mailer;
use MailPoet\Models\SendingQueue;
use MailPoet\Models\Subscriber;
use MailPoet\Services\Bridge;
use MailPoet\Services\Bridge\API;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
class Bounce {
const TASK_TYPE = 'bounce';
const BATCH_SIZE = 100;
const BOUNCED_HARD = 'hard';
const BOUNCED_SOFT = 'soft';
const NOT_BOUNCED = null;
const BATCH_SIZE = 100;
public $timer;
public $api;
@ -25,21 +29,15 @@ class Bounce {
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']);
$this->api = new API($mailer_config['mailpoet_api_key']);
}
}
function process() {
if(!self::checkBounceSyncAvailable()) {
if(!Bridge::isMPSendingServiceEnabled()) {
return false;
}
@ -64,7 +62,7 @@ class Bounce {
}
static function scheduleBounceSync() {
$already_scheduled = SendingQueue::where('type', 'bounce')
$already_scheduled = SendingQueue::where('type', self::TASK_TYPE)
->whereNull('deleted_at')
->where('status', SendingQueue::STATUS_SCHEDULED)
->findMany();
@ -72,7 +70,7 @@ class Bounce {
return false;
}
$queue = SendingQueue::create();
$queue->type = 'bounce';
$queue->type = self::TASK_TYPE;
$queue->status = SendingQueue::STATUS_SCHEDULED;
$queue->priority = SendingQueue::PRIORITY_LOW;
$queue->scheduled_at = self::getNextRunDate();
@ -143,7 +141,7 @@ class Bounce {
}
function processEmails(array $subscriber_emails) {
$checked_emails = $this->api->check($subscriber_emails);
$checked_emails = $this->api->checkBounces($subscriber_emails);
$this->processApiResponse((array)$checked_emails);
}
@ -170,7 +168,7 @@ class Bounce {
static function getScheduledQueues($future = false) {
$dateWhere = ($future) ? 'whereGt' : 'whereLte';
return SendingQueue::where('type', 'bounce')
return SendingQueue::where('type', self::TASK_TYPE)
->$dateWhere('scheduled_at', Carbon::createFromTimestamp(current_time('timestamp')))
->whereNull('deleted_at')
->where('status', SendingQueue::STATUS_SCHEDULED)
@ -178,7 +176,7 @@ class Bounce {
}
static function getRunningQueues() {
return SendingQueue::where('type', 'bounce')
return SendingQueue::where('type', self::TASK_TYPE)
->whereLte('scheduled_at', Carbon::createFromTimestamp(current_time('timestamp')))
->whereNull('deleted_at')
->whereNull('status')

View File

@ -1,41 +0,0 @@
<?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)
);
}
}

View File

@ -0,0 +1,147 @@
<?php
namespace MailPoet\Cron\Workers;
use Carbon\Carbon;
use MailPoet\Cron\CronHelper;
use MailPoet\Mailer\Mailer;
use MailPoet\Models\SendingQueue;
use MailPoet\Services\Bridge;
if(!defined('ABSPATH')) exit;
class SendingServiceKeyCheck {
const TASK_TYPE = 'sending_service_key_check';
const UNAVAILABLE_SERVICE_RESCHEDULE_TIMEOUT = 60;
public $timer;
public $bridge;
function __construct($timer = false) {
$this->timer = ($timer) ? $timer : microtime(true);
// abort if execution limit is reached
CronHelper::enforceExecutionLimit($this->timer);
}
function initApi() {
if(!$this->bridge) {
$this->bridge = new Bridge();
}
}
function process() {
if(!Bridge::isMPSendingServiceEnabled()) {
return false;
}
$this->initApi();
$scheduled_queues = self::getScheduledQueues();
$running_queues = self::getRunningQueues();
if(!$scheduled_queues && !$running_queues) {
self::schedule();
return false;
}
foreach($scheduled_queues as $i => $queue) {
$this->prepareQueue($queue);
}
foreach($running_queues as $i => $queue) {
$this->processQueue($queue);
}
return true;
}
static function schedule() {
$already_scheduled = SendingQueue::where('type', self::TASK_TYPE)
->whereNull('deleted_at')
->where('status', SendingQueue::STATUS_SCHEDULED)
->findMany();
if($already_scheduled) {
return false;
}
$queue = SendingQueue::create();
$queue->type = self::TASK_TYPE;
$queue->status = SendingQueue::STATUS_SCHEDULED;
$queue->priority = SendingQueue::PRIORITY_LOW;
$queue->scheduled_at = self::getNextRunDate();
$queue->newsletter_id = 0;
$queue->save();
return $queue;
}
function prepareQueue(SendingQueue $queue) {
$queue->status = null;
$queue->save();
// abort if execution limit is reached
CronHelper::enforceExecutionLimit($this->timer);
return true;
}
function processQueue(SendingQueue $queue) {
// abort if execution limit is reached
CronHelper::enforceExecutionLimit($this->timer);
try {
$mailer_config = Mailer::getMailerConfig();
$result = $this->bridge->checkKey($mailer_config['mailpoet_api_key']);
$this->bridge->updateSubscriberCount($result);
} catch (\Exception $e) {
$result = false;
}
if(empty($result['code']) || $result['code'] == Bridge::CHECK_ERROR_UNAVAILABLE) {
// reschedule the check
$scheduled_at = Carbon::createFromTimestamp(current_time('timestamp'));
$queue->scheduled_at = $scheduled_at->addMinutes(
self::UNAVAILABLE_SERVICE_RESCHEDULE_TIMEOUT
);
$queue->save();
return false;
}
$queue->processed_at = current_time('mysql');
$queue->status = SendingQueue::STATUS_COMPLETED;
$queue->save();
return true;
}
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', self::TASK_TYPE)
->$dateWhere('scheduled_at', Carbon::createFromTimestamp(current_time('timestamp')))
->whereNull('deleted_at')
->where('status', SendingQueue::STATUS_SCHEDULED)
->findMany();
}
static function getRunningQueues() {
return SendingQueue::where('type', self::TASK_TYPE)
->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);
}
}

View File

@ -7,7 +7,7 @@ abstract class Base {
if($block['id'] === 'email') {
$rules['required'] = true;
$rules['error-message'] = __('Please specify a valid email address', 'mailpoet');
$rules['error-message'] = __('Please specify a valid email address.', 'mailpoet');
}
if($block['id'] === 'segments') {
@ -132,4 +132,4 @@ abstract class Base {
}
return join(' ', $modifiers);
}
}
}

View File

@ -1,7 +1,5 @@
<?php
namespace MailPoet\Form;
use MailPoet\Form\Block;
use MailPoet\Form\Util;
if(!defined('ABSPATH')) exit;

View File

@ -1,13 +1,9 @@
<?php
namespace MailPoet\Form;
use \MailPoet\Config\Renderer;
use \MailPoet\Models\Form;
use \MailPoet\Models\Segment;
use \MailPoet\Models\Setting;
use \MailPoet\Models\Subscriber;
use \MailPoet\Form\Renderer as FormRenderer;
use \MailPoet\Form\Util;
use \MailPoet\Util\Security;
use MailPoet\Config\Renderer;
use MailPoet\Models\Form;
use MailPoet\Form\Renderer as FormRenderer;
use MailPoet\Util\Security;
if(!defined('ABSPATH')) exit;

View File

@ -28,9 +28,9 @@ class Mailer {
$this->mailer_instance = $this->buildMailer();
}
function send($newsletter, $subscriber) {
function send($newsletter, $subscriber, $extra_params = array()) {
$subscriber = $this->formatSubscriberNameAndEmailAddress($subscriber);
return $this->mailer_instance->send($newsletter, $subscriber);
return $this->mailer_instance->send($newsletter, $subscriber, $extra_params);
}
function buildMailer() {
@ -80,7 +80,7 @@ class Mailer {
);
break;
default:
throw new \Exception(__('Mailing method does not exist', 'mailpoet'));
throw new \Exception(__('Mailing method does not exist.', 'mailpoet'));
}
return $mailer_instance;
}
@ -88,7 +88,7 @@ class Mailer {
static function getMailerConfig($mailer = false) {
if(!$mailer) {
$mailer = Setting::getValue(self::MAILER_CONFIG_SETTING_NAME);
if(!$mailer || !isset($mailer['method'])) throw new \Exception(__('Mailer is not configured', 'mailpoet'));
if(!$mailer || !isset($mailer['method'])) throw new \Exception(__('Mailer is not configured.', 'mailpoet'));
}
if(empty($mailer['frequency'])) {
$default_settings = Setting::getDefaults();
@ -105,7 +105,7 @@ class Mailer {
function getSenderNameAndAddress($sender = false) {
if(empty($sender)) {
$sender = Setting::getValue('sender', array());
if(empty($sender['address'])) throw new \Exception(__('Sender name and email are not configured', 'mailpoet'));
if(empty($sender['address'])) throw new \Exception(__('Sender name and email are not configured.', 'mailpoet'));
}
$from_name = $this->encodeAddressNamePart($sender['name']);
return array(
@ -187,4 +187,4 @@ class Mailer {
'response' => true
);
}
}
}

View File

@ -18,6 +18,7 @@ class AmazonSES {
public $sender;
public $reply_to;
public $return_path;
public $message;
public $date;
public $date_without_time;
private $available_regions = array(
@ -31,7 +32,7 @@ class AmazonSES {
$this->aws_secret_key = $secret_key;
$this->aws_region = (in_array($region, $this->available_regions)) ? $region : false;
if(!$this->aws_region) {
throw new \Exception(__('Unsupported Amazon SES region.', 'mailpoet'));
throw new \Exception(__('Unsupported Amazon SES region', 'mailpoet'));
}
$this->aws_endpoint = sprintf('email.%s.amazonaws.com', $this->aws_region);
$this->aws_signing_algorithm = 'AWS4-HMAC-SHA256';
@ -48,10 +49,10 @@ class AmazonSES {
$this->date_without_time = gmdate('Ymd');
}
function send($newsletter, $subscriber) {
function send($newsletter, $subscriber, $extra_params = array()) {
$result = wp_remote_post(
$this->url,
$this->request($newsletter, $subscriber)
$this->request($newsletter, $subscriber, $extra_params)
);
if(is_wp_error($result)) {
return Mailer::formatMailerConnectionErrorResult($result->get_error_message());
@ -66,27 +67,61 @@ class AmazonSES {
return Mailer::formatMailerSendSuccessResult();
}
function getBody($newsletter, $subscriber) {
function getBody($newsletter, $subscriber, $extra_params = array()) {
$this->message = $this->createMessage($newsletter, $subscriber, $extra_params);
$body = array(
'Action' => 'SendEmail',
'Action' => 'SendRawEmail',
'Version' => '2010-12-01',
'Destination.ToAddresses.member.1' => $subscriber,
'Source' => $this->sender['from_name_email'],
'ReplyToAddresses.member.1' => $this->reply_to['reply_to_name_email'],
'Message.Subject.Data' => $newsletter['subject'],
'ReturnPath' => $this->return_path
'RawMessage.Data' => $this->encodeMessage($this->message)
);
if(!empty($newsletter['body']['html'])) {
$body['Message.Body.Html.Data'] = $newsletter['body']['html'];
}
if(!empty($newsletter['body']['text'])) {
$body['Message.Body.Text.Data'] = $newsletter['body']['text'];
}
return $body;
}
function request($newsletter, $subscriber) {
$body = array_map('urlencode', $this->getBody($newsletter, $subscriber));
function createMessage($newsletter, $subscriber, $extra_params = array()) {
$message = \Swift_Message::newInstance()
->setTo($this->processSubscriber($subscriber))
->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($extra_params['unsubscribe_url'])) {
$headers = $message->getHeaders();
$headers->addTextHeader('List-Unsubscribe', '<' . $extra_params['unsubscribe_url'] . '>');
}
if(!empty($newsletter['body']['html'])) {
$message = $message->setBody($newsletter['body']['html'], 'text/html');
}
if(!empty($newsletter['body']['text'])) {
$message = $message->addPart($newsletter['body']['text'], 'text/plain');
}
return $message;
}
function encodeMessage(\Swift_Message $message) {
return base64_encode($message->toString());
}
function processSubscriber($subscriber) {
preg_match('!(?P<name>.*?)\s<(?P<email>.*?)>!', $subscriber, $subscriber_data);
if(!isset($subscriber_data['email'])) {
$subscriber_data = array(
'email' => $subscriber,
);
}
return array(
$subscriber_data['email'] =>
(isset($subscriber_data['name'])) ? $subscriber_data['name'] : ''
);
}
function request($newsletter, $subscriber, $extra_params = array()) {
$body = array_map('urlencode', $this->getBody($newsletter, $subscriber, $extra_params));
return array(
'timeout' => 10,
'httpversion' => '1.1',
@ -176,4 +211,4 @@ class AmazonSES {
true
);
}
}
}

View File

@ -2,37 +2,46 @@
namespace MailPoet\Mailer\Methods;
use MailPoet\Mailer\Mailer;
use MailPoet\Config\ServicesChecker;
use MailPoet\Services\Bridge;
use MailPoet\Services\Bridge\API;
if(!defined('ABSPATH')) exit;
class MailPoet {
public $url = 'https://bridge.mailpoet.com/api/messages';
public $api_key;
public $api;
public $sender;
public $reply_to;
public $services_checker;
function __construct($api_key, $sender, $reply_to) {
$this->api_key = $api_key;
$this->api = new API($api_key);
$this->sender = $sender;
$this->reply_to = $reply_to;
$this->services_checker = new ServicesChecker(false);
}
function send($newsletter, $subscriber) {
$message_body = $this->getBody($newsletter, $subscriber);
$result = wp_remote_post(
$this->url,
$this->request($message_body)
);
if(is_wp_error($result)) {
return Mailer::formatMailerConnectionErrorResult($result->get_error_message());
}
if(wp_remote_retrieve_response_code($result) !== 201) {
$response = (wp_remote_retrieve_body($result)) ?
wp_remote_retrieve_body($result) :
wp_remote_retrieve_response_message($result);
function send($newsletter, $subscriber, $extra_params = array()) {
if($this->services_checker->checkMailPoetAPIKeyValid() === false) {
$response = __('MailPoet API key is invalid!', 'mailpoet');
return Mailer::formatMailerSendErrorResult($response);
}
return Mailer::formatMailerSendSuccessResult();
$message_body = $this->getBody($newsletter, $subscriber);
$result = $this->api->sendMessages($message_body);
switch($result['status']) {
case API::SENDING_STATUS_CONNECTION_ERROR:
return Mailer::formatMailerConnectionErrorResult($result['message']);
case API::SENDING_STATUS_SEND_ERROR:
if(!empty($result['code']) && $result['code'] === API::RESPONSE_CODE_KEY_INVALID) {
Bridge::invalidateKey();
}
return Mailer::formatMailerSendErrorResult($result['message']);
case API::SENDING_STATUS_OK:
default:
return Mailer::formatMailerSendSuccessResult();
}
}
function processSubscriber($subscriber) {
@ -86,21 +95,4 @@ class MailPoet {
}
return $body;
}
function auth() {
return 'Basic ' . base64_encode('api:' . $this->api_key);
}
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)
);
}
}

View File

@ -20,9 +20,9 @@ class PHPMail {
$this->mailer = $this->buildMailer();
}
function send($newsletter, $subscriber) {
function send($newsletter, $subscriber, $extra_params = array()) {
try {
$message = $this->createMessage($newsletter, $subscriber);
$message = $this->createMessage($newsletter, $subscriber, $extra_params);
$result = $this->mailer->send($message);
} catch(\Exception $e) {
return Mailer::formatMailerSendErrorResult($e->getMessage());
@ -39,7 +39,7 @@ class PHPMail {
return \Swift_Mailer::newInstance($transport);
}
function createMessage($newsletter, $subscriber) {
function createMessage($newsletter, $subscriber, $extra_params = array()) {
$message = \Swift_Message::newInstance()
->setTo($this->processSubscriber($subscriber))
->setFrom(array(
@ -51,6 +51,10 @@ class PHPMail {
))
->setReturnPath($this->return_path)
->setSubject($newsletter['subject']);
if(!empty($extra_params['unsubscribe_url'])) {
$headers = $message->getHeaders();
$headers->addTextHeader('List-Unsubscribe', '<' . $extra_params['unsubscribe_url'] . '>');
}
if(!empty($newsletter['body']['html'])) {
$message = $message->setBody($newsletter['body']['html'], 'text/html');
}

View File

@ -34,9 +34,9 @@ class SMTP {
$this->mailer = $this->buildMailer();
}
function send($newsletter, $subscriber) {
function send($newsletter, $subscriber, $extra_params = array()) {
try {
$message = $this->createMessage($newsletter, $subscriber);
$message = $this->createMessage($newsletter, $subscriber, $extra_params);
$result = $this->mailer->send($message);
} catch(\Exception $e) {
return Mailer::formatMailerSendErrorResult($e->getMessage());
@ -60,7 +60,7 @@ class SMTP {
return \Swift_Mailer::newInstance($transport);
}
function createMessage($newsletter, $subscriber) {
function createMessage($newsletter, $subscriber, $extra_params = array()) {
$message = \Swift_Message::newInstance()
->setTo($this->processSubscriber($subscriber))
->setFrom(array(
@ -72,6 +72,10 @@ class SMTP {
))
->setReturnPath($this->return_path)
->setSubject($newsletter['subject']);
if(!empty($extra_params['unsubscribe_url'])) {
$headers = $message->getHeaders();
$headers->addTextHeader('List-Unsubscribe', '<' . $extra_params['unsubscribe_url'] . '>');
}
if(!empty($newsletter['body']['html'])) {
$message = $message->setBody($newsletter['body']['html'], 'text/html');
}

View File

@ -17,10 +17,10 @@ class SendGrid {
$this->reply_to = $reply_to;
}
function send($newsletter, $subscriber) {
function send($newsletter, $subscriber, $extra_params = array()) {
$result = wp_remote_post(
$this->url,
$this->request($newsletter, $subscriber)
$this->request($newsletter, $subscriber, $extra_params)
);
if(is_wp_error($result)) {
return Mailer::formatMailerConnectionErrorResult($result->get_error_message());
@ -35,7 +35,7 @@ class SendGrid {
return Mailer::formatMailerSendSuccessResult();
}
function getBody($newsletter, $subscriber) {
function getBody($newsletter, $subscriber, $extra_params = array()) {
$body = array(
'to' => $subscriber,
'from' => $this->sender['from_email'],
@ -43,6 +43,13 @@ class SendGrid {
'replyto' => $this->reply_to['reply_to_email'],
'subject' => $newsletter['subject']
);
$headers = array();
if(!empty($extra_params['unsubscribe_url'])) {
$headers['List-Unsubscribe'] = '<' . $extra_params['unsubscribe_url'] . '>';
}
if($headers) {
$body['headers'] = json_encode($headers);
}
if(!empty($newsletter['body']['html'])) {
$body['html'] = $newsletter['body']['html'];
}
@ -56,8 +63,8 @@ class SendGrid {
return 'Bearer ' . $this->api_key;
}
function request($newsletter, $subscriber) {
$body = $this->getBody($newsletter, $subscriber);
function request($newsletter, $subscriber, $extra_params = array()) {
$body = $this->getBody($newsletter, $subscriber, $extra_params);
return array(
'timeout' => 10,
'httpversion' => '1.1',

View File

@ -11,10 +11,10 @@ class CustomField extends Model {
function __construct() {
parent::__construct();
$this->addValidations('name', array(
'required' => __('Please specify a name', 'mailpoet')
'required' => __('Please specify a name.', 'mailpoet')
));
$this->addValidations('type', array(
'required' => __('Please specify a type', 'mailpoet')
'required' => __('Please specify a type.', 'mailpoet')
));
}
@ -142,4 +142,4 @@ class CustomField extends Model {
return $custom_field->save();
}
}
}

View File

@ -10,7 +10,7 @@ class Form extends Model {
parent::__construct();
$this->addValidations('name', array(
'required' => __('Please specify a name', 'mailpoet')
'required' => __('Please specify a name.', 'mailpoet')
));
}
@ -39,6 +39,49 @@ class Form extends Model {
return parent::save();
}
function getFieldList() {
$form = $this->asArray();
if(empty($form['body'])) {
return false;
}
$skipped_types = array('html', 'divider', 'submit');
$fields = array();
foreach((array)$form['body'] as $field) {
if(empty($field['id'])
|| empty($field['type'])
|| in_array($field['type'], $skipped_types)
) {
continue;
}
if($field['id'] > 0) {
$fields[] = 'cf_' . $field['id'];
} else {
$fields[] = $field['id'];
}
}
return $fields ?: false;
}
function filterSegments(array $segment_ids = array()) {
$form = $this->asArray();
if(empty($form['settings']['segments'])) {
return array();
}
if(!empty($form['settings']['segments_selected_by'])
&& $form['settings']['segments_selected_by'] == 'user'
) {
$segment_ids = array_intersect($segment_ids, $form['settings']['segments']);
} else {
$segment_ids = $form['settings']['segments'];
}
return $segment_ids;
}
static function search($orm, $search = '') {
return $orm->whereLike('name', '%'.$search.'%');
}

View File

@ -24,7 +24,7 @@ class Newsletter extends Model {
function __construct() {
parent::__construct();
$this->addValidations('type', array(
'required' => __('Please specify a type', 'mailpoet')
'required' => __('Please specify a type.', 'mailpoet')
));
}
@ -593,6 +593,7 @@ class Newsletter extends Model {
return self::select(array(
'id',
'subject',
'hash',
'type',
'status',
'updated_at',
@ -714,4 +715,4 @@ class Newsletter extends Model {
self::NEWSLETTER_HASH_LENGTH
);
}
}
}

View File

@ -9,10 +9,10 @@ class NewsletterOptionField extends Model {
function __construct() {
parent::__construct();
$this->addValidations('name', array(
'required' => __('Please specify a name', 'mailpoet')
'required' => __('Please specify a name.', 'mailpoet')
));
$this->addValidations('newsletter_type', array(
'required' => __('Please specify a newsletter type', 'mailpoet')
'required' => __('Please specify a newsletter type.', 'mailpoet')
));
}

View File

@ -10,10 +10,10 @@ class NewsletterTemplate extends Model {
parent::__construct();
$this->addValidations('name', array(
'required' => __('Please specify a name', 'mailpoet')
'required' => __('Please specify a name.', 'mailpoet')
));
$this->addValidations('body', array(
'required' => __('The template body cannot be empty', 'mailpoet')
'required' => __('The template body cannot be empty.', 'mailpoet')
));
}

View File

@ -10,7 +10,7 @@ class Segment extends Model {
parent::__construct();
$this->addValidations('name', array(
'required' => __('Please specify a name', 'mailpoet')
'required' => __('Please specify a name.', 'mailpoet')
));
}
@ -106,7 +106,7 @@ class Segment extends Model {
$wp_segment->hydrate(array(
'name' => __('WordPress Users', 'mailpoet'),
'description' =>
__('This lists containts all of your WordPress users', 'mailpoet'),
__('This list contains all of your WordPress users.', 'mailpoet'),
'type' => 'wp_users'
));
$wp_segment->save();

View File

@ -19,7 +19,7 @@ class Setting extends Model {
parent::__construct();
$this->addValidations('name', array(
'required' => __('Please specify a name', 'mailpoet')
'required' => __('Please specify a name.', 'mailpoet')
));
}

View File

@ -280,7 +280,9 @@ class Subscriber extends Model {
'value' => ''
);
$subscribers_without_segment = self::filter('withoutSegments')->count();
$subscribers_without_segment = self::filter('withoutSegments')
->whereNull('deleted_at')
->count();
$subscribers_without_segment_label = sprintf(
__('Subscribers without a list (%s)', 'mailpoet'),
number_format($subscribers_without_segment)
@ -485,6 +487,17 @@ class Subscriber extends Model {
unset($data['segments']);
}
// fields that must exist
$not_null_fields = array(
'first_name' => '',
'last_name' => ''
);
foreach($not_null_fields as $field => $value) {
if(!isset($data[$field])) {
$data[$field] = $value;
}
}
// custom fields
$custom_fields = array();
foreach($data as $key => $value) {
@ -771,13 +784,18 @@ class Subscriber extends Model {
static function withoutSegments($orm) {
return $orm->select(MP_SUBSCRIBERS_TABLE.'.*')
->leftOuterJoin(
MP_SUBSCRIBER_SEGMENT_TABLE,
->rawJoin(
'LEFT OUTER JOIN (
SELECT `subscriber_id`
FROM '.MP_SUBSCRIBER_SEGMENT_TABLE.'
WHERE `status` = "'.self::STATUS_SUBSCRIBED.'"
)',
array(
MP_SUBSCRIBERS_TABLE.'.id',
'=',
MP_SUBSCRIBER_SEGMENT_TABLE.'.subscriber_id'
)
),
MP_SUBSCRIBER_SEGMENT_TABLE
)
->whereNull(MP_SUBSCRIBER_SEGMENT_TABLE.'.subscriber_id');
}

View File

@ -1,7 +1,6 @@
<?php
namespace MailPoet\Models;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
@ -54,6 +53,15 @@ class SubscriberSegment extends Model {
return true;
}
static function resubscribeToAllSegments($subscriber) {
if($subscriber === false) return false;
// (re)subscribe to all segments linked to the subscriber
return self::where('subscriber_id', $subscriber->id)
->findResultSet()
->set('status', Subscriber::STATUS_SUBSCRIBED)
->save();
}
static function subscribeToSegments($subscriber, $segment_ids = array()) {
if($subscriber === false) return false;
if(!empty($segment_ids)) {
@ -68,12 +76,6 @@ class SubscriberSegment extends Model {
}
}
return true;
} else {
// (re)subscribe to all segments linked to the subscriber
return self::where('subscriber_id', $subscriber->id)
->findResultSet()
->set('status', Subscriber::STATUS_SUBSCRIBED)
->save();
}
}

View File

@ -2,7 +2,6 @@
namespace MailPoet\Newsletter;
use MailPoet\Newsletter\Editor\Transformer;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;

View File

@ -1,7 +1,7 @@
<?php
namespace MailPoet\Newsletter\Editor;
use \MailPoet\Newsletter\Editor\PostTransformer;
use MailPoet\Newsletter\Editor\PostTransformer;
if(!defined('ABSPATH')) exit;

View File

@ -1,9 +1,9 @@
<?php
namespace MailPoet\Newsletter\Editor;
use \MailPoet\Newsletter\Editor\PostContentManager;
use \MailPoet\Newsletter\Editor\MetaInformationManager;
use \MailPoet\Newsletter\Editor\StructureTransformer;
use MailPoet\Newsletter\Editor\PostContentManager;
use MailPoet\Newsletter\Editor\MetaInformationManager;
use MailPoet\Newsletter\Editor\StructureTransformer;
if(!defined('ABSPATH')) exit;

View File

@ -1,7 +1,7 @@
<?php
namespace MailPoet\Newsletter\Editor;
use \pQuery;
use pQuery;
if(!defined('ABSPATH')) exit;

View File

@ -1,8 +1,8 @@
<?php
namespace MailPoet\Newsletter\Editor;
use \MailPoet\Newsletter\Editor\TitleListTransformer;
use \MailPoet\Newsletter\Editor\PostListTransformer;
use MailPoet\Newsletter\Editor\TitleListTransformer;
use MailPoet\Newsletter\Editor\PostListTransformer;
if(!defined('ABSPATH')) exit;

View File

@ -27,8 +27,7 @@ class Url {
$preview = true;
break;
case self::TYPE_LISTING_EDITOR:
// enable preview and hide newsletter hash when displaying from editor or listings
$newsletter->hash = null;
// enable preview when displaying from editor or listings
$preview = true;
break;
default:

View File

@ -50,15 +50,24 @@ class ViewInBrowser {
if(empty($data->subscriber_token) ||
!Subscriber::verifyToken($data->subscriber->email, $data->subscriber_token)
) return false;
} else if(!$data->subscriber && !empty($data->preview)) {
// if this is a preview and subscriber does not exist,
// attempt to set subscriber to the current logged-in WP user
$data->subscriber = Subscriber::getCurrentWPUser();
}
// if newsletter ID is defined then subscriber must exist
if($data->newsletter_id && !$data->subscriber) return false;
// if newsletter hash is not provided but newsletter ID is defined then subscriber must exist
if(empty($data->newsletter_hash) && $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();
// queue is optional; try to find it if it's not defined and this is not a welcome email
if($data->newsletter->type !== Newsletter::TYPE_WELCOME) {
$data->queue = (!empty($data->queue_id)) ?
SendingQueue::findOne($data->queue_id) :
SendingQueue::where('newsletter_id', $data->newsletter->id)
->findOne();
} else {
$data->queue = false;
}
// allow users with 'manage_options' permission to preview any newsletter
if(!empty($data->preview) && current_user_can(Env::$required_permission)

View File

@ -31,11 +31,11 @@ class Router {
$endpoint_class = __NAMESPACE__ . "\\Endpoints\\" . ucfirst($this->endpoint);
if(!$this->api_request) return;
if(!$this->endpoint || !class_exists($endpoint_class)) {
return $this->terminateRequest(self::RESPONSE_ERROR, __('Invalid router endpoint.', 'mailpoet'));
return $this->terminateRequest(self::RESPONSE_ERROR, __('Invalid router endpoint', 'mailpoet'));
}
$endpoint = new $endpoint_class($this->data);
if(!method_exists($endpoint, $this->action) || !in_array($this->action, $endpoint->allowed_actions)) {
return $this->terminateRequest(self::RESPONSE_ERROR, __('Invalid router endpoint action.', 'mailpoet'));
return $this->terminateRequest(self::RESPONSE_ERROR, __('Invalid router endpoint action', 'mailpoet'));
}
do_action('mailpoet_conflict_resolver_router_url_query_parameters');
return call_user_func(
@ -74,4 +74,4 @@ class Router {
status_header($code, $message);
exit;
}
}
}

96
lib/Services/Bridge.php Normal file
View File

@ -0,0 +1,96 @@
<?php
namespace MailPoet\Services;
use MailPoet\Mailer\Mailer;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
if(!defined('ABSPATH')) exit;
class Bridge {
const API_KEY_STATE_SETTING_NAME = 'mta.mailpoet_api_key_state';
const MAILPOET_KEY_VALID = 'valid';
const MAILPOET_KEY_INVALID = 'invalid';
const MAILPOET_KEY_EXPIRING = 'expiring';
const MAILPOET_KEY_CHECK_ERROR = 'check_error';
const CHECK_ERROR_UNAVAILABLE = 503;
const CHECK_ERROR_UNKNOWN = 'unknown';
public $api;
static function isMPSendingServiceEnabled() {
try {
$mailer_config = Mailer::getMailerConfig();
return !empty($mailer_config['method'])
&& $mailer_config['method'] === Mailer::METHOD_MAILPOET;
} catch (\Exception $e) {
return false;
}
}
function initApi($api_key) {
if($this->api) {
$this->api->setKey($api_key);
} else {
$this->api = new Bridge\API($api_key);
}
}
function checkKey($api_key) {
$this->initApi($api_key);
$result = $this->api->checkKey();
return $this->processResult($result);
}
function processResult(array $result) {
$state_map = array(
200 => self::MAILPOET_KEY_VALID,
401 => self::MAILPOET_KEY_INVALID,
402 => self::MAILPOET_KEY_EXPIRING
);
$update_settings = false;
if(!empty($result['code']) && isset($state_map[$result['code']])) {
$key_state = $state_map[$result['code']];
$update_settings = true;
} else {
$key_state = self::MAILPOET_KEY_CHECK_ERROR;
}
$state = array(
'state' => $key_state,
'data' => !empty($result['data']) ? $result['data'] : null,
'code' => !empty($result['code']) ? $result['code'] : self::CHECK_ERROR_UNKNOWN
);
if($update_settings) {
Setting::setValue(
self::API_KEY_STATE_SETTING_NAME,
$state
);
}
return $state;
}
function updateSubscriberCount($result) {
if(!empty($result['state'])
&& ($result['state'] === self::MAILPOET_KEY_VALID
|| $result['state'] === self::MAILPOET_KEY_EXPIRING)
) {
return $this->api->updateSubscriberCount(Subscriber::getTotalSubscribers());
}
return null;
}
static function invalidateKey() {
Setting::setValue(
self::API_KEY_STATE_SETTING_NAME,
array('state' => self::MAILPOET_KEY_INVALID)
);
}
}

119
lib/Services/Bridge/API.php Normal file
View File

@ -0,0 +1,119 @@
<?php
namespace MailPoet\Services\Bridge;
if(!defined('ABSPATH')) exit;
class API {
const SENDING_STATUS_OK = 'ok';
const SENDING_STATUS_CONNECTION_ERROR = 'connection_error';
const SENDING_STATUS_SEND_ERROR = 'send_error';
const RESPONSE_CODE_KEY_INVALID = 401;
const RESPONSE_CODE_STATS_SAVED = 204;
private $api_key;
public $url_me = 'https://bridge.mailpoet.com/api/v0/me';
public $url_messages = 'https://bridge.mailpoet.com/api/v0/messages';
public $url_bounces = 'https://bridge.mailpoet.com/api/v0/bounces/search';
public $url_stats = 'https://bridge.mailpoet.com/api/v0/stats';
function __construct($api_key) {
$this->setKey($api_key);
}
function checkKey() {
$result = $this->request(
$this->url_me,
array('site' => home_url())
);
$code = wp_remote_retrieve_response_code($result);
switch($code) {
case 200:
case 402:
$body = json_decode(wp_remote_retrieve_body($result), true);
break;
case 401:
$body = wp_remote_retrieve_body($result);
break;
default:
$body = null;
break;
}
return array('code' => $code, 'data' => $body);
}
function sendMessages($message_body) {
$result = $this->request(
$this->url_messages,
$message_body
);
if(is_wp_error($result)) {
return array(
'status' => self::SENDING_STATUS_CONNECTION_ERROR,
'message' => $result->get_error_message()
);
}
$response_code = wp_remote_retrieve_response_code($result);
if($response_code !== 201) {
$response = (wp_remote_retrieve_body($result)) ?
wp_remote_retrieve_body($result) :
wp_remote_retrieve_response_message($result);
return array(
'status' => self::SENDING_STATUS_SEND_ERROR,
'message' => $response,
'code' => $response_code
);
}
return array('status' => self::SENDING_STATUS_OK);
}
function checkBounces(array $emails) {
$result = $this->request(
$this->url_bounces,
$emails
);
if(wp_remote_retrieve_response_code($result) === 200) {
return json_decode(wp_remote_retrieve_body($result), true);
}
return false;
}
function updateSubscriberCount($count) {
$result = $this->request(
$this->url_stats,
array('subscriber_count' => (int)$count),
'PUT'
);
return wp_remote_retrieve_response_code($result) === self::RESPONSE_CODE_STATS_SAVED;
}
function setKey($api_key) {
$this->api_key = $api_key;
}
function getKey() {
return $this->api_key;
}
private function auth() {
return 'Basic ' . base64_encode('api:' . $this->api_key);
}
private function request($url, $body, $method = 'POST') {
$params = array(
'timeout' => 10,
'httpversion' => '1.0',
'method' => $method,
'headers' => array(
'Content-Type' => 'application/json',
'Authorization' => $this->auth()
),
'body' => json_encode($body)
);
return wp_remote_post($url, $params);
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace MailPoet\Settings;
use \MailPoet\Subscription;
use MailPoet\Subscription;
class Pages {
function __construct() {

View File

@ -47,10 +47,10 @@ class Export {
function process() {
try {
if(is_writable($this->export_path) === false) {
throw new \Exception(__("Couldn't save export file on the server", 'mailpoet'));
throw new \Exception(__("The export file could not be saved on the server.", 'mailpoet'));
}
if(!extension_loaded('zip')) {
throw new \Exception(__('Export requires a ZIP extension to be installed on the host', 'mailpoet'));
throw new \Exception(__('Export requires a ZIP extension to be installed on the host.', 'mailpoet'));
}
$processed_subscribers = call_user_func(
array(

View File

@ -1,7 +1,7 @@
<?php
namespace MailPoet\Subscription;
use \MailPoet\Models\Setting;
use \MailPoet\Models\Subscriber;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
class Comment {
const SPAM = 'spam';
@ -20,7 +20,7 @@ class Comment {
static function getSubscriptionField() {
$label = Setting::getValue(
'subscribe.on_comment.label',
__('Yes, please add me to your mailing list', 'mailpoet')
__('Yes, please add me to your mailing list.', 'mailpoet')
);
return '<p class="comment-form-mailpoet">
@ -91,4 +91,4 @@ class Comment {
);
}
}
}
}

View File

@ -1,11 +1,8 @@
<?php
namespace MailPoet\Subscription;
use \MailPoet\API\Endpoints\Subscribers;
use \MailPoet\API\Response as APIResponse;
use \MailPoet\Models\Subscriber;
use \MailPoet\Models\StatisticsForms;
use \MailPoet\Models\Form as FormModel;
use \MailPoet\Util\Url;
use MailPoet\API\Endpoints\Subscribers;
use MailPoet\API\Response as APIResponse;
use MailPoet\Util\Url;
class Form {
static function onSubmit() {

View File

@ -1,7 +1,7 @@
<?php
namespace MailPoet\Subscription;
use \MailPoet\Models\Subscriber;
use \MailPoet\Util\Url;
use MailPoet\Models\Subscriber;
use MailPoet\Util\Url;
class Manage {

View File

@ -1,16 +1,15 @@
<?php
namespace MailPoet\Subscription;
use \MailPoet\Models\Subscriber;
use \MailPoet\Models\SubscriberSegment;
use \MailPoet\Models\CustomField;
use \MailPoet\Models\Setting;
use \MailPoet\Models\Segment;
use \MailPoet\Util\Helpers;
use \MailPoet\Util\Url;
use \MailPoet\Subscription;
use \MailPoet\Form\Renderer as FormRenderer;
use \MailPoet\Form\Block\Date as FormBlockDate;
use MailPoet\Models\Subscriber;
use MailPoet\Models\SubscriberSegment;
use MailPoet\Models\CustomField;
use MailPoet\Models\Setting;
use MailPoet\Models\Segment;
use MailPoet\Util\Url;
use MailPoet\Subscription;
use MailPoet\Form\Renderer as FormRenderer;
use MailPoet\Form\Block\Date as FormBlockDate;
class Pages {
const DEMO_EMAIL = 'demo@mailpoet.com';
@ -88,7 +87,7 @@ class Pages {
global $post;
if($this->isPreview() === false && $this->subscriber === false) {
return __("Hmmm... we don't have a record of you", 'mailpoet');
return __("Hmmm... we don't have a record of you.", 'mailpoet');
}
if(
@ -190,7 +189,7 @@ class Pages {
private function getUnsubscribeTitle() {
if($this->isPreview() || $this->subscriber !== false) {
return __("You are now unsubscribed", 'mailpoet');
return __("You are now unsubscribed.", 'mailpoet');
}
}

View File

@ -1,7 +1,7 @@
<?php
namespace MailPoet\Subscription;
use \MailPoet\Models\Setting;
use \MailPoet\Models\Subscriber;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
class Registration {

View File

@ -24,6 +24,7 @@ class I18n extends \Twig_Extension {
'localize' => 'localize',
'__' => 'translate',
'_n' => 'pluralize',
'_x' => 'translateWithContext',
'date' => 'date'
);
@ -63,6 +64,12 @@ class I18n extends \Twig_Extension {
return call_user_func_array('_n', $this->setTextDomain($args));
}
function translateWithContext() {
$args = func_get_args();
return call_user_func_array('_x', $this->setTextDomain($args));
}
function date() {
$args = func_get_args();
$date = (isset($args[0])) ? $args[0] : null;
@ -88,4 +95,4 @@ class I18n extends \Twig_Extension {
}
return $args;
}
}
}

View File

@ -2,25 +2,23 @@
namespace MailPoet\Util;
class ConflictResolver {
public $allowed_assets = array(
public $permitted_assets_locations = array(
'styles' => array(
// WP default
'admin-bar',
'colors',
'ie',
'wp-auth-check',
'^/wp-admin',
'^/wp-includes',
// third-party
'query-monitor'
'query-monitor',
'wpt-tx-updater-network'
),
'scripts' => array(
// WP default
'common',
'admin-bar',
'utils',
'svg-painter',
'wp-auth-check',
'^/wp-admin',
'^/wp-includes',
'googleapis.com/ajax/libs',
// third-party
'query-monitor'
'query-monitor',
'wpt-tx-updater-network'
)
);
@ -54,32 +52,39 @@ class ConflictResolver {
}
function resolveStylesConflict() {
$_this = $this;
// unload all styles except from the list of allowed
$dequeue_styles = function() {
$dequeue_styles = function() use($_this) {
global $wp_styles;
if(empty($wp_styles->queue)) return;
foreach($wp_styles->queue as $wp_style) {
if(!in_array($wp_style, $this->allowed_assets['styles'])) {
if(empty($wp_styles->registered[$wp_style])) continue;
$registered_style = $wp_styles->registered[$wp_style];
if(!preg_match('!' . implode('|', $_this->permitted_assets_locations['styles']) . '!i', $registered_style->src)) {
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);
add_action('wp_print_styles', $dequeue_styles, PHP_INT_MAX);
add_action('admin_print_styles', $dequeue_styles, PHP_INT_MAX);
add_action('admin_print_footer_scripts', $dequeue_styles, PHP_INT_MAX);
add_action('admin_footer', $dequeue_styles, PHP_INT_MAX);
}
function resolveScriptsConflict() {
$_this = $this;
// unload all scripts except from the list of allowed
$dequeue_scripts = function() {
$dequeue_scripts = function() use($_this) {
global $wp_scripts;
foreach($wp_scripts->queue as $wp_script) {
if(!in_array($wp_script, $this->allowed_assets['scripts'])) {
if(empty($wp_scripts->registered[$wp_script])) continue;
$registered_script = $wp_scripts->registered[$wp_script];
if(!preg_match('!' . implode('|', $_this->permitted_assets_locations['scripts']) . '!i', $registered_script->src)) {
wp_dequeue_script($wp_script);
}
}
};
add_action('wp_print_scripts', $dequeue_scripts);
add_action('admin_print_footer_scripts', $dequeue_scripts);
add_action('wp_print_scripts', $dequeue_scripts, PHP_INT_MAX);
add_action('admin_print_footer_scripts', $dequeue_scripts, PHP_INT_MAX);
}
}
}

View File

@ -43,7 +43,7 @@ class Notice {
}
function displayWPNotice() {
$class = sprintf('notice notice-%s', $this->type);
$class = sprintf('notice notice-%s mailpoet_notice_server', $this->type);
$message = nl2br($this->message);
printf('<div class="%1$s"><p>%2$s</p></div>', $class, $message);

View File

@ -1,40 +1,72 @@
<?php
if(!defined('ABSPATH')) exit;
use MailPoet\Config\Initializer;
if(!defined('ABSPATH')) exit;
/*
* Plugin Name: MailPoet
* Version: 3.0.0-beta.13
* Version: 3.0.0-beta.17
* 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.6
* Tested up to: 4.7
* Tested up to: 4.7.2
*
* Text Domain: mailpoet
* Domain Path: /lang/
*
* @package WordPress
* @author MailPoet
* @since 0.0.8
* @since 3.0.0-beta.1
*/
$mailpoet_loader = dirname(__FILE__) . '/vendor/autoload.php';
if(file_exists($mailpoet_loader)) {
require $mailpoet_loader;
define('MAILPOET_VERSION', '3.0.0-beta.13');
$initializer = new Initializer(
array(
'file' => __FILE__,
'version' => MAILPOET_VERSION
)
);
$initializer->init();
} else {
add_action('admin_notices', function() {
$notice = __('MailPoet cannot start because it is missing core files. Please reinstall the plugin.', 'mailpoet');
printf('<div class="error"><p>%1$s</p></div>', $notice);
});
$mailpoet_plugin = array(
'version' => '3.0.0-beta.17',
'filename' => __FILE__,
'path' => dirname(__FILE__),
'autoloader' => dirname(__FILE__) . '/vendor/autoload.php',
'initializer' => dirname(__FILE__) . '/mailpoet_initializer.php'
);
function mailpoet_deactivate_plugin() {
deactivate_plugins(plugin_basename(__FILE__));
if(!empty($_GET['activate'])) {
unset($_GET['activate']);
}
}
// Check for minimum supported PHP version
if(version_compare(phpversion(), '5.3.0', '<')) {
add_action('admin_notices', 'mailpoet_php_version_notice');
// deactivate the plugin
add_action('admin_init', 'mailpoet_deactivate_plugin');
return;
}
// Display PHP version error notice
function mailpoet_php_version_notice() {
$notice = str_replace(
'[link]',
'<a href="//beta.docs.mailpoet.com/article/152-minimum-requirements-for-mailpoet-3#php_version" target="_blank">',
__('MailPoet plugin requires PHP version 5.3 or newer. Please read our [link]instructions[/link] on how to resolve this issue.', 'mailpoet')
);
$notice = str_replace('[/link]', '</a>', $notice);
printf('<div class="error"><p>%1$s</p></div>', $notice);
}
// Check for presence of core dependencies
if(!file_exists($mailpoet_plugin['autoloader']) || !file_exists($mailpoet_plugin['initializer'])) {
add_action('admin_notices', 'mailpoet_core_dependency_notice');
// deactivate the plugin
add_action('admin_init', 'mailpoet_deactivate_plugin');
return;
}
// Display missing core dependencies error notice
function mailpoet_core_dependency_notice() {
$notice = __('MailPoet cannot start because it is missing core files. Please reinstall the plugin.', 'mailpoet');
printf('<div class="error"><p>%1$s</p></div>', $notice);
}
// Initialize plugin
require_once($mailpoet_plugin['initializer']);

17
mailpoet_initializer.php Normal file
View File

@ -0,0 +1,17 @@
<?php
use MailPoet\Config\Initializer;
if(!defined('ABSPATH') || empty($mailpoet_plugin)) exit;
require_once($mailpoet_plugin['autoloader']);
define('MAILPOET_VERSION', $mailpoet_plugin['version']);
$initializer = new Initializer(
array(
'file' => $mailpoet_plugin['filename'],
'version' => $mailpoet_plugin['version']
)
);
$initializer->init();

View File

@ -1,9 +1,9 @@
=== MailPoet 3 - Beta Version ===
Contributors: mailpoet, wysija
Tags: newsletter, email, welcome email, post notification, autoresponder, mailchimp, signup, smtp
Tags: newsletter, email, welcome email, post notification, autoresponder, signup, smtp
Requires at least: 4.6
Tested up to: 4.7
Stable tag: 3.0.0-beta.13
Tested up to: 4.7.2
Stable tag: 3.0.0-beta.17
Create and send beautiful emails and newsletters from WordPress.
== Description ==
@ -83,6 +83,27 @@ Our [support site](https://docs.mailpoet.com/) has plenty of articles. You can w
== Changelog ==
= 3.0.0-beta.17 - 2017-02-01 =
* Added: send in style with MailPoet's own sending service. Visit your MailPoet Settings > Send with... tab.
= 3.0.0-beta.16 - 2017-01-31 =
* Improved: Updated language strings for better translation support;
* Fixed: subscription forms now allow to subscribe only to specified lists. Thanks Paul!
* Fixed: subscription forms now ignore any extra fields added not via the Form editor. Thx again Paul!
* Fixed: previewing sent welcome emails now displays latest email version. Thanks Tim!
* Fixed: plugin no longer triggers a PHP error during initialization on hosts using PHP 5.3;
* Fixed: plugin warns about missing required PDO_MYSQL extension.
= 3.0.0-beta.15 - 2017-01-24 =
* Fixed: plugin no longer throws a fatal exception error on (prehistoric :)) hosts running older versions than PHP 5.3. Thanks Otto & jtm12!;
* Fixed: users who are not subscribed to any list can be filtered in the admin panel;
* Fixed: newsletter preview links can now be shared with non WP users.
= 3.0.0-beta.14 - 2017-01-19 =
* Fixed: images can't be added to newsletters. Thanks Leon!;
* Fixed: forms require first & last name input fields on some systems;
* Fixed: unable to remove subscribers from lists in admin panel. Thanks Kay!
= 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!;

View File

@ -260,6 +260,7 @@ class StringExtractor {
$function_patterns = array(
'/(__)\((([\'"]).+?\3)\)/',
'/(_x)\(([\'"].+?[\'"],\s*[\'"].+?[\'"])\)/',
'/(_n)\(([\'"].+?[\'"],\s*[\'"].+?[\'"],\s*.+?)\)/'
);
@ -287,7 +288,8 @@ class StringExtractor {
$arguments = array();
foreach($arguments_matches[0] as $argument) {
$arguments[] = trim($argument, "'\"");
// Remove surrounding quotes of the same type from argument strings
$arguments[] = preg_replace("/^(('|\")+)(.*)\\1$/", "\\3", $argument);
}
$call = array(

View File

@ -1,5 +1,5 @@
<?php
use \MailPoet\API\API;
use MailPoet\API\API;
// required to be able to use wp_delete_user()
require_once(ABSPATH.'wp-admin/includes/user.php');

View File

@ -1,8 +1,8 @@
<?php
use \MailPoet\API\Endpoints\CustomFields;
use \MailPoet\API\Response as APIResponse;
use \MailPoet\Models\CustomField;
use MailPoet\API\Endpoints\CustomFields;
use MailPoet\API\Response as APIResponse;
use MailPoet\Models\CustomField;
class CustomFieldsTest extends MailPoetTest {
private $custom_fields = array(
@ -97,18 +97,18 @@ class CustomFieldsTest extends MailPoetTest {
// missing type
$response = $router->save(array('name' => 'New custom field'));
expect($response->status)->equals(APIResponse::STATUS_BAD_REQUEST);
expect($response->errors[0]['message'])->equals('Please specify a type');
expect($response->errors[0]['message'])->equals('Please specify a type.');
// missing name
$response = $router->save(array('type' => 'text'));
expect($response->status)->equals(APIResponse::STATUS_BAD_REQUEST);
expect($response->errors[0]['message'])->equals('Please specify a name');
expect($response->errors[0]['message'])->equals('Please specify a name.');
// missing data
$response = $router->save();
expect($response->status)->equals(APIResponse::STATUS_BAD_REQUEST);
expect($response->errors[0]['message'])->equals('Please specify a name');
expect($response->errors[1]['message'])->equals('Please specify a type');
expect($response->errors[0]['message'])->equals('Please specify a name.');
expect($response->errors[1]['message'])->equals('Please specify a type.');
}
function testItCanGetACustomField() {

View File

@ -1,8 +1,8 @@
<?php
use \MailPoet\API\Endpoints\Forms;
use \MailPoet\API\Response as APIResponse;
use \MailPoet\Models\Form;
use \MailPoet\Models\Segment;
use MailPoet\API\Endpoints\Forms;
use MailPoet\API\Response as APIResponse;
use MailPoet\Models\Form;
use MailPoet\Models\Segment;
class FormsTest extends MailPoetTest {
function _before() {
@ -65,7 +65,7 @@ class FormsTest extends MailPoetTest {
$router = new Forms();
$response = $router->save(/* missing data */);
expect($response->status)->equals(APIResponse::STATUS_BAD_REQUEST);
expect($response->errors[0]['message'])->equals('Please specify a name');
expect($response->errors[0]['message'])->equals('Please specify a name.');
$response = $router->save($form_data);
expect($response->status)->equals(APIResponse::STATUS_OK);

View File

@ -1,8 +1,7 @@
<?php
use \MailPoet\API\Response as APIResponse;
use \MailPoet\API\Error as APIError;
use \MailPoet\API\Endpoints\NewsletterTemplates;
use \MailPoet\Models\NewsletterTemplate;
use MailPoet\API\Response as APIResponse;
use MailPoet\API\Endpoints\NewsletterTemplates;
use MailPoet\Models\NewsletterTemplate;
class NewsletterTemplatesTest extends MailPoetTest {
function _before() {

View File

@ -8,6 +8,8 @@ use MailPoet\Models\NewsletterOptionField;
use MailPoet\Models\NewsletterSegment;
use MailPoet\Models\Segment;
use MailPoet\Newsletter\Scheduler\Scheduler;
use MailPoet\Newsletter\Url;
use MailPoet\Router\Router;
class NewslettersTest extends MailPoetTest {
function _before() {
@ -29,7 +31,7 @@ class NewslettersTest extends MailPoetTest {
function testItCanGetANewsletter() {
$router = new Newsletters();
$response = $router->get(/* missing id */);
$response = $router->get(); // missing id
expect($response->status)->equals(APIResponse::STATUS_NOT_FOUND);
expect($response->errors[0]['message'])
->equals('This newsletter does not exist.');
@ -78,7 +80,7 @@ class NewslettersTest extends MailPoetTest {
$response = $router->save($invalid_data);
expect($response->status)->equals(APIResponse::STATUS_BAD_REQUEST);
expect($response->errors[0]['message'])->equals('Please specify a type');
expect($response->errors[0]['message'])->equals('Please specify a type.');
}
function testItCanSaveAnExistingNewsletter() {
@ -282,7 +284,7 @@ class NewslettersTest extends MailPoetTest {
$response = $router->create();
expect($response->status)->equals(APIResponse::STATUS_BAD_REQUEST);
expect($response->errors[0]['message'])->equals('Please specify a type');
expect($response->errors[0]['message'])->equals('Please specify a type.');
}
function testItCanGetListingData() {
@ -517,7 +519,7 @@ class NewslettersTest extends MailPoetTest {
expect($response->status)->equals(APIResponse::STATUS_OK);
}
function testItReturnsMaillerErrorWhenSendingFailed() {
function testItReturnsMailerErrorWhenSendingFailed() {
$subscriber = 'test@subscriber.com';
$data = array(
'subscriber' => $subscriber,
@ -542,10 +544,22 @@ class NewslettersTest extends MailPoetTest {
expect($response->errors[0]['message'])->equals('The email could not be sent: failed');
}
function testItGeneratesPreviewLinksWithNewsletterHashAndNoSubscriberData() {
$router = new Newsletters();
$response = $router->listing();
$preview_link = $response->data[0]['preview_url'];
parse_str(parse_url($preview_link, PHP_URL_QUERY), $preview_link_data);
$preview_link_data = Url::transformUrlDataObject(Router::decodeRequestData($preview_link_data['data']));
expect($preview_link_data['newsletter_hash'])->notEmpty();
expect($preview_link_data['subscriber_id'])->false();
expect($preview_link_data['subscriber_token'])->false();
expect((boolean)$preview_link_data['preview'])->true();
}
function _after() {
Newsletter::deleteMany();
NewsletterSegment::deleteMany();
NewsletterOptionField::deleteMany();
Segment::deleteMany();
}
}
}

View File

@ -1,7 +1,7 @@
<?php
use \MailPoet\API\Endpoints\Segments;
use \MailPoet\API\Response as APIResponse;
use \MailPoet\Models\Segment;
use MailPoet\API\Endpoints\Segments;
use MailPoet\API\Response as APIResponse;
use MailPoet\Models\Segment;
class SegmentsTest extends MailPoetTest {
function _before() {
@ -52,7 +52,7 @@ class SegmentsTest extends MailPoetTest {
$router = new Segments();
$response = $router->save(/* missing data */);
expect($response->status)->equals(APIResponse::STATUS_BAD_REQUEST);
expect($response->errors[0]['message'])->equals('Please specify a name');
expect($response->errors[0]['message'])->equals('Please specify a name.');
$response = $router->save($segment_data);
expect($response->status)->equals(APIResponse::STATUS_OK);

View File

@ -0,0 +1,65 @@
<?php
use Codeception\Util\Stub;
use MailPoet\API\Endpoints\Services;
use MailPoet\API\Response as APIResponse;
use MailPoet\Services\Bridge;
class ServicesTest extends MailPoetTest {
function _before() {
$this->services_endpoint = new Services();
$this->data = array('key' => '1234567890abcdef');
}
function testItRespondsWithErrorIfNoKeyIsGiven() {
$response = $this->services_endpoint->verifyMailPoetKey(array('key' => ''));
expect($response->status)->equals(APIResponse::STATUS_BAD_REQUEST);
expect($response->errors[0]['message'])->equals('Please specify a key.');
}
function testItRespondsWithSuccessIfKeyIsValid() {
$this->services_endpoint->bridge = Stub::make(
new Bridge(),
array('checkKey' => array('state' => Bridge::MAILPOET_KEY_VALID)),
$this
);
$response = $this->services_endpoint->verifyMailPoetKey($this->data);
expect($response->status)->equals(APIResponse::STATUS_OK);
}
function testItRespondsWithErrorIfKeyIsInvalid() {
$this->services_endpoint->bridge = Stub::make(
new Bridge(),
array('checkKey' => array('state' => Bridge::MAILPOET_KEY_INVALID)),
$this
);
$response = $this->services_endpoint->verifyMailPoetKey($this->data);
expect($response->status)->equals(APIResponse::STATUS_NOT_FOUND);
}
function testItRespondsWithErrorIfKeyIsExpiring() {
$date = new DateTime;
$this->services_endpoint->bridge = Stub::make(
new Bridge(),
array('checkKey' => array(
'state' => Bridge::MAILPOET_KEY_EXPIRING,
'data' => array('expire_at' => $date->format('c'))
)),
$this
);
$response = $this->services_endpoint->verifyMailPoetKey($this->data);
expect($response->status)->equals(APIResponse::STATUS_OK);
expect($response->data['message'])->contains($date->format('Y-m-d'));
}
function testItRespondsWithErrorIfServiceIsUnavailable() {
$this->services_endpoint->bridge = Stub::make(
new Bridge(),
array('checkKey' => array('code' => Bridge::CHECK_ERROR_UNAVAILABLE)),
$this
);
$response = $this->services_endpoint->verifyMailPoetKey($this->data);
expect($response->status)->equals(APIResponse::STATUS_NOT_FOUND);
expect($response->errors[0]['message'])->contains((string)Bridge::CHECK_ERROR_UNAVAILABLE);
}
}

View File

@ -1,8 +1,8 @@
<?php
use \MailPoet\API\Response as APIResponse;
use \MailPoet\API\Error as APIError;
use \MailPoet\API\Endpoints\Settings;
use \MailPoet\Models\Setting;
use MailPoet\API\Response as APIResponse;
use MailPoet\API\Error as APIError;
use MailPoet\API\Endpoints\Settings;
use MailPoet\Models\Setting;
class SettingsTest extends MailPoetTest {
function _before() {

View File

@ -1,7 +1,7 @@
<?php
use \MailPoet\API\Response as APIResponse;
use \MailPoet\API\Endpoints\Setup;
use \MailPoet\Models\Setting;
use MailPoet\API\Response as APIResponse;
use MailPoet\API\Endpoints\Setup;
use MailPoet\Models\Setting;
class SetupTest extends MailPoetTest {
function _before() {

View File

@ -1,10 +1,12 @@
<?php
use \MailPoet\API\Endpoints\Subscribers;
use \MailPoet\API\Response as APIResponse;
use \MailPoet\Models\Subscriber;
use \MailPoet\Models\Segment;
use \MailPoet\Models\Setting;
use Codeception\Util\Fixtures;
use MailPoet\API\Endpoints\Subscribers;
use MailPoet\API\Response as APIResponse;
use MailPoet\Models\Form;
use MailPoet\Models\Subscriber;
use MailPoet\Models\Segment;
use MailPoet\Models\Setting;
class SubscribersTest extends MailPoetTest {
function _before() {
@ -28,6 +30,18 @@ class SubscribersTest extends MailPoetTest {
)
));
$this->form = Form::createOrUpdate(array(
'name' => 'My Form',
'body' => Fixtures::get('form_body_template'),
'settings' => array(
'segments_selected_by' => 'user',
'segments' => array(
$this->segment_1->id,
$this->segment_2->id
)
)
));
// setup mailer
Setting::setValue('sender', array(
'address' => 'sender@mailpoet.com',
@ -116,6 +130,20 @@ class SubscribersTest extends MailPoetTest {
expect($response->data['first_name'])->equals('Super Jane');
}
function testItCanRemoveListsFromAnExistingSubscriber() {
$router = new Subscribers();
$subscriber_data = $this->subscriber_2->asArray();
unset($subscriber_data['created_at']);
unset($subscriber_data['segments']);
$response = $router->save($subscriber_data);
expect($response->status)->equals(APIResponse::STATUS_OK);
expect($response->data)->equals(
Subscriber::findOne($this->subscriber_2->id)->asArray()
);
expect($this->subscriber_2->segments()->findArray())->count(0);
}
function testItCanRestoreASubscriber() {
$this->subscriber_1->trash();
@ -361,10 +389,22 @@ class SubscribersTest extends MailPoetTest {
expect($response->errors[0]['message'])->contains('has no method');
}
function testItCannotSubscribeWithoutSegments() {
function testItCannotSubscribeWithoutFormID() {
$router = new Subscribers();
$response = $router->subscribe(array(
'email' => 'toto@mailpoet.com'
// no form ID specified
));
expect($response->status)->equals(APIResponse::STATUS_BAD_REQUEST);
expect($response->errors[0]['message'])->equals('Please specify a valid form ID.');
}
function testItCannotSubscribeWithoutSegmentsIfTheyAreSelectedByUser() {
$router = new Subscribers();
$response = $router->subscribe(array(
'email' => 'toto@mailpoet.com',
'form_id' => $this->form->id
// no segments specified
));
@ -376,23 +416,84 @@ class SubscribersTest extends MailPoetTest {
$router = new Subscribers();
$response = $router->subscribe(array(
'email' => 'toto@mailpoet.com',
'form_id' => $this->form->id,
'segments' => array($this->segment_1->id, $this->segment_2->id)
));
expect($response->status)->equals(APIResponse::STATUS_OK);
}
function testItCanSubscribeWithoutSegmentsIfTheyAreSelectedByAdmin() {
$form = $this->form->asArray();
$form['settings']['segments_selected_by'] = 'admin';
$this->form->settings = $form['settings'];
$this->form->save();
$router = new Subscribers();
$response = $router->subscribe(array(
'email' => 'toto@mailpoet.com',
'form_id' => $this->form->id
// no segments specified
));
expect($response->status)->equals(APIResponse::STATUS_OK);
$subscriber = Subscriber::findOne($response->data['id']);
$subscriber_segments = $subscriber->segments()->findArray();
expect($subscriber_segments)->count(2);
expect($subscriber_segments[0]['id'])->equals($form['settings']['segments'][0]);
expect($subscriber_segments[1]['id'])->equals($form['settings']['segments'][1]);
}
function testItCannotSubscribeIfFormHasNoSegmentsDefined() {
$form = $this->form->asArray();
$form['settings']['segments_selected_by'] = 'admin';
unset($form['settings']['segments']);
$this->form->settings = $form['settings'];
$this->form->save();
$router = new Subscribers();
$response = $router->subscribe(array(
'email' => 'toto@mailpoet.com',
'form_id' => $this->form->id,
'segments' => array($this->segment_1->id, $this->segment_2->id)
));
expect($response->status)->equals(APIResponse::STATUS_BAD_REQUEST);
expect($response->errors[0]['message'])->equals('Please select a list.');
}
function testItCanFilterOutNonFormFieldsWhenSubscribing() {
$router = new Subscribers();
$response = $router->subscribe(array(
'email' => 'toto@mailpoet.com',
'form_id' => $this->form->id,
'segments' => array($this->segment_1->id, $this->segment_2->id),
// exists in table and in the form
'first_name' => 'aaa',
// exists in table, but not in the form
'last_name' => 'bbb',
// doesn't exist
'bogus' => 'hahaha'
));
expect($response->status)->equals(APIResponse::STATUS_OK);
expect($response->data['first_name'])->equals('aaa');
expect($response->data['last_name'])->isEmpty();
expect(isset($response->data['bogus']))->false();
}
function testItCannotMassSubscribe() {
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
$router = new Subscribers();
$response = $router->subscribe(array(
'email' => 'toto@mailpoet.com',
'form_id' => $this->form->id,
'segments' => array($this->segment_1->id, $this->segment_2->id)
));
try {
$response = $router->subscribe(array(
'email' => 'tata@mailpoet.com',
'form_id' => $this->form->id,
'segments' => array($this->segment_1->id, $this->segment_2->id)
));
$this->fail('It should not be possible to subscribe a second time so soon');

Some files were not shown because too many files have changed in this diff Show More