Compare commits

...

215 Commits

Author SHA1 Message Date
95e66f1f29 Bump up release version to 3.0.0-beta.23.1 2017-03-14 16:13:07 +02:00
ac0460ab04 Merge pull request #847 from mailpoet/freeze_dependencies
Freeze PHP and JS dependencies to specific versions [MAILPOET-858]
2017-03-14 16:58:23 +03:00
2d059debb7 Freeze PHP and JS dependencies to specific versions 2017-03-14 15:11:35 +02:00
df0ad2df37 Update release version to 3.0.0-beta.23 2017-03-14 13:18:21 +02:00
0f7725e6af Merge pull request #842 from mailpoet/verify_subscription_status_during_sending
Prevents sending to unsubscribed subscribers [MAILPOET-824]
2017-03-14 10:49:43 +03:00
eda346c582 Fix a unit test for PHP 7.1 [MAILPOET-824] 2017-03-14 10:31:44 +03:00
94060a6443 Merge pull request #846 from mailpoet/transifex_import
Add importing of translations from Transifex [MAILPOET-849]
2017-03-13 18:45:10 +02:00
1cd7c5e876 Update README, force translations downloading, set shell script permissions [MAILPOET-849] 2017-03-13 19:34:17 +03:00
b369cadde0 Merge pull request #841 from mailpoet/post_notification_reschedule
Reschedules sending queues when scheduling options change [MAILPOET-837]
2017-03-13 17:21:03 +02:00
5321a136e7 Add importing of translations from Transifex [MAILPOET-849] 2017-03-13 17:43:56 +03:00
d4c04f29bf Merge pull request #845 from mailpoet/readme_update
Update translations section of readme.txt [MAILPOET-854]
2017-03-13 14:56:47 +02:00
20798d8957 Update translations section of readme.txt [MAILPOET-854] 2017-03-13 12:55:52 +03:00
3cde437628 Adds enforcement of global subcriber subscription status and
subscribption to segments to which newsletter is sent
2017-03-09 20:38:34 -05:00
8db7af48cd Merge pull request #843 from mailpoet/http_build_query_fix
Forces ampersand as query separator for mailers [MAILPOET-850]
2017-03-09 13:40:08 +03:00
d05d033727 Forces ampersand as query separator for mailers 2017-03-08 14:34:35 -05:00
ccba1925b1 Prevents sending to unsubscribed subscribers 2017-03-07 18:55:49 -05:00
b590586d4c Reschedules previously scheduled sending queues when post notification's
scheduling options change
2017-03-07 18:39:55 -05:00
44c742402c Bump up release verison to 3.0.0-beta.22 2017-03-07 17:49:11 +03:00
3a9db95c37 Merge pull request #840 from mailpoet/sending_batch_size_update
Reduces sending batch size [MAILPOET-847]
2017-03-07 16:53:52 +03:00
5d88938d94 Reduces sending batch size 2017-03-06 19:05:14 -05:00
67e0f1776d Merge pull request #839 from mailpoet/mailer_endpoint_update
Prevents setting current user's name to recipient of test email [MAILPOET-846]
2017-03-06 10:38:34 +03:00
5b68febb05 Removes first/last name that's based on current user from being sent to
test e-mail
2017-03-02 17:28:44 -05:00
9bf65ca798 Merge pull request #838 from mailpoet/editor_notices
Fix notices on newsletter editor page to not be absorbed into contents [MAILPOET-811]
2017-03-01 20:38:58 +03:00
d95aa40502 Fix notices on newsletter editor page to not be absorbed into contents 2017-03-01 17:26:17 +02:00
a59bf76fb4 Update version information for 3.0.0-beta.21 release 2017-02-28 14:11:03 +02:00
51fdc7f1df Merge pull request #837 from mailpoet/catch_cron_exceptions
Catches exception thrown by cron dependency and prevents a fatal error [MAILPOET-844]
2017-02-28 13:20:58 +02:00
aa51b751d0 Merge pull request #835 from mailpoet/translations
Fix translations [MAILPOET-841]
2017-02-28 14:08:44 +03:00
aff522c5cd Merge pull request #836 from mailpoet/translation_string_update
Removes unnecessary period [MAILPOET-842]
2017-02-28 12:40:06 +02:00
66d039ace3 Catches exception thrown by cron dependency and prevents a fatal error 2017-02-27 19:21:32 -05:00
ed5e6cdd8c Removes unnecessary period 2017-02-27 12:58:54 -05:00
47f5e1e7b4 Translate color picker actions in newsletter editor 2017-02-27 19:32:08 +02:00
626d6c0fa9 Fix "newsletter was updated successfully" translation 2017-02-27 19:15:29 +02:00
2b45d64695 Make numberOfItems translation singular/plural aware 2017-02-27 18:33:09 +02:00
c27446666e Translate untranslated strings 2017-02-27 18:33:09 +02:00
7f0195378c Merge pull request #833 from mailpoet/stop_daemon_with_sending_is_paused
Stops cron daemon when sending is paused due to an error [MAILPOET-839]
2017-02-27 16:49:55 +03:00
6caa3a069b Merge pull request #832 from mailpoet/mailer_output_escape_fix
Fixes double escaping of HTML entities in mailer output [MAILPOET-836]
2017-02-27 10:52:10 +03:00
baaf73b584 Merge pull request #834 from mailpoet/mailpoet_mailer_php53_fix
Fixes 'Using $this when not in object context' MailPoet mailer error on PHP 5.3 [MAILPOET-840]
2017-02-27 10:19:21 +03:00
b2a92feb50 Restore unnecessary $this replacements [MAILPOET-840] 2017-02-27 10:18:14 +03:00
c11b9677d5 Fixes 'Using $this when not in object context' error on PHP 5.3 2017-02-26 22:08:34 -05:00
0e5a26ce1f Stops cron daemon when sending is paused due to an error 2017-02-26 11:26:59 -05:00
40ec5569d0 Removes "unprocessed subscriber" message since MailPoet's mailer method
processes subscribers in batches of 50 and it makes no sense to display
them all
2017-02-25 10:32:14 -05:00
d14ecc982b Does not display unprocessed subscriber when sending a test message 2017-02-24 11:18:27 -05:00
9c27384ba3 Passes responsibility for mailer error message HTML entity escaping from
server to the client side
2017-02-24 11:06:20 -05:00
2268f0ffa6 Merge pull request #831 from mailpoet/php53_bulk_trash_and_restore_fix
Fix bulk trashing/restoring not working for newsletters/forms on PHP 5.3 [MAILPOET-835]
2017-02-24 13:22:30 +01:00
bfc04bfa87 Fix bulk trashing/restoring not working for newsletters/forms on PHP 5.3 [MAILPOET-835] 2017-02-24 15:10:38 +03:00
37ac31cdac Bumps up release version to 3.0.0-beta.20 and updates changelog 2017-02-23 19:04:55 -05:00
15096d483f Merge pull request #830 from mailpoet/newsletter_save_fix
Fixes newsletter options being wiped on Step 3 of creation process [MAILPOET-833]
2017-02-23 23:51:59 +03:00
16724affad Remove an excess count() [MAILPOET-833] 2017-02-23 23:42:28 +03:00
384d59abe0 Update Newsletters.php
Removes request interruption when options are not found
2017-02-23 15:25:34 -05:00
027414b7a2 Updates method data verification condition 2017-02-23 14:39:56 -05:00
a1cd56c419 Updates newsletter option creation logic and fixes a bug that results in
them being wiped clean
2017-02-23 14:30:53 -05:00
6ee1c23f9a Merge pull request #829 from mailpoet/post_title_shortcode_fix
Fix post_title shortcode breaking sending if the post is trashed in the process [MAILPOET-831]
2017-02-23 14:41:24 +01:00
44a223eba1 Fix post_title shortcode breaking sending if the post is trashed in the process [MAILPOET-831] 2017-02-23 16:33:18 +03:00
7c66754541 Merge pull request #828 from mailpoet/premium_hooks
Premium hooks / Additional endpoints in Free API [PREMIUM-4]
2017-02-23 15:26:30 +02:00
bb80fc0860 Add unit tests for API endpoints injection [PREMIUM-4] 2017-02-22 18:02:58 +03:00
6c7cc5de0d Implement support for additional endpoints in Free API [PREMIUM-4] 2017-02-22 11:19:35 +03:00
063cc9edc3 added do_action() helper to twig + settings/basics hook 2017-02-22 10:12:08 +03:00
76c283502e Bumps up release version to 3.0.0-beta.19 and updates changelog 2017-02-21 11:55:53 -05:00
554add0882 Merge pull request #823 from mailpoet/smtp_mailer_logger_implementation
Provides detailed SMTP error message and shows last unprocessed subscriber [MAILPOET-823]
2017-02-21 18:09:23 +03:00
689c340081 Converts special characters to HTML entities for all error messages 2017-02-21 09:55:18 -05:00
01e4ed7efc Merge pull request #827 from mailpoet/template_screenshots
Newsletter template fixes [MAILPOET-817]
2017-02-21 15:50:06 +01:00
59f408846a Make all default newsletter templates read-only 2017-02-21 16:32:03 +02:00
64606e69cf Fix Populator to update existing newsletter templates and remove duplicates 2017-02-21 15:46:28 +02:00
e8e0c1e0b3 Automatically update SVN /assets folder upon publishing a release 2017-02-21 15:46:28 +02:00
953c124ef0 Move newsletter template assets to be shared through WP plugin repo 2017-02-21 15:46:28 +02:00
2e1be55bf9 Merge pull request #826 from mailpoet/newsletter_restore_fix
Set newsletter status to draft when restoring newsletters trashed during sending [MAILPOET-816]
2017-02-21 13:54:17 +01:00
33125f2ce7 Merge pull request #820 from mailpoet/translations
Fix translations based on translators' feedback [MAILPOET-819]
2017-02-21 13:20:24 +01:00
e99d2b380c Fix a space [MAILPOET-816] 2017-02-21 13:34:33 +03:00
bcf89f0dfe Set newsletter status as draft when restoring newsletters trashed during sending [MAILPOET-816] 2017-02-21 13:13:59 +03:00
3d64a42126 Merge pull request #819 from mailpoet/bulk_delete_custom_fields
Deletes custom fields associations when bulk deleting subscribers [MAILPOET-820]
2017-02-21 11:32:11 +02:00
97eb772ab8 Enables temporarily disabled unit test 2017-02-20 13:17:06 -05:00
bc40bbb44b Convert special characters to HTML entities when displaying email
addresses
2017-02-20 13:03:08 -05:00
ed117e53d9 Adds unit test for bulk delete action 2017-02-20 12:58:26 -05:00
b74f40c7a8 Makes code more readable/cleaner 2017-02-20 09:30:14 -05:00
bf4023c0ad Restores accidentally removed tests 2017-02-20 09:07:18 -05:00
c98cdb3e57 Merge pull request #811 from mailpoet/popups_focus
Focus on popups by default [MAILPOET-724]
2017-02-20 15:36:07 +02:00
a01b094f9f Merge pull request #824 from mailpoet/import_regex_update
Updates email regex from loose HTML5 standard to stricter RFC 5322 [MAILPOET-825]
2017-02-20 16:30:01 +03:00
e75bb7ee59 Capitalize words of a title in welcome template 2017-02-20 15:22:28 +02:00
563c62855a Undo title capitalization changes, as not all languages use it 2017-02-20 14:58:53 +02:00
8a15424a62 Remove "Read more" link to obsolete KB article 2017-02-20 14:58:53 +02:00
8eb04534bc Fixed translations based on feedback from translators on Transifex 2017-02-20 14:58:53 +02:00
b3abf46604 Update outdated newsletter template translation strings 2017-02-20 14:58:53 +02:00
6f19a1593e Fix translations from GDocs based on FxB's feedback [MAILPOET-819] 2017-02-20 14:58:53 +02:00
883711698e Updates email regex from loose HTML5 standard to stricter RFC 5322
that's required by some mail transport agents
2017-02-18 10:33:45 -05:00
bdcfd77d42 Returns last unprocessed subscriber in error message 2017-02-16 23:25:05 -05:00
2cd503e0e0 Merge pull request #822 from mailpoet/php71_button_rendering
Fix button block rendering producing notices on PHP 7.1 [MAILPOET-822]
2017-02-17 05:05:38 +01:00
c9519f0b3d Adds logger to record SMTP communication
Returns a more complete error message and last unprocessed subscriber
2017-02-16 22:45:09 -05:00
0daa3057e7 Focus on all modal types by default [MAILPOET-724] 2017-02-16 21:58:41 +03:00
0ac5129e0e Merge pull request #818 from mailpoet/destroyed_newsletter
Fix newsletter corruption when going back to editor from last newsletter creation step [MAILPOET-787]
2017-02-16 18:39:01 +03:00
a3aa347fdf Fix button block rendering producing notices on PHP 7.1 [MAILPOET-822] 2017-02-16 17:53:36 +03:00
a40d1122a5 Change corrupted newsletter notice to a static (dismissable) one 2017-02-16 11:21:01 +02:00
9955b8fda1 Merge pull request #821 from mailpoet/php53_fix
Replaces references to self::$variable with Class:$variable for PHP 5.3 hosts [MAILPOET-821]
2017-02-16 11:53:34 +03:00
c994438fa8 Replace more self:: references in closures for PHP 5.3 [MAILPOET-821] 2017-02-16 11:22:26 +03:00
3085ae575a Replaces references to self::$variable with Class:$variable for PHP 5.3
hosts
2017-02-15 18:27:52 -05:00
793b8ce29e Deletes custom fields associations when bulk deleting subscribers 2017-02-15 15:40:26 -05:00
5d447cdec3 Merge pull request #817 from mailpoet/large_list_sending_fix
Fixes DB query that returns large result set when there are thousands of subscribers [MAILPOET-815]
2017-02-15 19:01:26 +02:00
e7698b0131 Optimizes DB query and result processing code 2017-02-15 10:02:13 -05:00
f86121b656 Update the error message to a clearer one 2017-02-15 15:05:30 +02:00
45c223c14b Fix newsletter corruption when going back to editor from last newsletter step 2017-02-15 15:04:07 +02:00
66990d62c2 Restore focus on previously focused element after closing a modal box [MAILPOET-724] 2017-02-15 10:09:14 +03:00
4439111a44 Focus on popups by default [MAILPOET-724] 2017-02-15 10:09:14 +03:00
440b7e4e6c Fixes DB query that returns large result set when there are thousands of
subscribers
2017-02-14 21:07:40 -05:00
b90d7894ca Bump up release version to 3.0.0-beta.18 2017-02-14 17:49:48 +03:00
a0b37eb08c Merge pull request #810 from mailpoet/list_stats_fix
Fix subscriber stats for lists ignoring subscription status and including trashed items [MAILPOET-807]
2017-02-14 14:27:59 +01:00
44b83436bc Merge pull request #809 from mailpoet/view_plans_button_utm
Add GA UTM params to 'View Email Plans' link in Settings [MAILPOET-806]
2017-02-14 13:54:55 +01:00
0d75ee0e12 Merge pull request #816 from mailpoet/utf8_renderer_fix
Fixed UTF-8 encoding in renderer on PHP 5.3 [MAILPOET-812]
2017-02-14 12:39:53 +03:00
50d77f2aff Merge pull request #814 from mailpoet/robo_upgrade
Update Robo from 0.7.2 to 1.0.5 [MAILPOET-813]
2017-02-14 11:22:56 +02:00
cb813171ce Remove html_entity_decode() constants not supported by PHP 5.3 [MAILPOET-812] 2017-02-14 12:22:35 +03:00
3f188e3690 Updates lint rules to ignore pQuery class (name not in camel case caps,
etc.)
2017-02-13 21:00:46 -05:00
e74938df90 Updates code to use the extended pQuery class 2017-02-13 21:00:45 -05:00
93f7739f46 Extends pQuery to use UTF-8 encoding on older versions of PHP 2017-02-13 21:00:32 -05:00
a918091977 Merge pull request #815 from mailpoet/sendgrid_fix
Fixes SendGrid's error response [MAILPOET-810]
2017-02-13 10:51:34 +03:00
b539eae7f9 Fix SendGrid error message assignment [MAILPOET-810] 2017-02-13 10:35:21 +03:00
228a671749 Converts SendGrid's error response message from array to string 2017-02-11 05:41:02 -05:00
70fe253db3 Update Robo from 0.7.2 to 1.0.5 2017-02-09 19:08:04 +03:00
bdb97261fd Merge pull request #813 from mailpoet/alc_categories_fix
Fix categories not showing for posts in newsletter editor [MAILPOET-808]
2017-02-09 06:23:01 -05:00
8507c77699 Fix categories not showing for posts in newsletter editor [MAILPOET-808] 2017-02-09 11:13:46 +03:00
771ff134a8 Merge pull request #812 from mailpoet/create_form_in_widget_fix
Fix 'Create a new form' link on widgets page having no feedback [MAILPOET-691]
2017-02-08 11:05:23 -05:00
9c1cbba163 Fix 'Create a new form' link on widgets page having no feedback [MAILPOET-691] 2017-02-08 13:30:49 +03:00
f51122b58f Fix subscriber stats for lists ignoring subscription status and including trashed items [MAILPOET-807] 2017-02-06 16:04:16 +03:00
e8fd992235 Add GA UTM params to 'View Email Plans' link in Settings [MAILPOET-806] 2017-02-06 12:56:39 +03:00
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
245 changed files with 4338 additions and 1349 deletions

View File

@ -16,3 +16,4 @@ WP_TEST_MAILER_SMTP_LOGIN=""
WP_TEST_MAILER_SMTP_PASSWORD=""
WP_SVN_USERNAME=""
WP_SVN_PASSWORD=""
WP_TRANSIFEX_API_TOKEN=""

9
.tx/config Normal file
View File

@ -0,0 +1,9 @@
[main]
host = https://www.transifex.com
[mp3.mailpoet]
source_file = lang/mailpoet.pot
file_filter = lang/mailpoet-<lang>.po
source_lang = en_US
type = PO
minimum_perc = 100

View File

@ -129,6 +129,19 @@ _n()
You can use Twig i18n functions in Handlebars, just load your template from a Twig view.
# Build
To build a plugin , run `./build.sh`.
Some build process steps are described below (their dependencies etc.).
## packtranslations step
This step imports translations from Transifex and generates MO files. It requires:
* `tx` client: https://docs.transifex.com/client/installing-the-client
* `msgfmt` command (from Gettext package)
Finally , a `WP_TRANSIFEX_API_TOKEN` environment variable should be initialized with a valid key.
# Publish
Before you run a publishing command, you need to:

View File

@ -68,9 +68,9 @@ class RoboFile extends \Robo\Tasks {
}
function compileAll() {
$collection = $this->collection();
$collection->add(array($this, 'compileJs'));
$collection->add(array($this, 'compileCss'));
$collection = $this->collectionBuilder();
$collection->addCode(array($this, 'compileJs'));
$collection->addCode(array($this, 'compileCss'));
return $collection->run();
}
@ -104,6 +104,12 @@ class RoboFile extends \Robo\Tasks {
);
}
function packtranslations() {
// Define WP_TRANSIFEX_API_TOKEN env. variable
$this->loadEnv();
return $this->_exec('./tasks/pack_translations.sh');
}
function testUnit($opts=['file' => null, 'xml' => false]) {
$this->loadEnv();
$this->_exec('vendor/bin/codecept build');
@ -164,9 +170,9 @@ class RoboFile extends \Robo\Tasks {
}
function qa() {
$collection = $this->collection();
$collection->add(array($this, 'qaLint'));
$collection->add(function() {
$collection = $this->collectionBuilder();
$collection->addCode(array($this, 'qaLint'));
$collection->addCode(function() {
return $this->qaCodeSniffer('all');
});
return $collection->run();
@ -186,7 +192,7 @@ class RoboFile extends \Robo\Tasks {
'./vendor/bin/phpcs '.
'--standard=./tasks/code_sniffer/MailPoet '.
'--ignore=./lib/Util/Sudzy/*,./lib/Util/CSS.php,./lib/Util/XLSXWriter.php,'.
'./lib/Config/PopulatorData/Templates/* '.
'./lib/Util/pQuery/*,./lib/Config/PopulatorData/Templates/* '.
'lib/ '.
$severityFlag
);
@ -219,42 +225,54 @@ class RoboFile extends \Robo\Tasks {
return;
}
$collection = $this->collection();
$collection = $this->collectionBuilder();
// Clean up tmp dirs if the previous run was halted
if(file_exists("$svn_dir/trunk_new") || file_exists("$svn_dir/trunk_old")) {
$this->taskFileSystemStack()
$collection->taskFileSystemStack()
->stopOnFail()
->remove(array("$svn_dir/trunk_new", "$svn_dir/trunk_old"))
->addToCollection($collection);
->remove(array("$svn_dir/trunk_new", "$svn_dir/trunk_old"));
}
// Extract the distributable zip to tmp trunk dir
$this->taskExtract($plugin_dist_file)
$collection->taskExtract($plugin_dist_file)
->to("$svn_dir/trunk_new")
->preserveTopDirectory(false)
->addToCollection($collection);
->preserveTopDirectory(false);
// Rename current trunk
if(file_exists("$svn_dir/trunk")) {
$this->taskFileSystemStack()
->rename("$svn_dir/trunk", "$svn_dir/trunk_old")
->addToCollection($collection);
$collection->taskFileSystemStack()
->rename("$svn_dir/trunk", "$svn_dir/trunk_old");
}
// Replace old trunk with a new one
$this->taskFileSystemStack()
$collection->taskFileSystemStack()
->stopOnFail()
->rename("$svn_dir/trunk_new", "$svn_dir/trunk")
->remove("$svn_dir/trunk_old")
->addToCollection($collection);
->remove("$svn_dir/trunk_old");
// Add new repository assets
$collection->taskFileSystemStack()
->mirror('./plugin_repository/assets', "$svn_dir/assets_new");
// Rename current assets folder
if(file_exists("$svn_dir/assets")) {
$collection->taskFileSystemStack()
->rename("$svn_dir/assets", "$svn_dir/assets_old");
}
// Replace old assets with new ones
$collection->taskFileSystemStack()
->stopOnFail()
->rename("$svn_dir/assets_new", "$svn_dir/assets")
->remove("$svn_dir/assets_old");
// Windows compatibility
$awkCmd = '{print " --force \""$2"\""}';
// Mac OS X compatibility
$xargsFlag = (stripos(PHP_OS, 'Darwin') !== false) ? '' : '-r';
$this->taskExecStack()
$collection->taskExecStack()
->stopOnFail()
// Set SVN repo as working directory
->dir($svn_dir)
@ -263,8 +281,7 @@ class RoboFile extends \Robo\Tasks {
// Recursively add files to SVN that haven't been added yet
->exec("svn add --force * --auto-props --parents --depth infinity -q")
// Tag the release
->exec("svn cp trunk tags/$plugin_version")
->addToCollection($collection);
->exec("svn cp trunk tags/$plugin_version");
$result = $collection->run();

View File

@ -131,6 +131,7 @@ body
display: none
.wrap > .mailpoet_notice,
.notice
.update-nag
margin-left: 2px + 15px !important

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

@ -1024,7 +1024,7 @@ WysijaForm.Widget = Class.create(WysijaForm.Block, {
},
editSettings: function() {
MailPoet.Modal.popup({
title: 'Edit field settings', // TODO: translate!
title: MailPoet.I18n.t('editFieldSettings'),
template: jQuery('#form_template_field_settings').html(),
data: this.getData(),
onSuccess: function() {
@ -1061,4 +1061,4 @@ function info(value) {
} catch(e) {}
}
module.exports = WysijaForm;
module.exports = WysijaForm;

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

@ -164,11 +164,16 @@ define([
{ 'one-page': (this.props.count <= this.props.limit) }
);
var numberOfItemsLabel;
if (this.props.count == 1) {
numberOfItemsLabel = MailPoet.I18n.t('numberOfItemsSingular');
} else {
numberOfItemsLabel = MailPoet.I18n.t('numberOfItemsMultiple')
.replace('%$1d', this.props.count.toLocaleString());
}
return (
<div className={ classes }>
<span className="displaying-num">{
MailPoet.I18n.t('numberOfItems').replace('%$1d', this.props.count.toLocaleString())
}</span>
<span className="displaying-num">{ numberOfItemsLabel }</span>
{ pagination }
</div>
);

View File

@ -28,6 +28,9 @@ define('modal', ['mailpoet', 'jquery'],
opened: false,
locked: false,
// previously focused element
prevFocus: null,
// sub panels
subpanels: [],
@ -59,6 +62,9 @@ define('modal', ['mailpoet', 'jquery'],
// display overlay
overlay: false,
// focus upon displaying
focus: true,
// highlighted elements
highlight: null,
@ -71,7 +77,7 @@ define('modal', ['mailpoet', 'jquery'],
options: {},
templates: {
overlay: '<div id="mailpoet_modal_overlay" style="display:none;"></div>',
popup: '<div id="mailpoet_popup">'+
popup: '<div id="mailpoet_popup" tabindex="-1">'+
'<div class="mailpoet_popup_wrapper">'+
'<a href="javascript:;" id="mailpoet_modal_close"></a>'+
'<div id="mailpoet_popup_title"><h2></h2></div>'+
@ -85,11 +91,11 @@ define('modal', ['mailpoet', 'jquery'],
'</div>',
panel: '<div id="mailpoet_panel">'+
'<a href="javascript:;" id="mailpoet_modal_close"></a>'+
'<div class="mailpoet_panel_wrapper">'+
'<div class="mailpoet_panel_wrapper" tabindex="-1">'+
'<div class="mailpoet_panel_body clearfix"></div>'+
'</div>'+
'</div>',
subpanel: '<div class="mailpoet_panel_wrapper">'+
subpanel: '<div class="mailpoet_panel_wrapper" tabindex="-1">'+
'<div class="mailpoet_panel_body clearfix"></div>'+
'</div>'
},
@ -251,6 +257,11 @@ define('modal', ['mailpoet', 'jquery'],
// add sub panel content
jQuery('.mailpoet_'+this.options.type+'_body').last()
.html(this.subpanels[(this.subpanels.length - 1)].element);
// focus on sub panel
if(this.options.focus) {
this.focus();
}
} else if (this.options.element) {
jQuery('.mailpoet_'+this.options.type+'_body').empty();
jQuery('.mailpoet_'+this.options.type+'_body')
@ -369,6 +380,9 @@ define('modal', ['mailpoet', 'jquery'],
// set modal dimensions
this.setDimensions();
// remember the previously focused element
this.prevFocus = jQuery(':focus');
// add a flag on the body so that we can prevent scrolling
jQuery('body').addClass('mailpoet_modal_opened');
@ -388,6 +402,10 @@ define('modal', ['mailpoet', 'jquery'],
}
}
if(this.options.focus) {
this.focus();
}
// set popup as opened
this.opened = true;
@ -398,6 +416,16 @@ define('modal', ['mailpoet', 'jquery'],
return this;
},
focus: function() {
if(this.options.type == 'popup') {
jQuery('#mailpoet_'+this.options.type).focus();
} else {
// panel and subpanel
jQuery('#mailpoet_'+this.options.type+' .mailpoet_panel_wrapper')
.filter(':visible').focus();
}
return this;
},
highlightOn: function(element) {
jQuery(element).addClass('mailpoet_modal_highlight');
return this;
@ -579,6 +607,11 @@ define('modal', ['mailpoet', 'jquery'],
// remove last subpanels
this.subpanels.pop();
// focus on previous panel
if(this.options.focus) {
this.focus();
}
return this;
}
@ -591,6 +624,11 @@ define('modal', ['mailpoet', 'jquery'],
// destroy modal element
this.destroy();
// restore the previously focused element
if(this.prevFocus !== undefined){
this.prevFocus.focus();
}
// reset options
this.options = {
onSuccess: null,

View File

@ -6,8 +6,9 @@
define([
'backbone.marionette',
'newsletter_editor/behaviors/BehaviorsLookup',
'mailpoet',
'spectrum'
], function(Marionette, BehaviorsLookup, Spectrum) {
], function(Marionette, BehaviorsLookup, MailPoet, Spectrum) {
BehaviorsLookup.ColorPickerBehavior = Marionette.Behavior.extend({
onRender: function() {
@ -17,6 +18,8 @@ define([
showInitial: true,
preferredFormat: "hex6",
allowEmpty: true,
chooseText: MailPoet.I18n.t('selectColor'),
cancelText: MailPoet.I18n.t('cancelColorSelection')
});
},
});

View File

@ -1,8 +1,9 @@
define([
'newsletter_editor/App',
'backbone.supermodel',
'underscore'
], function(App, SuperModel, _) {
'underscore',
'mailpoet'
], function(App, SuperModel, _, MailPoet) {
"use strict";
var Module = {};
@ -81,7 +82,16 @@ define([
App.on('start', function(options) {
var body = options.newsletter.body;
App._contentContainer = new (App.getBlockTypeModel('container'))(body.content, {parse: true});
var content = (_.has(body, 'content')) ? body.content : {};
if (!_.has(options.newsletter, 'body') || !_.isObject(options.newsletter.body)) {
MailPoet.Notice.error(
MailPoet.I18n.t('newsletterBodyIsCorrupted'),
{ static: true }
);
}
App._contentContainer = new (App.getBlockTypeModel('container'))(content, {parse: true});
App._contentContainerView = new (App.getBlockTypeView('container'))({
model: App._contentContainer,
renderOptions: { depth: 0 },

View File

@ -46,9 +46,8 @@ define([
//MailPoet.Notice.success("<?php _e('Newsletter has been saved.'); ?>");
} else if(response.error !== undefined) {
if(response.error.length === 0) {
// TODO: Handle translations
MailPoet.Notice.error(
"An unknown error occurred, please check your settings.",
MailPoet.I18n.t('templateSaveFailed'),
{
scroll: true,
}
@ -169,7 +168,7 @@ define([
},
beforeSave: function() {
// TODO: Add a loading animation instead
this.$('.mailpoet_autosaved_at').text('Saving...');
this.$('.mailpoet_autosaved_at').text(MailPoet.I18n.t('saving'));
},
afterSave: function(json, response) {
this.validateNewsletter(json);
@ -330,8 +329,7 @@ define([
Module.beforeExitWithUnsavedChanges = function(e) {
if (saveTimeout) {
// TODO: Translate this message
var message = "There are unsaved changes which will be lost if you leave this page.";
var message = MailPoet.I18n.t('unsavedChangesWillBeLost');
e = e || window.event;
if (e) {

View File

@ -158,6 +158,9 @@ define([
*/
Module.SidebarStylesView = Marionette.LayoutView.extend({
getTemplate: function() { return templates.sidebarStyles; },
behaviors: {
ColorPickerBehavior: {},
},
events: function() {
return {
"change #mailpoet_text_font_color": _.partial(this.changeColorField, 'text.fontColor'),
@ -205,15 +208,6 @@ define([
initialize: function(options) {
this.availableStyles = options.availableStyles;
},
onRender: function() {
this.$('.mailpoet_color').spectrum({
clickoutFiresChange: true,
showInput: true,
showInitial: true,
preferredFormat: "hex6",
allowEmpty: true,
});
},
changeField: function(field, event) {
this.model.set(field, jQuery(event.target).val());
},

View File

@ -69,11 +69,11 @@ define([
// Expose style methods to global application
App.getGlobalStyles = Module.getGlobalStyles;
App.setGlobalStyles = Module.setGlobalStyles;
App.getAvailableStyles = Module.getAvailableStyles;
var body = options.newsletter.body;
this.setGlobalStyles(body.globalStyles);
var globalStyles = (_.has(body, 'globalStyles')) ? body.globalStyles : {};
this.setGlobalStyles(globalStyles);
});
App.on('start', function(options) {

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

@ -173,15 +173,20 @@ define(
var data = this.state.item;
this.setState({ loading: true });
// Ensure that body is JSON encoded
if (!_.isUndefined(data.body)) {
data.body = JSON.stringify(data.body);
}
// Store only properties that can be changed on this page
const IGNORED_NEWSLETTER_PROPERTIES = [
'preheader', 'body', 'created_at', 'deleted_at', 'hash',
'status', 'updated_at', 'type'
];
const newsletterData = _.omit(
data,
IGNORED_NEWSLETTER_PROPERTIES
);
return MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'save',
data: data,
data: newsletterData,
}).always(() => {
this.setState({ loading: false });
});

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

@ -3,6 +3,7 @@
# Translations (npm install & composer install need to be run before)
echo '[BUILD] Generating translations'
./do makepot
./do packtranslations
plugin_name='mailpoet'
@ -50,17 +51,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

@ -8,24 +8,25 @@
"require": {
"php": ">=5.3.3",
"twig/twig": "1.*",
"cerdic/css-tidy": "*",
"tburry/pquery": "*",
"cerdic/css-tidy": "^1.5.5",
"tburry/pquery": "^1.1.1",
"j4mie/paris": "1.5.4",
"swiftmailer/swiftmailer": "^5.4",
"mtdowling/cron-expression": "^1.1",
"nesbot/carbon": "^1.21",
"soundasleep/html2text": "dev-master",
"soundasleep/html2text": "^0.3.4",
"sabberworm/php-css-parser": "^8.1"
},
"require-dev": {
"codeception/codeception": "*",
"codeception/verify": "*",
"codegyre/robo": "*",
"vlucas/phpdotenv": "*",
"codeception/codeception": "^2.2.9",
"codeception/verify": "^0.3.3",
"consolidation/robo": "^1.0.5",
"henrikbjorn/lurker": "^1.2",
"vlucas/phpdotenv": "^2.4.0",
"umpirsky/twig-gettext-extractor": "1.1.*",
"raveren/kint": "^1.0",
"squizlabs/php_codesniffer": "*",
"wimg/php-compatibility": "*",
"squizlabs/php_codesniffer": "^2.8.1",
"wimg/php-compatibility": "^7.1.2",
"simplyadmire/composer-plugins" : "@dev"
},
"autoload": {

1049
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,8 @@
<?php
namespace MailPoet\API;
use \MailPoet\Util\Security;
use MailPoet\Util\Helpers;
use MailPoet\Util\Security;
if(!defined('ABSPATH')) exit;
@ -9,9 +11,14 @@ class API {
private $_method;
private $_token;
private $_endpoint_namespaces = array();
private $_endpoint_class;
private $_data = array();
function __construct() {
$this->addEndpointNamespace(__NAMESPACE__ . "\\Endpoints");
}
function init() {
// Admin Security token
add_action(
@ -33,12 +40,14 @@ class API {
}
function setupAjax() {
$this->getRequestData();
do_action('mailpoet_api_setup', array($this));
$this->getRequestData($_POST);
if($this->checkToken() === false) {
$error_response = new ErrorResponse(
array(
Error::UNAUTHORIZED => __('Invalid request.', 'mailpoet')
Error::UNAUTHORIZED => __('Invalid request', 'mailpoet')
),
array(),
Response::STATUS_UNAUTHORIZED
@ -46,37 +55,41 @@ class API {
$error_response->send();
}
$this->processRoute();
$response = $this->processRoute();
$response->send();
}
function getRequestData() {
$this->_endpoint = isset($_POST['endpoint'])
? trim($_POST['endpoint'])
function getRequestData($data) {
$this->_endpoint = isset($data['endpoint'])
? Helpers::underscoreToCamelCase(trim($data['endpoint']))
: null;
$this->_method = isset($_POST['method'])
? trim($_POST['method'])
$this->_method = isset($data['method'])
? Helpers::underscoreToCamelCase(trim($data['method']))
: null;
$this->_token = isset($_POST['token'])
? trim($_POST['token'])
$this->_token = isset($data['token'])
? trim($data['token'])
: null;
if(!$this->_endpoint || !$this->_method) {
// 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
);
$error_response->send();
} else {
$this->_endpoint_class = (
__NAMESPACE__."\\Endpoints\\".ucfirst($this->_endpoint)
);
foreach($this->_endpoint_namespaces as $namespace) {
$class_name = $namespace . "\\" . ucfirst($this->_endpoint);
if(class_exists($class_name)) {
$this->_endpoint_class = $class_name;
}
}
$this->_data = isset($_POST['data'])
? stripslashes_deep($_POST['data'])
$this->_data = isset($data['data'])
? stripslashes_deep($data['data'])
: array();
// remove reserved keywords from data
@ -98,6 +111,10 @@ class API {
function processRoute() {
try {
if(empty($this->_endpoint_class)) {
throw new \Exception('Invalid endpoint');
}
$endpoint = new $this->_endpoint_class();
// check the accessibility of the requested endpoint's action
@ -119,17 +136,17 @@ class API {
array(),
Response::STATUS_FORBIDDEN
);
$error_response->send();
return $error_response;
}
}
$response = $endpoint->{$this->_method}($this->_data);
$response->send();
return $response;
} catch(\Exception $e) {
$error_response = new ErrorResponse(
array($e->getCode() => $e->getMessage())
);
$error_response->send();
return $error_response;
}
}
@ -149,4 +166,12 @@ class API {
$global .= '</script>';
echo $global;
}
}
function addEndpointNamespace($namespace) {
$this->_endpoint_namespaces[] = $namespace;
}
function getEndpointNamespaces() {
return $this->_endpoint_namespaces;
}
}

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

@ -14,7 +14,10 @@ class Mailer extends APIEndpoint {
(isset($data['sender'])) ? $data['sender'] : false,
(isset($data['reply_to'])) ? $data['reply_to'] : false
);
$result = $mailer->send($data['newsletter'], $data['subscriber']);
$extra_params = array(
'test_email' => true
);
$result = $mailer->send($data['newsletter'], $data['subscriber'], $extra_params);
} catch(\Exception $e) {
return $this->errorResponse(array(
$e->getCode() => $e->getMessage()

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

@ -4,6 +4,7 @@ namespace MailPoet\API\Endpoints;
use MailPoet\API\Endpoint as APIEndpoint;
use MailPoet\API\Error as APIError;
use MailPoet\Listing;
use MailPoet\Models\SendingQueue;
use MailPoet\Models\Setting;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterTemplate;
@ -70,30 +71,40 @@ class Newsletters extends APIEndpoint {
}
if(!empty($options)) {
NewsletterOption::where('newsletter_id', $newsletter->id)
->deleteMany();
$option_fields = NewsletterOptionField::where(
'newsletter_type',
$data['type']
)->findArray();
$newsletter->type
)->findMany();
// update newsletter options
foreach($option_fields as $option_field) {
if(isset($options[$option_field['name']])) {
$relation = NewsletterOption::create();
$relation->newsletter_id = $newsletter->id;
$relation->option_field_id = $option_field['id'];
$relation->value = $options[$option_field['name']];
$relation->save();
if(isset($options[$option_field->name])) {
$newsletter_option = NewsletterOption::createOrUpdate(
array(
'newsletter_id' => $newsletter->id,
'option_field_id' => $option_field->id,
'value' => $options[$option_field->name]
)
);
}
}
}
$newsletter = Newsletter::filter('filterWithOptions')->findOne($newsletter->id);
// reload newsletter with updated options
$newsletter = Newsletter::filter('filterWithOptions')
->findOne($newsletter->id);
// if this is a post notification, process options and update its schedule
if($newsletter->type === Newsletter::TYPE_NOTIFICATION) {
Scheduler::processPostNotificationSchedule($newsletter);
// if this is a post notification, process newsletter options and update its schedule
if($newsletter->type === Newsletter::TYPE_NOTIFICATION) {
// generate the new schedule from options and get the new "next run" date
$newsletter->schedule = Scheduler::processPostNotificationSchedule($newsletter);
$next_run_date = Scheduler::getNextRunDate($newsletter->schedule);
// find previously scheduled jobs and reschedule them using the new "next run" date
SendingQueue::where('newsletter_id', $newsletter->id)
->where('status', SendingQueue::STATUS_SCHEDULED)
->findResultSet()
->set('scheduled_at', $next_run_date)
->save();
}
}
return $this->successResponse($newsletter->asArray());
@ -340,11 +351,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 +445,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;
@ -52,7 +48,6 @@ class SendingQueue extends APIEndpoint {
APIError::NOT_FOUND => __('This newsletter is already being sent.', 'mailpoet')
));
}
$queue = SendingQueueModel::where('newsletter_id', $newsletter->id)
->where('status', SendingQueueModel::STATUS_SCHEDULED)
->findOne();
@ -77,10 +72,8 @@ class SendingQueue extends APIEndpoint {
$segment_ids = array_map(function($segment) {
return $segment['id'];
}, $segments);
$subscribers = Subscriber::getSubscribedInSegments($segment_ids)
->findArray();
$subscribers = Helpers::arrayColumn($subscribers, 'subscriber_id');
$subscribers = array_unique($subscribers);
$subscribers = Subscriber::getSubscribedInSegments($segment_ids)->findArray();
$subscribers = Helpers::flattenArray($subscribers);
if(!count($subscribers)) {
return $this->errorResponse(array(
APIError::UNKNOWN => __('There are no subscribers in that list!', 'mailpoet')

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() {
@ -74,7 +75,8 @@ class Menu {
add_screen_option('per_page', array(
'label' => _x(
'Number of newsletters per page',
'newsletters per page (screen options)'
'newsletters per page (screen options)',
'mailpoet'
),
'option' => 'mailpoet_newsletters_per_page'
));
@ -96,7 +98,8 @@ class Menu {
add_screen_option('per_page', array(
'label' => _x(
'Number of forms per page',
'forms per page (screen options)'
'forms per page (screen options)',
'mailpoet'
),
'option' => 'mailpoet_forms_per_page'
));
@ -118,7 +121,8 @@ class Menu {
add_screen_option('per_page', array(
'label' => _x(
'Number of subscribers per page',
'subscribers per page (screen options)'
'subscribers per page (screen options)',
'mailpoet'
),
'option' => 'mailpoet_subscribers_per_page'
));
@ -141,7 +145,8 @@ class Menu {
add_screen_option('per_page', array(
'label' => _x(
'Number of segments per page',
'segments per page (screen options)'
'segments per page (screen options)',
'mailpoet'
),
'option' => 'mailpoet_segments_per_page'
));
@ -302,6 +307,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 +393,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 +492,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 +546,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,14 +138,14 @@ 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();
}
}
private function newsletterOptionFields() {
return array(
$option_fields = array(
array(
'name' => 'isScheduled',
'newsletter_type' => 'standard',
@ -200,6 +200,14 @@ class Populator {
'newsletter_type' => 'notification',
)
);
return array(
'rows' => $option_fields,
'identification_columns' => array(
'name',
'newsletter_type'
)
);
}
private function newsletterTemplates() {
@ -209,17 +217,40 @@ class Populator {
$template = new $template(Env::$assets_url);
$templates[] = $template->get();
}
return $templates;
return array(
'rows' => $templates,
'identification_columns' => array(
'name'
),
'remove_duplicates' => true
);
}
private function populate($model) {
$modelMethod = Helpers::underscoreToCamelCase($model);
$rows = $this->$modelMethod();
$table = $this->prefix . $model;
$data_descriptor = $this->$modelMethod();
$rows = $data_descriptor['rows'];
$identification_columns = array_fill_keys(
$data_descriptor['identification_columns'],
''
);
$remove_duplicates =
isset($data_descriptor['remove_duplicates']) && $data_descriptor['remove_duplicates'];
foreach($rows as $row) {
if(!$this->rowExists($table, $row)) {
$existence_comparison_fields = array_intersect_key(
$row,
$identification_columns
);
if(!$this->rowExists($table, $existence_comparison_fields)) {
$this->insertRow($table, $row);
} else {
if($remove_duplicates) {
$this->removeDuplicates($table, $row, $existence_comparison_fields);
}
$this->updateRow($table, $row, $existence_comparison_fields);
}
}
}
@ -245,4 +276,36 @@ class Populator {
$row
);
}
private function updateRow($table, $row, $where) {
global $wpdb;
return $wpdb->update(
$table,
$row,
$where
);
}
private function removeDuplicates($table, $row, $where) {
global $wpdb;
$conditions = array('1=1');
$values = array();
foreach($where as $field => $value) {
$conditions[] = "`t1`.`$field` = `t2`.`$field`";
$conditions[] = "`t1`.`$field` = %s";
$values[] = $value;
}
$conditions = implode(' AND ', $conditions);
$sql = "DELETE FROM `$table` WHERE $conditions";
return $wpdb->query(
$wpdb->prepare(
"DELETE t1 FROM $table t1, $table t2 WHERE t1.id < t2.id AND $conditions",
$values
)
);
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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,25 @@ 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_is_paused = MailerLog::isSendingPaused();
// 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 && !$sending_is_paused);
$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

@ -76,10 +76,8 @@ class Scheduler {
}, $segments);
// ensure that subscribers are in segments
$subscribers = Subscriber::getSubscribedInSegments($segment_ids)
->findArray();
$subscribers = Helpers::arrayColumn($subscribers, 'subscriber_id');
$subscribers = array_unique($subscribers);
$subscribers = Subscriber::getSubscribedInSegments($segment_ids)->findArray();
$subscribers = Helpers::flattenArray($subscribers);
if(empty($subscribers)) {
return $this->deleteQueueOrUpdateNextRunDate($queue, $newsletter);
@ -107,11 +105,8 @@ class Scheduler {
$segment_ids = array_map(function($segment) {
return $segment['id'];
}, $segments);
$subscribers = Subscriber::getSubscribedInSegments($segment_ids)
->findArray();
$subscribers = Helpers::arrayColumn($subscribers, 'subscriber_id');
$subscribers = array_unique($subscribers);
$subscribers = Subscriber::getSubscribedInSegments($segment_ids)->findArray();
$subscribers = Helpers::flattenArray($subscribers);
// update current queue
$queue->subscribers = serialize(
array(
@ -169,8 +164,13 @@ class Scheduler {
function deleteQueueOrUpdateNextRunDate($queue, $newsletter) {
if($newsletter->intervalType === NewsletterScheduler::INTERVAL_IMMEDIATELY) {
$queue->delete();
return;
} else {
$next_run_date = NewsletterScheduler::getNextRunDate($newsletter->schedule);
if(!$next_run_date) {
$queue->delete();
return;
}
$queue->scheduled_at = $next_run_date;
$queue->save();
}

View File

@ -6,8 +6,8 @@ use MailPoet\Cron\Workers\SendingQueue\Tasks\Mailer as MailerTask;
use MailPoet\Cron\Workers\SendingQueue\Tasks\Newsletter as NewsletterTask;
use MailPoet\Mailer\MailerLog;
use MailPoet\Models\SendingQueue as SendingQueueModel;
use MailPoet\Models\StatisticsNewsletters as StatisticsNewslettersModel;
use MailPoet\Models\Subscriber as SubscriberModel;
use MailPoet\Models\StatisticsNewsletters as StatisticsNewslettersModel;
if(!defined('ABSPATH')) exit;
@ -15,7 +15,7 @@ class SendingQueue {
public $mailer_task;
public $newsletter_task;
public $timer;
const BATCH_SIZE = 50;
const BATCH_SIZE = 20;
function __construct($timer = false, $mailer_task = false, $newsletter_task = false) {
$this->mailer_task = ($mailer_task) ? $mailer_task : new MailerTask();
@ -34,6 +34,8 @@ class SendingQueue {
}
// configure mailer
$this->mailer_task->configureMailer($newsletter);
// get newsletter segments
$newsletter_segments_ids = $this->newsletter_task->getSegments($newsletter);
// get subscribers
$queue->subscribers = $queue->getSubscribers();
$subscriber_batches = array_chunk(
@ -41,12 +43,10 @@ class SendingQueue {
self::BATCH_SIZE
);
foreach($subscriber_batches as $subscribers_to_process_ids) {
$found_subscribers = SubscriberModel::whereIn('id', $subscribers_to_process_ids)
->whereNull('deleted_at')
->findMany();
$found_subscribers_ids = array_map(function($subscriber) {
return $subscriber->id;
}, $found_subscribers);
$found_subscribers = SubscriberModel::findSubscribersInSegments(
$subscribers_to_process_ids, $newsletter_segments_ids
)->findMany();
$found_subscribers_ids = SubscriberModel::extractSubscribersIds($found_subscribers);
// if some subscribers weren't found, remove them from the processing list
if(count($found_subscribers_ids) !== count($subscribers_to_process_ids)) {
$subscibers_to_remove = array_diff(

View File

@ -5,6 +5,7 @@ use MailPoet\Cron\Workers\SendingQueue\Tasks\Links as LinksTask;
use MailPoet\Cron\Workers\SendingQueue\Tasks\Posts as PostsTask;
use MailPoet\Cron\Workers\SendingQueue\Tasks\Shortcodes as ShortcodesTask;
use MailPoet\Models\Newsletter as NewsletterModel;
use MailPoet\Models\NewsletterSegment as NewsletterSegmentModel;
use MailPoet\Models\Setting;
use MailPoet\Newsletter\Links\Links as NewsletterLinks;
use MailPoet\Newsletter\Renderer\PostProcess\OpenTracking;
@ -100,4 +101,11 @@ class Newsletter {
$newsletter->setStatus(NewsletterModel::STATUS_SENT);
}
}
function getSegments($newsletter) {
$segments = NewsletterSegmentModel::where('newsletter_id', $newsletter->id)
->select('segment_id')
->findArray();
return Helpers::flattenArray($segments);
}
}

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;
@ -83,9 +79,9 @@ class Widget extends \WP_Widget {
endpoint: 'forms',
action: 'create'
}).done(function(response) {
if(response.result && response.form_id) {
if(response.data && response.data.id) {
window.location =
"<?php echo $form_edit_url; ?>" + response.form_id;
"<?php echo $form_edit_url; ?>" + response.data.id;
}
});
return false;

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

@ -47,7 +47,7 @@ class MailerLog {
if($mailer_log['retry_attempt'] === self::RETRY_ATTEMPTS_LIMIT) {
$mailer_log = self::pauseSending($mailer_log);
}
if($mailer_log['status'] === self::STATUS_PAUSED) {
if(self::isSendingPaused($mailer_log)) {
throw new \Exception(__('Sending has been paused.', 'mailpoet'));
}
if(!is_null($mailer_log['retry_at'])) {
@ -117,4 +117,9 @@ class MailerLog {
}
return false;
}
static function isSendingPaused($mailer_log = false) {
$mailer_log = self::getMailerLog($mailer_log);
return $mailer_log['status'] === self::STATUS_PAUSED;
}
}

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());
@ -61,32 +62,69 @@ class AmazonSES {
$response = ($response) ?
$response->Error->Message->__toString() :
sprintf(__('%s has returned an unknown error.', 'mailpoet'), Mailer::METHOD_AMAZONSES);
if(empty($extra_params['test_email'])) {
$response .= sprintf(' %s: %s', __('Unprocessed subscriber', 'mailpoet'), $subscriber);
}
return Mailer::formatMailerSendErrorResult($response);
}
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',
@ -96,7 +134,7 @@ class AmazonSES {
'Authorization' => $this->signRequest($body),
'X-Amz-Date' => $this->date
),
'body' => urldecode(http_build_query($body))
'body' => urldecode(http_build_query($body, null, '&'))
);
}
@ -137,7 +175,7 @@ class AmazonSES {
'x-amz-date:' . $this->date,
'',
'host;x-amz-date',
hash($this->hash_algorithm, urldecode(http_build_query($body)))
hash($this->hash_algorithm, urldecode(http_build_query($body, null, '&')))
));
}
@ -176,4 +214,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) {
@ -49,19 +58,20 @@ class MailPoet {
}
function getBody($newsletter, $subscriber) {
$composeBody = function($newsletter, $subscriber) {
$_this = $this;
$composeBody = function($newsletter, $subscriber) use($_this) {
$body = array(
'to' => (array(
'address' => $subscriber['email'],
'name' => $subscriber['name']
)),
'from' => (array(
'address' => $this->sender['from_email'],
'name' => $this->sender['from_name']
'address' => $_this->sender['from_email'],
'name' => $_this->sender['from_name']
)),
'reply_to' => (array(
'address' => $this->reply_to['reply_to_email'],
'name' => $this->reply_to['reply_to_name']
'address' => $_this->reply_to['reply_to_email'],
'name' => $_this->reply_to['reply_to_name']
)),
'subject' => $newsletter['subject']
);
@ -86,21 +96,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,18 +20,22 @@ 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());
}
return ($result === 1) ?
Mailer::formatMailerSendSuccessResult() :
Mailer::formatMailerSendErrorResult(
sprintf(__('%s has returned an unknown error.', 'mailpoet'), Mailer::METHOD_PHPMAIL)
);
if($result === 1) {
return Mailer::formatMailerSendSuccessResult();
} else {
$result = sprintf(__('%s has returned an unknown error.', 'mailpoet'), Mailer::METHOD_PHPMAIL);
if(empty($extra_params['test_email'])) {
$result .= sprintf(' %s: %s', __('Unprocessed subscriber', 'mailpoet'), $subscriber);
}
return Mailer::formatMailerSendErrorResult($result);
}
}
function buildMailer() {
@ -39,7 +43,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 +55,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

@ -16,6 +16,7 @@ class SMTP {
public $reply_to;
public $return_path;
public $mailer;
const SMTP_CONNECTION_TIMEOUT = 10; // seconds
function __construct(
$host, $port, $authentication, $login = null, $password = null, $encryption,
@ -32,26 +33,28 @@ class SMTP {
$return_path :
$this->sender['from_email'];
$this->mailer = $this->buildMailer();
$this->mailer_logger = new \Swift_Plugins_Loggers_ArrayLogger();
$this->mailer->registerPlugin(new \Swift_Plugins_LoggerPlugin($this->mailer_logger));
}
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());
return Mailer::formatMailerSendErrorResult(
$this->processExceptionMessage($e->getMessage())
);
}
return ($result === 1) ?
Mailer::formatMailerSendSuccessResult() :
Mailer::formatMailerSendErrorResult(
sprintf(__('%s has returned an unknown error.', 'mailpoet'), Mailer::METHOD_SMTP)
);
Mailer::formatMailerSendErrorResult($this->processLogMessage($subscriber, $extra_params));
}
function buildMailer() {
$transport = \Swift_SmtpTransport::newInstance(
$this->host, $this->port, $this->encryption);
$transport->setTimeout(10);
$transport->setTimeout(self::SMTP_CONNECTION_TIMEOUT);
if($this->authentication) {
$transport
->setUsername($this->login)
@ -60,18 +63,26 @@ 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(
->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']
))
->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');
}
@ -93,4 +104,27 @@ class SMTP {
(isset($subscriber_data['name'])) ? $subscriber_data['name'] : ''
);
}
function processLogMessage($subscriber, $extra_params = array(), $log = false) {
$log = ($log) ? $log : $this->mailer_logger->dump();
// extract error message from log
preg_match('/!! (.*?)>>/ism', $log, $message);
if(!empty($message[1])) {
$message = $message[1];
// remove line breaks from the message due to how logger's dump() method works
$message = preg_replace('/\r|\n/', '', $message);
} else {
$message = sprintf(__('%s has returned an unknown error.', 'mailpoet'), Mailer::METHOD_SMTP);
}
if(empty($extra_params['test_email'])) {
$message .= sprintf(' %s: %s', __('Unprocessed subscriber', 'mailpoet'), $subscriber);
}
return $message;
}
function processExceptionMessage($message) {
// remove redundant information appended by Swift logger to exception messages
$message = explode(PHP_EOL, $message);
return $message[0];
}
}

View File

@ -17,25 +17,28 @@ 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());
}
if(wp_remote_retrieve_response_code($result) !== 200) {
$response = json_decode($result['body'], true);
$response = (!empty($response['errors'])) ?
$response['errors'] :
$response = (!empty($response['errors'][0])) ?
$response['errors'][0] :
sprintf(__('%s has returned an unknown error.', 'mailpoet'), Mailer::METHOD_SENDGRID);
if(empty($extra_params['test_email'])) {
$response .= sprintf(' %s: %s', __('Unprocessed subscriber', 'mailpoet'), $subscriber);
}
return Mailer::formatMailerSendErrorResult($response);
}
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 +46,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 +66,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',
@ -65,7 +75,7 @@ class SendGrid {
'headers' => array(
'Authorization' => $this->auth()
),
'body' => http_build_query($body)
'body' => http_build_query($body, null, '&')
);
}
}

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

@ -78,7 +78,7 @@ class Model extends \Sudzy\ValidModel {
static function bulkTrash($orm) {
$model = get_called_class();
$count = self::bulkAction($orm, function($ids) use($model) {
self::rawExecute(join(' ', array(
$model::rawExecute(join(' ', array(
'UPDATE `'.$model::$_table.'`',
'SET `deleted_at` = NOW()',
'WHERE `id` IN ('.rtrim(str_repeat('?,', count($ids)), ',').')'
@ -106,7 +106,7 @@ class Model extends \Sudzy\ValidModel {
static function bulkRestore($orm) {
$model = get_called_class();
$count = self::bulkAction($orm, function($ids) use($model) {
self::rawExecute(join(' ', array(
$model::rawExecute(join(' ', array(
'UPDATE `'.$model::$_table.'`',
'SET `deleted_at` = NULL',
'WHERE `id` IN ('.rtrim(str_repeat('?,', count($ids)), ',').')'

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,23 @@ class Newsletter extends Model {
self::NEWSLETTER_HASH_LENGTH
);
}
}
function restore() {
if($this->status == self::STATUS_SENDING) {
$this->set('status', self::STATUS_DRAFT);
$this->save();
}
return parent::restore();
}
static function bulkRestore($orm) {
parent::bulkAction($orm, function($ids) {
Newsletter::whereIn('id', $ids)
->where('status', Newsletter::STATUS_SENDING)
->findResultSet()
->set('status', Newsletter::STATUS_DRAFT)
->save();
});
return parent::bulkRestore($orm);
}
}

View File

@ -5,4 +5,20 @@ if(!defined('ABSPATH')) exit;
class NewsletterOption extends Model {
public static $_table = MP_NEWSLETTER_OPTION_TABLE;
static function createOrUpdate($data = array()) {
if(!is_array($data) || empty($data['newsletter_id']) || empty($data['option_field_id'])) return;
$newsletter_option = self::where('option_field_id', $data['option_field_id'])
->where('newsletter_id', $data['newsletter_id'])
->findOne();
if(empty($newsletter_option)) $newsletter_option = self::create();
$newsletter_option->newsletter_id = $data['newsletter_id'];
$newsletter_option->option_field_id = $data['option_field_id'];
$newsletter_option->value = $data['value'];
$newsletter_option->save();
return $newsletter_option;
}
}

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')
));
}
@ -76,21 +76,26 @@ class Segment extends Model {
'subscribers'
)
->select_expr(
'SUM(CASE subscribers.status WHEN "' . Subscriber::STATUS_SUBSCRIBED . '" THEN 1 ELSE 0 END)',
'SUM(CASE WHEN subscribers.status = "' . Subscriber::STATUS_SUBSCRIBED . '"
AND relation.status = "' . Subscriber::STATUS_SUBSCRIBED . '" THEN 1 ELSE 0 END)',
Subscriber::STATUS_SUBSCRIBED
)
->select_expr(
'SUM(CASE subscribers.status WHEN "' . Subscriber::STATUS_UNSUBSCRIBED . '" THEN 1 ELSE 0 END)',
'SUM(CASE WHEN subscribers.status = "' . Subscriber::STATUS_UNSUBSCRIBED . '"
OR relation.status = "' . Subscriber::STATUS_UNSUBSCRIBED . '" THEN 1 ELSE 0 END)',
Subscriber::STATUS_UNSUBSCRIBED
)
->select_expr(
'SUM(CASE subscribers.status WHEN "' . Subscriber::STATUS_UNCONFIRMED . '" THEN 1 ELSE 0 END)',
'SUM(CASE WHEN subscribers.status = "' . Subscriber::STATUS_UNCONFIRMED . '"
AND relation.status != "' . Subscriber::STATUS_UNSUBSCRIBED . '" THEN 1 ELSE 0 END)',
Subscriber::STATUS_UNCONFIRMED
)
->select_expr(
'SUM(CASE subscribers.status WHEN "' . Subscriber::STATUS_BOUNCED . '" THEN 1 ELSE 0 END)',
'SUM(CASE WHEN subscribers.status = "' . Subscriber::STATUS_BOUNCED . '"
AND relation.status != "' . Subscriber::STATUS_UNSUBSCRIBED . '" THEN 1 ELSE 0 END)',
Subscriber::STATUS_BOUNCED
)
->whereNull('subscribers.deleted_at')
->findOne()
->asArray();
@ -106,7 +111,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();
@ -227,8 +232,8 @@ class Segment extends Model {
static function bulkTrash($orm) {
$count = parent::bulkAction($orm, function($ids) {
parent::rawExecute(join(' ', array(
'UPDATE `'.self::$_table.'`',
Segment::rawExecute(join(' ', array(
'UPDATE `' . Segment::$_table . '`',
'SET `deleted_at` = NOW()',
'WHERE `id` IN ('.rtrim(str_repeat('?,', count($ids)), ',').')',
'AND `type` = "default"'

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)
@ -446,8 +448,10 @@ class Subscriber extends Model {
'subscribers.id = relation.subscriber_id',
'subscribers'
)
->select('subscribers.id')
->whereNull('subscribers.deleted_at')
->where('subscribers.status', 'subscribed');
->where('subscribers.status', 'subscribed')
->distinct();
return $subscribers;
}
@ -485,6 +489,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) {
@ -716,8 +731,8 @@ class Subscriber extends Model {
static function bulkTrash($orm) {
$count = parent::bulkAction($orm, function($subscriber_ids) {
self::rawExecute(join(' ', array(
'UPDATE `'.self::$_table.'`',
Subscriber::rawExecute(join(' ', array(
'UPDATE `' . Subscriber::$_table . '`',
'SET `deleted_at` = NOW()',
'WHERE `id` IN ('.
rtrim(str_repeat('?,', count($subscriber_ids)), ',')
@ -735,7 +750,8 @@ class Subscriber extends Model {
$count = parent::bulkAction($orm, function($subscriber_ids) {
// delete all subscriber/segment relationships
SubscriberSegment::deleteManySubscriptions($subscriber_ids);
// delete all subscriber/custom field relationships
SubscriberCustomField::deleteManySubscriberRelations($subscriber_ids);
// delete subscribers (except WP Users)
Subscriber::whereIn('id', $subscriber_ids)
->whereNull('wp_user_id')
@ -771,13 +787,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');
}
@ -847,4 +868,18 @@ class Subscriber extends Model {
)
);
}
}
static function findSubscribersInSegments(array $subscribers_ids, array $segments_ids) {
return self::getSubscribedInSegments($segments_ids)
->whereIn('subscribers.id', $subscribers_ids)
->select('subscribers.*');
}
static function extractSubscribersIds(array $subscribers) {
return array_filter(
array_map(function($subscriber) {
return (!empty($subscriber->id)) ? $subscriber->id : false;
}, $subscribers)
);
}
}

View File

@ -89,4 +89,10 @@ class SubscriberCustomField extends Model {
$relations = self::where('subscriber_id', $subscriber->id);
return $relations->deleteMany();
}
static function deleteManySubscriberRelations(array $subscriber_ids) {
if(empty($subscriber_ids)) return false;
$relations = self::whereIn('subscriber_id', $subscriber_ids);
return $relations->deleteMany();
}
}

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

@ -43,7 +43,7 @@ class MetaInformationManager {
// Get categories
$categories = wp_get_post_terms(
$post_id,
get_object_taxonomies($post_type),
array('post_tag', 'category'),
array('fields' => 'names')
);
if(!empty($categories)) {

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

@ -19,7 +19,7 @@ class Button {
style="height:' . $element['styles']['block']['lineHeight'] . ';
width:' . $element['styles']['block']['width'] . ';
v-text-anchor:middle;"
arcsize="' . round($element['styles']['block']['borderRadius'] / $element['styles']['block']['lineHeight'] * 100) . '%"
arcsize="' . round((int)$element['styles']['block']['borderRadius'] / (int)$element['styles']['block']['lineHeight'] * 100) . '%"
strokeweight="' . $element['styles']['block']['borderWidth'] . '"
strokecolor="' . $element['styles']['block']['borderColor'] . '"
fillcolor="' . $element['styles']['block']['backgroundColor'] . '">

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