Compare commits

...

418 Commits

Author SHA1 Message Date
a25ea3ddf6 Bump up release version to 0.0.23 2016-04-09 02:08:05 +03:00
021349caee Merge pull request #415 from mailpoet/public_api_update
Updates query parameters for public API
2016-04-09 02:01:20 +03:00
89f2958d23 - Updates cron to use new public API query parameters 2016-04-08 18:53:57 -04:00
8c8435766e - Updates query parameters for public API 2016-04-08 18:40:45 -04:00
40dd1bbb3b Merge pull request #413 from mailpoet/rendering_engine_update
Rendering engine update
2016-04-08 22:27:48 +03:00
ba40437eb9 - Enables dynamic line-height based on font-size 2016-04-08 15:19:14 -04:00
813db1ae33 - Sets LI bottom margin to 10px 2016-04-08 08:45:09 -04:00
9588397e4e - Moves all styles logic to the StylesHelper class
- Sets margin-bottom on h1-4 to be 0.3*font-size
2016-04-07 19:39:09 -04:00
ecf83ca419 - Disables color compression when tidying CSS 2016-04-07 19:39:09 -04:00
9cc494f0fa - Removes image height HTML attribute and sets "height:auto" style 2016-04-07 19:39:09 -04:00
dd4b7e4d1c - Adds lines heights to LIs 2016-04-07 19:39:09 -04:00
738b2f6c17 - Adds dynamic line breaks to paragraphs when the elment is followed by
list
2016-04-07 19:39:09 -04:00
33289342d3 - Implements dynamic margins on headings 2016-04-07 19:39:09 -04:00
a00f1efcfe - Updates unit test due to button style change 2016-04-07 19:38:15 -04:00
b89897e6d4 - Adds line breaks after headings IF they are followed by paragraph
- Adds line breaks after blocksquotes
- Updates margin on lists
- Adds line breaks after paragraphs IF they are not the last element
2016-04-07 19:38:15 -04:00
c539837896 - Makes button's stroke width = border width 2016-04-07 19:38:15 -04:00
27edf5f71d - Updates list styles 2016-04-07 19:38:15 -04:00
32cc5644f9 - Removes height:auto from images 2016-04-07 19:38:15 -04:00
8a664aa7f1 - Set left text alignment on all elements when alignment is otherwise
absent
2016-04-07 19:38:15 -04:00
7e5e8a4282 - Updates button padding 2016-04-07 19:38:15 -04:00
70d5d609e2 Merge pull request #412 from mailpoet/scheduled_sending
Fixes an issue with welcome emails not being sent to confirmed subscribers
2016-04-07 12:09:30 +03:00
19160c99e1 Merge pull request #411 from mailpoet/send_with_review
Send with review
2016-04-06 18:23:50 +03:00
99b2a7457e Merge pull request #410 from mailpoet/newsletter_send_preview_fix
Fixes an issue with shortcodes not rendering when sending newsletter
2016-04-06 18:05:39 +03:00
945d7edc70 Send with review
- updated MailPoet logo
- added SPF
- hide Dkim for beta
- added warning in case the number of emails/sec is too high
2016-04-06 16:57:10 +02:00
6a97badfed - Fixes an issue with welcome emails not being seint to confirmed
subscribers
2016-04-05 14:34:22 -04:00
5ec8e4ed52 - Fixes an issue with shortcodes not rendering when sending newsletter
preview
2016-04-05 09:54:04 -04:00
1569b5f80a Merge pull request #409 from mailpoet/scheduled_sending
Fixed an issue with newsletter schedule not being saved
2016-04-05 12:10:34 +03:00
e62ecc5036 - Fixes duplicate detection check 2016-04-04 21:09:15 -04:00
b0150e184b - Fixed an issue with newsletter schedule not being saved 2016-04-04 19:41:18 -04:00
8b96854f39 Bump up release version to 0.0.22 2016-04-01 14:57:11 +03:00
28d8600078 Merge pull request #408 from mailpoet/rendering_engine_update
Rendering engine update
2016-04-01 12:37:11 +03:00
4f30158722 - Updates button height calculation
- Removes background color when it's set to "transparent"
- Removes second line break from paragraphs
- Introduces other changes based on Becks's testing
- Updates unit tests
2016-03-31 19:19:58 -04:00
4486f9c5b0 Merge pull request #407 from mailpoet/scheduled_sending
Prevents welcome emails to be sent to unconfirmed subsribers
2016-03-31 14:53:41 +03:00
8515dcf29b Merge pull request #406 from mailpoet/rendering_engine_update
Rendering engine update
2016-03-31 14:05:18 +03:00
d16eb87782 - Prevents welcome emails to be sent to unconfirmed subscribers
- Closes #384
2016-03-30 20:59:55 -04:00
3c77e5d25e - Removes dynamic line-height based on column width
- Updates logic behind removing last element bottom padding
- Properly sets font size for H1-4 tags, header, footer and text
2016-03-30 20:09:06 -04:00
aa1a2a0da9 - Updates button padding 2016-03-30 20:09:06 -04:00
7c9029b227 Bump up release version to 0.0.21 2016-03-25 13:10:37 +02:00
1823bf606a Merge pull request #403 from mailpoet/signup_confirmation_review
Signup confirmation review
2016-03-24 14:43:36 -04:00
525b7fdd65 Merge pull request #402 from mailpoet/scheduled_sending
Scheduled sending
2016-03-24 16:10:56 +02:00
11031d2b56 Remove unused cron translations, fix start and stop button translations 2016-03-24 16:10:39 +02:00
4d45635d03 - Implements "in any role" for WP user welcome e-mails 2016-03-24 10:02:04 -04:00
b81764402b - Updates hooks used to schedule welcome email
- Simplifies cron status message
- Implements newsletter change detection between scheduled queues
- Implements detection for subscribers who unsubscribed from list
- Implements detection for WP users who changed roles
- Updates scheduler worker logic
- Various fixes
2016-03-24 10:02:04 -04:00
947e1150d8 - Removes cron worker hooks as the execution timer can't be persisted
between them
2016-03-24 10:00:52 -04:00
97f0e512af populate from/reply_to for signup confirmation
- added loading screen to reinstall process
2016-03-24 14:22:37 +01:00
f15374de43 don't replace title on custom mailpoet pages 2016-03-24 14:22:37 +01:00
f082c065d1 Confirmation email + Subscription pages
- form as an iframe: increased marginY
- fixed issue with page titles (old themes using wp_title hook)
- redirect on reinstall instead of showing a message (to avoid being able to re-save old settings)
2016-03-24 14:22:37 +01:00
72a9951125 Merge pull request #400 from mailpoet/i18n
Javascript translations
2016-03-24 13:11:51 +01:00
a82d9a63d4 Add missing unsubscribedOn translation on subscribers form 2016-03-24 14:09:35 +02:00
a7e9979781 Merge pull request #398 from mailpoet/rendering_engine_update
Updates rendering engine based on Becs's comments
2016-03-24 13:18:06 +02:00
cebd1ee7ae Merge pull request #399 from mailpoet/editor_rendering
Adapts editor rendering to Becs' feedback
2016-03-23 18:21:00 -04:00
5faa467306 - Updates merge issue with template
- Updates button width/height based on border radius
2016-03-23 18:18:21 -04:00
53eb9cd2ae Translate JS code in listings and forms 2016-03-23 18:59:01 +02:00
7f6eed6d66 Translate cron, forms, newsletters, segments and subscribers 2016-03-23 14:52:06 +02:00
74f3fa65cd Add translatiosn for newsletter creation and listings 2016-03-23 14:52:06 +02:00
8723aa4e4e Remove obsolete test file 2016-03-23 14:52:06 +02:00
ccab8b4cf3 Add MailPoet.I18n for basic translation handling, removed MailPoetI18n 2016-03-23 14:52:06 +02:00
4c4a4ab31d Reduce max button width to 288px, max button border to 10px 2016-03-23 14:09:19 +02:00
45df02b0ec Merge pull request #395 from mailpoet/many_improvements
Many improvements
2016-03-23 13:38:40 +02:00
dd7067e590 Merge pull request #397 from mailpoet/scheduled_sending
Updates scheduling logic to work with the new normalized DB time
2016-03-23 11:51:58 +02:00
1219b5ba49 Merge pull request #396 from mailpoet/timezone_fix
Normalizes time difference between WP and database
2016-03-23 11:45:42 +02:00
01496ac813 Merge branch 'scheduled_sending' of mailpoet:mailpoet/mailpoet into scheduled_sending 2016-03-22 20:55:42 -04:00
3b3ccc18ce - Updates scheduling logic to work with the new normalized DB time 2016-03-22 20:43:16 -04:00
3dce951e66 - Updates rendering engine based on Becs's comments
- Prevents removing of side padding
- Enables font size on output
- Sets line-height in footer
- Adds image height:auto
- Adds mobile styles for buttons
- Updates style sheet
2016-03-22 20:21:39 -04:00
db1dc172aa - Updates scheduling logic to work with the new normalized DB time 2016-03-22 18:26:37 -04:00
26c5cc1e43 - Normalizes time difference between WP and database 2016-03-22 13:05:41 -04:00
f91bfbf473 handle form as iframe 2016-03-22 17:25:25 +01:00
3dae0ef13f increased page input in listings 2016-03-22 17:25:25 +01:00
3281ac390e Improved segment selection errors in form editor
- improved error display to make it more obvious (added border on select2 on error)
2016-03-22 17:25:25 +01:00
d731a6b432 toggle segment selection validation on form editor 2016-03-22 17:25:25 +01:00
bb869e8ae8 Fixed setWindowTitle for WP version < 4.4
- fixed variable name Env::temp_url (instead of temp_URL)
- updated cache folder to be in the temp folder (uploads) instead of views (within plugin)
2016-03-22 17:25:25 +01:00
7331e5cabd Merge pull request #392 from mailpoet/shortcodes_update
Updates shortcodes logic
2016-03-21 17:58:38 +02:00
ba05ca35af - Implements shortcodes rendering in subject line 2016-03-21 11:45:08 -04:00
91e7bf6336 Merge branch 'shortcodes_update' of mailpoet:mailpoet/mailpoet into shortcodes_update 2016-03-21 10:12:10 -04:00
ff58067d55 Change array_column to Helpers::arrayColumn method 2016-03-21 16:07:20 +02:00
2ba6bb339e Fix newsletter options format for newsletters 2016-03-21 15:50:48 +02:00
a47afdd313 - Fixes queue worker issue 2016-03-21 09:09:43 -04:00
608b559ee1 - Minor adjustment to the shortcodes logic 2016-03-21 09:09:43 -04:00
181ed45d0b - Updates shortcodes logic
- Implements [newsletter:total] and [newsletter:number] shortcodes
- Implements shortcode replacement in subject line
- Updates unit tests
Issue #380
2016-03-21 09:09:43 -04:00
29fac8d052 Merge pull request #394 from mailpoet/scheduled_sending
Updates scheduled sending
2016-03-21 15:06:30 +02:00
a3d7d53eea - Fixes queue worker issue 2016-03-21 08:58:51 -04:00
3f6caf5fa4 - Implements scheduler worker for welcome and post notifications
- Updates sending queue worker to save rendered newsletter body
- Updates sending queue router to schedule post notification newsletters
2016-03-20 22:01:01 -04:00
42c4139ba5 - Minor adjustment to the shortcodes logic 2016-03-20 11:37:59 -04:00
ad31b143d2 - Updates scheduler worker to process queued newsletters 2016-03-19 12:29:10 -04:00
6ec15bec22 - Updates shortcodes logic
- Implements [newsletter:total] and [newsletter:number] shortcodes
- Implements shortcode replacement in subject line
- Updates unit tests
Issue #380
2016-03-19 11:19:22 -04:00
71d8fb0d93 Bump up version to 0.0.20 2016-03-18 19:40:11 +02:00
2f293da7a3 Merge pull request #391 from mailpoet/scheduled_sending
Scheduled sending
2016-03-18 18:49:47 +02:00
17b56f0160 - Fixes scheduling issues
- Fixes unit test
- Updates as per code review comments
2016-03-18 12:11:38 -04:00
bb9fce7f82 - Implements post notification scheduling 2016-03-18 11:15:31 -04:00
ad4f1f8326 Merge pull request #390 from mailpoet/newsletter_footer_links
Newsletter footer links
2016-03-18 16:10:17 +02:00
8ece62c9a6 fix tests 2016-03-18 14:58:33 +01:00
a9b9e9c631 Updated tests in order to fix WP related issues 2016-03-18 14:30:59 +01:00
6e289b6a8f - Implements welcome e-mail scheduling 2016-03-17 11:22:29 -04:00
20ced8b099 replace mailpoet_title 'hack' by checking the post type 2016-03-17 15:48:41 +01:00
f38b632707 properly handle custom subscriptions pages 2016-03-17 15:48:41 +01:00
8ce0595342 turned static values into constants 2016-03-17 15:48:06 +01:00
f11de2f1ad Updated shortcodes for unsubscribe/manage/browser links
- fixed all issues in #387 except the custom mailpoet pages
2016-03-17 15:48:06 +01:00
e28451d410 removed edit page in settings + manage subscription update 2016-03-17 15:45:05 +01:00
72882aaf2b fixed shortcodes replacement for "global:" in newsletters
- extracted "get subscription pages urls" from models\Subscriber
- added unit tests for subscription\url class (not working because of WP/Codeception issue)
2016-03-17 15:45:05 +01:00
fdf9dd0fa3 Merge pull request #386 from mailpoet/editor_refactor
Editor refactor
2016-03-17 15:19:04 +01:00
97dd0abea2 fixed makepot task
- added proper grunt module (grunt-cli)
- updated translations extract code to remove warnings when no translations are found
2016-03-17 15:08:38 +01:00
a099174226 Revert "Extract text labels from React code for translation in twig views"
This reverts commit 9aef6850c2.

Conflicts:
	views/newsletters.html
2016-03-17 15:24:07 +02:00
a054acc6e6 Merge pull request #389 from mailpoet/import_xss_update
Updates import to santize user input
2016-03-17 12:02:58 +02:00
74254d7e2a - Updates import to santize user input 2016-03-15 13:06:21 -04:00
1795964c69 - Updates composer dependencies 2016-03-15 12:10:09 -04:00
0118b2472a Fix loading of makepot task for pot translation file generation 2016-03-10 19:24:35 +02:00
5fe03f0dee Add event selection explanation to welcome email type 2016-03-10 17:49:06 +02:00
9aef6850c2 Extract text labels from React code for translation in twig views 2016-03-10 17:08:40 +02:00
f688a69f8b Fix translations to be injected at config time 2016-03-07 17:52:14 +02:00
b2682fa0b7 Remove console.log statements from newsletter editor 2016-03-07 16:57:20 +02:00
18b15c5440 Restructure JS loading to not duplicate JS asset loading 2016-03-07 16:15:26 +02:00
c3416977bb Replace editor translations with Twig localize function 2016-03-07 15:42:12 +02:00
88a00bc38c Rename editor form.html to editor.html 2016-03-07 14:42:12 +02:00
a1441dfde6 Bump up version to 0.0.19 2016-03-04 18:43:22 +02:00
8e7336d352 Merge pull request #373 from mailpoet/editor_rendering
Editor rendering (Part 2)
2016-03-04 11:11:42 -05:00
e0e2933cdf Merge pull request #379 from mailpoet/scheduled_sending
Fix mistyped method name
2016-03-04 11:06:40 -05:00
c93ec629ea Fix mistyped method name 2016-03-04 18:03:02 +02:00
d613df6558 Merge pull request #378 from mailpoet/scheduled_sending
Prepares codebase for future scheduled sending
2016-03-04 17:58:59 +02:00
01c9096543 - Rewrites hooks to not use closures 2016-03-04 10:47:55 -05:00
f120e839dd Merge pull request #375 from mailpoet/export_large_dataset_fix
Export update
2016-03-04 14:12:20 +02:00
f0ab592c04 Merge pull request #376 from mailpoet/rendering_serverside_update
Removes padding from the last column element
2016-03-04 13:30:31 +02:00
1a269d28b3 Merge pull request #374 from mailpoet/edit_subscription
edit subscription form + save
2016-03-04 12:54:56 +02:00
8d4a666bf0 added logged out handling of subscriber save request 2016-03-04 11:52:06 +01:00
4a96e483a6 edit profile rendering (missing segments list) + fallback for Url::redirectBack() 2016-03-04 11:20:17 +01:00
263a66407f - Removes mailpoet_padded class from the last element in a column 2016-03-03 19:13:50 -05:00
c4b728f4e1 - Updates Daemon to execute workers via WP's action hook
- Bootstraps Scheduler class
2016-03-03 15:42:10 -05:00
9970ad7fb6 Merge pull request #369 from mailpoet/manage_subscription
Manage subscription (part 2)
2016-03-03 15:33:56 -05:00
eb380499d9 - Rewrites export to support large datasets
- Updates unit tests
- Fixes an issue with export UI not displaying subscribers without segment
2016-03-03 14:33:10 -05:00
4b528549f5 edit subscription form + save 2016-03-03 15:57:42 +01:00
a903017bc2 Update "Please include unsubscribe link" message 2016-03-03 15:48:59 +02:00
afd25e9174 Remove old and unused editor template 2016-03-03 15:39:46 +02:00
b1bbf1b3bc Add default email for newsletter preview, disallow empty email 2016-03-03 12:59:49 +02:00
9e84e8df93 Show loading icon when loading newsletter preview 2016-03-02 16:57:04 +02:00
df775b5a07 Set font-style for paragraph and headings 2016-03-02 16:57:04 +02:00
c9c22b9b52 Switch 'Comic Sans' to 'Comic Sans MS' 2016-03-02 16:57:04 +02:00
93d20688ea Prevent heading line height and font weight from being overridden 2016-03-02 16:57:04 +02:00
9ebcddfa2a Update button defaults 2016-03-02 16:57:04 +02:00
85995bc8a6 Merge pull request #369 from mailpoet/manage_subscription
Manage subscription (part 2)
2016-03-02 16:42:12 +02:00
a8f2959bc6 added Subscriber::generateToken() 2016-03-02 15:07:37 +01:00
36242bd580 Fixed sending confirmation email to new subscribers (first time)
- fixed newsletters listing issue with queue
2016-03-02 13:11:06 +01:00
1e7dbc8449 refactor pages 2016-03-01 18:27:31 +01:00
12c159c627 bugfix + refactoring 2016-03-01 16:23:15 +01:00
82ed7e51c5 Replaced "contains" by "indexOf" (chrome issue)
- added public ajax routing (not checking permissions)
- exception handling in form subscription
2016-03-01 13:18:36 +01:00
0b3aa0d12a Merge pull request #370 from mailpoet/import_batch_update
Import batch size increase
2016-03-01 12:10:46 +02:00
4d788f69aa - Updates batch size to 2000 2016-02-29 11:41:17 -05:00
c721843c12 extracted subscription pages code from router 2016-02-29 13:34:17 +01:00
d2be407ccb rendering of edit subscription form 2016-02-29 13:34:17 +01:00
e6337216cf improved demo view of subscription pages 2016-02-29 13:34:17 +01:00
f1c396f0b0 basic implementation of confirm/edit/unsubscribe pages 2016-02-29 13:34:17 +01:00
3622bc9fcb fixed sending issue of confirmation email 2016-02-29 13:34:17 +01:00
14fe333678 Send confirmation email + page 2016-02-29 13:34:17 +01:00
cf6466197a Fixed Subscribers' bulk actions when filtering by a segment
- filter by segment is now affected by the selected group (all, trash,...)
- updated relationship methods between subscribers & segments (to account for subsegment status)
2016-02-29 13:34:17 +01:00
f56bee76f2 MailPoet.Date to handle localized dates and times 2016-02-29 13:34:17 +01:00
2ec6bc8c99 Update release version to 0.0.18 2016-02-26 16:57:42 +02:00
4aeccb1961 Merge pull request #368 from mailpoet/rendering_serverside_update
Rendering serverside update
2016-02-26 16:18:11 +02:00
bd593b1ad4 - Updates button/spacer rendering and unit tests 2016-02-26 09:05:43 -05:00
bb7812bd5d Merge pull request #366 from mailpoet/newsletter_rendering
Newsletter rendering
2016-02-25 21:14:16 -05:00
73ed070a34 - Formats code 2016-02-25 11:32:56 -05:00
9e81c48bf8 - Adds new 'fontWeight' property
- Limits max button width to column width
- Adds bold option to buttons
2016-02-25 11:32:09 -05:00
f9028d28c0 - Adds background color to spacer 2016-02-25 11:31:38 -05:00
da32b243ea Change header, footer and text padding based on Becs' feedback 2016-02-25 16:29:55 +02:00
5092c3d328 Reduce ALC post refresh timeout to 0.5s instead of 2s 2016-02-25 16:29:55 +02:00
06ad4488bf Homogenize ALC and Posts output, wrap Read More text in a paragraph tag 2016-02-25 16:29:55 +02:00
a856800e6d Set line height multiplier to golden ratio for editor text blocks 2016-02-25 16:28:32 +02:00
dfc680f3a1 Do not rerender header and footer blocks on content change 2016-02-25 16:28:32 +02:00
a9baecc504 Specify encoding for newsletter previews in browser to fix entity issue 2016-02-25 16:28:32 +02:00
11e15659ac Merge pull request #365 from mailpoet/export_fix
Export update
2016-02-25 15:51:52 +02:00
ebe440a272 Remove debugging statement 2016-02-25 15:51:25 +02:00
853794d459 Merge pull request #364 from mailpoet/phpmail_fix
Fixes localhost sending
2016-02-25 15:20:30 +02:00
cfea13bf81 Merge pull request #363 from mailpoet/alc_posts_polishing
Alc posts polishing
2016-02-25 14:27:44 +02:00
7f291d80b9 - Updates UI based on @rafaehlers review comments
- Closes #323
2016-02-24 21:54:33 -05:00
9840b55de6 - Adds unit test for image link 2016-02-24 19:16:48 -05:00
ca7322933f - Fixes issue with incorrect transport being used for localhost sending 2016-02-24 11:56:29 -05:00
81569e5b81 Fix PHP code style 2016-02-23 15:37:00 +02:00
1942972282 Change title position to featured image position 2016-02-23 15:26:12 +02:00
a23aac370c Add optional links to image rendering 2016-02-23 13:07:52 +02:00
99d6f74d1b Change inline form fields to not be inline 2016-02-23 13:07:52 +02:00
a883e1176c Merge pull request #359 from mailpoet/import_batch_processing
Import update
2016-02-23 13:04:48 +02:00
24b98a1154 Move jquery.asyncqueue.js to assets/js/src/vendor 2016-02-23 12:49:11 +02:00
8dbb6ab79f - Updates based on code review comments 2016-02-22 11:54:31 -05:00
3e7d1690bd Merge pull request #360 from mailpoet/manage_subscription
Manage subscriptions (part 1)
2016-02-22 17:05:24 +02:00
07d533a810 Manage subscriptions
- make use of the SubscriberSegment::status column to keep track of unsubscriptions
- unsubscribed segments now appear grayed out in the Subscribers listing
- added unsubscribed_at after segment names when editing a subscriber
- added date() method for Twig (uses WP's date format / date_offset)
- fixed typo in Form iframe export
- fixed unit test for Newsletters
- updated selection component (JSX) to allow more customization
2016-02-22 11:35:34 +01:00
499936e3ab - Removes file size limit in import
- Implements chunked import processing
- Updates tests/migrator/Subscriber model
2016-02-20 18:55:34 -05:00
580ac989aa Bump up version to 0.0.17 2016-02-19 15:35:27 +02:00
acf300160d Merge pull request #356 from mailpoet/page_reviews
Page reviews
2016-02-18 15:34:10 +02:00
983df4ee13 Merge pull request #355 from mailpoet/editor_polishing_3
Editor polishing 3
2016-02-18 07:45:20 -05:00
03c782d4ab Fixed issue #305
- added validation on add/edit Custom Field for multiple values (select/radio)
- disabled Remove link when only one value remains
- Added confirmation message on Reinstall
- Added missing period in Import
2016-02-18 13:14:57 +01:00
0daf7e12c1 remove logging function (polluting unit tests) & bugfix on model subscriber addToSegments 2016-02-18 13:13:19 +01:00
6a2e18a0e1 fix segments being reset on Subscriber::createOrUpdate() 2016-02-18 13:13:19 +01:00
316d5ab183 fixed unit tests 2016-02-18 13:13:19 +01:00
eb6bba5961 Merge pull request #351 from mailpoet/import_language_update
Updates error messages displayed during import
2016-02-18 09:55:59 +01:00
b5864adf06 - Fixes writable path check 2016-02-17 11:47:20 -05:00
9bce50a633 Fix "Full width" image option 2016-02-17 15:10:51 +02:00
365a53cf27 Merge pull request #353 from mailpoet/subscribers_page_review
Subscribers page review
2016-02-17 14:28:36 +02:00
31a4575d43 replaced closure by callbacks 2016-02-17 13:02:35 +01:00
1ae584c4e7 Restyle ALC Post number/type selector, limit to 2 character input 2016-02-17 13:29:16 +02:00
aac2cd6eb8 Add button "Bold" text option, fix unit tests 2016-02-17 12:25:03 +02:00
9874e1c371 Remove 1px left black border from newsletter thumbnails 2016-02-17 12:25:03 +02:00
16b1c0dc41 Convert mailpoet buttons to WP buttons in newsletter editor 2016-02-17 12:25:03 +02:00
9223fbf478 Display loading animation when listing newsletter templates 2016-02-17 12:25:03 +02:00
eb27de36f4 Select active block format and allow custom colors in TinyMCe 2016-02-17 12:25:03 +02:00
636fa38ab6 - Enables check for writable export file 2016-02-16 17:35:20 -05:00
9b1503dc7a Fixed reported issues + refactoring
- refactored Config/Hooks to make it more readable
- added hook to save limit per page
- added default limit per page as a constant in Listing/Handler
2016-02-16 16:33:20 +01:00
2ac3b00af6 Merge pull request #354 from mailpoet/parsley_firefox_fix
fixed parsley issue with firefox
2016-02-16 12:32:29 +02:00
5fcdbfe826 fixed parsley issue with firefox 2016-02-15 21:19:21 +01:00
67036ddb61 cleanup and bugfix on bulk actions 2016-02-15 15:50:47 +01:00
6c0f6a07cd removed useless constant 2016-02-15 15:40:21 +01:00
8139a7dd0a Subscribers page review
- added screen option to set number of items per page
- improved bulk actions in order to handle large sets
2016-02-15 15:40:21 +01:00
97adfc14c0 Bump up version to 0.0.16 2016-02-15 14:50:13 +02:00
4ed703a351 Merge pull request #352 from mailpoet/exception_fix
Fixes remaining exception namescape issues
2016-02-15 13:39:18 +02:00
2aee853406 - Fixes remaining exception namescape issues 2016-02-13 21:39:55 -05:00
854736fac7 - Updates error messages 2016-02-12 14:12:14 -05:00
841c69af59 Merge pull request #348 from mailpoet/page_reviews
Page reviews
2016-02-12 13:36:47 -05:00
56b4688f93 updated unit tests for segments 2016-02-12 19:29:30 +01:00
e60bc7c387 handle duplicates in model 2016-02-12 19:24:04 +01:00
6094a83f4b Merge pull request #350 from mailpoet/rendering_engine_image_update
Updates logic behind image dimensions based on column width
2016-02-12 19:14:49 +02:00
91ddb98f56 Merge pull request #349 from mailpoet/exception_namescape_fix
Fixes namespace issue when catching exceptions
2016-02-12 19:11:25 +02:00
27d5972306 - Updates logic behind image dimensions based on column width 2016-02-12 12:05:27 -05:00
6088497433 Bump up release version to 0.0.15 2016-02-12 18:25:12 +02:00
0d894a6fef - Fixes namespace issue when catching exceptions 2016-02-12 11:20:43 -05:00
57f0b88299 Merge pull request #347 from mailpoet/sending_frequency
Implements sending frequency
2016-02-12 14:23:22 +02:00
5121dbe0c8 Form Editor Round 3
- added prefix to form styles so that it does not conflict when multiple forms are on the same page
- added validation on form save for segments
2016-02-12 12:45:07 +01:00
f874ffc19c Merge pull request #346 from mailpoet/rendering_engine_image_update
Updates image rendering & unit test
2016-02-12 12:42:49 +02:00
e928a5c2bc Segments page review
- remove edit link for WordPress users list
- hide trashed segments from Import
- fixed display issue in listing's item actions
2016-02-12 11:30:08 +01:00
d11badf3ce - Implements sending frequency
- Updates sending queue worker
2016-02-11 22:46:45 -05:00
3006c982cb - Updates image rendering & unit test 2016-02-11 21:28:43 -05:00
b29e31fdd6 Merge pull request #344 from mailpoet/router_update_sending_queue
Sending queue router update
2016-02-11 11:31:49 -05:00
bc42c8e280 Merge branch 'router_update_sending_queue' of mailpoet:mailpoet/mailpoet into router_update_sending_queue 2016-02-11 11:30:08 -05:00
409697ee64 Sending queue router update
- cleaned up useless code
- bugfixes
- improved code coverage
2016-02-11 11:30:01 -05:00
cfb4265971 Merge pull request #343 from mailpoet/queue_worker_rewrite
Sending queue worker rewrite
2016-02-11 18:16:07 +02:00
13d78aac05 Merge branch 'queue_worker_rewrite' of mailpoet:mailpoet/mailpoet into queue_worker_rewrite 2016-02-11 10:37:26 -05:00
6f176f4e6c - Updates MailChimp unit test 2016-02-11 10:34:24 -05:00
9b584296a5 - Updates queue worker based on code review comments 2016-02-11 10:23:41 -05:00
5ea87c5eed Sending queue router update
- cleaned up useless code
- bugfixes
- improved code coverage
2016-02-11 09:37:53 +01:00
7522084ccb - Rewrites sending queue worker and updates router
- Implements batch sending for queue worker
- Fixes mailer class issue when sender data can be empty
- Updates values for cron execution timeout/limit
2016-02-10 22:34:54 -05:00
214aa60d0e Merge pull request #338 from mailpoet/editor_polishing_2
Change `padded` image attribute to `fullWidth`
2016-02-10 22:34:36 -05:00
7a049ce1b7 - Rewrites sending queue worker and updates router
- Implements batch sending for queue worker
- Fixes mailer class issue when sender data can be empty
- Updates values for cron execution timeout/limit
2016-02-10 22:32:39 -05:00
94d293deb7 Merge pull request #339 from mailpoet/mailchimp_update
Updates MailChimp class and unit test
2016-02-09 18:04:43 +01:00
bd2d38d757 updated MP.Notice in order to handle arrays as error messages 2016-02-09 17:58:40 +01:00
abec524daa Merge pull request #340 from mailpoet/fix_registration_in_comments
Subscribe in comments
2016-02-09 18:41:58 +02:00
cac6beb4ac - Fixes display of error messages 2016-02-09 11:26:00 -05:00
cac995e15b fixed subscribe in comments 2016-02-09 16:55:00 +01:00
b26380fd10 - Updates MailChimp class and unit test 2016-02-09 10:03:29 -05:00
6c6a4070be Remove obsolete attribute from server response 2016-02-09 15:47:56 +02:00
8b001d820b Change padded image attribute to fullWidth 2016-02-09 15:26:06 +02:00
2ae3d8ebdf Merge pull request #337 from mailpoet/fix_listing_pagination
Pagination issues
2016-02-09 12:23:39 +02:00
459ec21f9d fixed pagination issues 2016-02-08 17:11:11 +01:00
9c7790d07e Merge pull request #336 from mailpoet/listing_empty_trash
Listing update + Unit tests
2016-02-08 16:01:42 +02:00
f9c5b99e46 updated Subscriber:: -> self:: in Models\Subscriber 2016-02-08 14:43:59 +01:00
95b0b39366 Fixed bulk_action success messages
- uptaded Subscriber & Segment routers' test
- moved add/remove segments logic to Subscriber::createOrUpdate
- fixed Router\Segments save not returning errors
2016-02-08 13:36:35 +01:00
4b6fa0e760 Added "Subscribers without a segment" filter
- removed useless dependency in filters.jsx
- fixed issue with Router\Subscribers::save() not updating segments
2016-02-08 10:32:06 +01:00
67a3440ced updated router unit tests - refactor + test bulk delete 2016-02-08 10:10:43 +01:00
0de372344a fixed bulkDelete 2016-02-06 15:17:19 +01:00
7a04eeb650 Listing: Empty trash button 2016-02-06 15:15:07 +01:00
8dbfe82922 Bump version up to 0.0.14 2016-02-05 17:34:53 +02:00
7322f2151c Merge pull request #334 from mailpoet/unit_tests_update
Enables conditional bypassing of unit tests
2016-02-05 16:38:31 +02:00
c43d2f240d - Updates MailChimp test and .env.sample 2016-02-05 09:35:32 -05:00
bbcd267b6f - Updates .env.sample with available options for unit tests 2016-02-05 09:00:28 -05:00
bbc4acb2a4 Merge pull request #335 from mailpoet/mailer_class_Fix
Fixes detection of reply_to address
2016-02-05 12:52:39 +02:00
c89cc5a919 Merge pull request #333 from mailpoet/test_email_fix
Test email fix
2016-02-05 12:19:40 +02:00
33075940de - Fixes detection of reply_to address 2016-02-04 19:22:11 -05:00
51c09b8360 Merge pull request #325 from mailpoet/router_unit_tests
Unit tests (Router\NewsletterTemplates & Router\Segments)
2016-02-04 18:59:09 -05:00
2fbf85f371 - Catches exception returned by mailer class when sender is not configured 2016-02-04 18:57:07 -05:00
24cb614adb - Enables conditional bypassing of unit tests 2016-02-04 18:41:18 -05:00
55f851208b Major update of unit tests / updated routers + models + react 2016-02-04 19:04:52 +01:00
990dac7727 Merge pull request #332 from mailpoet/editor_fixes
Editor bug fixes
2016-02-04 09:02:21 -05:00
233020ca20 fix unit tests 2016-02-04 14:38:25 +01:00
0c419cde16 Unit tests (Router\NewsletterTemplates & Router\Segments
- moved json_decode(body) inside NewsletterTemplate->asArray (override)
- updated NewsletterTemplate router accordingly
- finished Segments unit test
2016-02-04 14:36:08 +01:00
35f9530d8e Merge pull request #326 from mailpoet/router_unit_tests_2
More Unit Tests + Initializer fix
2016-02-04 08:24:15 -05:00
992fe2a6e9 Fix preventing dragging by settings/delete block tools 2016-02-04 13:47:46 +02:00
c2cb88f995 Unit tests fixed + models & routers update 2016-02-04 11:41:05 +01:00
ba69d659ab Changed editor to not send 'last_modified', let server pick one 2016-02-03 18:21:30 +02:00
397d988eb1 Allow block dragging only with "Move" tool, but not with others 2016-02-03 18:21:30 +02:00
d85f2341ec Change Posts/ALC to use MailPoet specific image size 2016-02-03 18:21:30 +02:00
2b3c288b5f Merge pull request #330 from mailpoet/template_footer_fix
Removes footer text/link color overrides for coffee shop template
2016-02-03 11:19:43 -05:00
8ec28a23a7 Removes footer text/link color overrides for coffee shop template 2016-02-03 15:04:18 +02:00
12c9623e2f Better Error handling for models
- added (array)getErrors() to models, returns false if no errors
- converted Forms::saveEditor method to use getErrors
- added error handling on the form editor view
2016-02-03 12:23:42 +01:00
8ba9fdccbc fix for initializer - widget is now registered 2016-02-03 10:42:56 +01:00
a2ef62302f More Unit Tests + Initializer fix
- added unit test for Router\Forms
- updated unit test for Model\Segment to reflect changes
2016-02-02 17:22:11 +01:00
24ecc879d3 Merge pull request #319 from mailpoet/php53-fix
Updates code to work with PHP 5.3
2016-02-02 13:25:23 +01:00
a1104a7f90 Merge pull request #320 from mailpoet/sending_queue_worker_fix
Sending queue worker fix
2016-02-01 16:32:42 +02:00
aa959810e9 Merge pull request #322 from mailpoet/code_coverage
Code coverage report
2016-02-01 16:31:53 +02:00
45b7a79277 Merge pull request #321 from mailpoet/router_upgrade_3
Router upgrade 3
2016-02-01 15:41:13 +02:00
41c8c0dae5 Code coverage report 2016-02-01 14:07:54 +01:00
2aeab7aaff Router Upgrade #3 & bugfix on Cron/Supervisor
- ALC
- Cron
- ImportExport
- Mailer
- Newsletters (only get method for consistency with other router get methods)
- Permissions
2016-02-01 13:00:11 +01:00
4fd0c4b484 Router updates + unit tests + React
- added -f flag to run unit test command in order to fail fast
- pass only id to "$endpoint->get($id)" in React forms instead of array
- updated routers according to the ->get($id) change
- refactored a bit the way form creation works
- added unit tests for Segments router
2016-02-01 11:56:21 +01:00
d4623cf763 Router update for Settings and Setup + unit tests 2016-02-01 11:56:21 +01:00
181c4fed08 - Fixes an issues with the sending queue worker throwing and error when
newsletter is not found
2016-01-31 21:35:34 -05:00
7884dd8389 - Updates code to work with PHP 5.3. Closes #307 2016-01-31 14:02:57 -05:00
b577d33414 Merge pull request #318 from mailpoet/minor_cron_update
Cron update
2016-01-29 22:03:55 +02:00
70de0a01bf - Moves cron timeout/execution limit to the central cron helper class 2016-01-29 15:01:10 -05:00
3b7f77d9af Bump up version to 0.0.13 2016-01-29 21:47:56 +02:00
21847ca875 Merge pull request #315 from mailpoet/sending_queue_worker_fix
Sending worker newsletter processing fix
2016-01-29 21:07:17 +02:00
6153316047 Merge pull request #314 from mailpoet/cron_loop_fix
Cron refactoring
2016-01-29 21:06:35 +02:00
32f8f07602 - Refactors and fixes issues identified during code review 2016-01-29 13:30:13 -05:00
70a04d9bf6 Merge pull request #317 from mailpoet/router_upgrade_2
CustomFields (Router update + Form block renaming + Unit tests)
2016-01-29 18:45:29 +02:00
bb1cc997cc CustomFields
- renamed form block type "input" to "text" for consistency with React
- updated CustomFields router to comply with main router
- unit tests for CF router
2016-01-29 17:16:24 +01:00
24f96d9d7d Merge pull request #313 from mailpoet/router_upgrade
Main Router update + Subscribers router update + Unit test
2016-01-29 17:01:21 +02:00
46c7332da2 Merge pull request #316 from mailpoet/sendgrid_mailer_fix
SendGrid mailer update
2016-01-29 12:53:44 +02:00
2f42f643ab - Fixes message body construction 2016-01-28 22:29:47 -05:00
63c87f3746 - Fixes issue with the sending worker failing to process newsletters 2016-01-28 21:40:30 -05:00
d4d575cda4 - Refactors cron supervisor/daemon/router 2016-01-28 21:38:23 -05:00
2cf03ec0a3 - Fixes cron HTTP request loop issue 2016-01-28 12:50:12 -05:00
72ad98a77f unit test for router Subscribers 2016-01-28 18:00:55 +01:00
b5094f568c Merge pull request #312 from mailpoet/mailpoet_bridge_update
MailPoet mailer update
2016-01-28 16:50:47 +02:00
7d224274fc - Rebases master & fixes some code 2016-01-28 09:40:57 -05:00
f3b9f7be92 - Updates MailPoet mailer to support batch sending
- Fixes message encoding issue
2016-01-28 09:40:57 -05:00
6e74f82ace Merge pull request #311 from mailpoet/mailer_tests_update
Disables send method for all mailer tests
2016-01-28 14:20:56 +02:00
831eb6af44 Merge pull request #310 from mailpoet/fix_install
Fix init/hooks so that we properly initialize db/plugin
2016-01-28 13:51:22 +02:00
1e8e5aecee Remove space after if 2016-01-28 13:50:58 +02:00
c0f98c9ba6 - Moves credentials from mailer tests to ENV file
- Disables send method by default unless enabled in ENV file
2016-01-27 18:36:38 -05:00
746c19d6ed - Resolves an issue with cron not starting 2016-01-27 14:15:00 -05:00
894a9e8c90 Router\Router is now taking care of outputting JSON
- converted Subscribers Router to return array instead of wp_send_json()
- added unit test for Subscribers Router
2016-01-27 17:23:30 +01:00
8fea917337 Merge pull request #308 from mailpoet/mailer_updates
Update to mailer methods
2016-01-27 14:13:37 +02:00
c60425afb2 Fix init/hooks so that we properly initialize db/plugin
- Added activation hook in main file (mailpoet.php)
- auto deactivate plugin in case of fatal errors during init
2016-01-27 12:52:40 +01:00
0776e9ad73 - Adds "reply to" option to all mailers
- Replaces WPMail with Swift using local transport (PHP mail)
- Fixes AmazonSES region naming convention
- Updates tests
2016-01-26 19:08:02 -05:00
91981cc324 Merge pull request #306 from mailpoet/mailer_cleanup
Removes Mandrill API mailer method
2016-01-26 18:52:10 +02:00
1906fafacb Merge pull request #301 from mailpoet/shortcodes_implementation
Shortcodes implementation
2016-01-26 18:32:15 +02:00
c11d95b402 - Refactors code 2016-01-26 11:29:56 -05:00
b4c8fe6f45 - Updates newsletter router and sending queue worker to work with the
shortcodes implementation
2016-01-26 10:44:18 -05:00
d0e770e0fc - Removes Mandrill API mailer method 2016-01-26 09:18:48 -05:00
3d6d1a4282 - Disables shortcodes unit test 2016-01-26 09:14:12 -05:00
dc3b47db00 - Implements shortcodes
- Updates newsletter router/sending queue worker to use shortcodes
  replacement class
2016-01-26 09:13:29 -05:00
900d6694e2 Merge pull request #302 from mailpoet/parsley_issue
removed parsley validation on step 3
2016-01-26 15:50:31 +02:00
e8074a61a5 Merge pull request #298 from mailpoet/text_block_rendering_fix
Text block formatting fix
2016-01-26 12:51:07 +02:00
64501a914a removed parsley validation on step 3
- fixed placeholder for select2 instances in settings
- fixed issue in sending queue worker when newsletter does not exist
2016-01-26 11:36:20 +01:00
de70e855ad - Replaces regex with DOM queries 2016-01-25 21:09:16 -05:00
03e3b5a94b - Fixes regex that formats heading tags 2016-01-25 21:08:16 -05:00
3331bed31c Merge pull request #300 from mailpoet/text_rendering
Text rendering
2016-01-25 18:16:16 +02:00
8c5a33a0fe Merge pull request #297 from mailpoet/alc_rendering
Automated latest content rendering
2016-01-25 17:35:51 +02:00
01eb6c7a98 - Refactors ALC 2016-01-25 09:27:51 -05:00
f502e0b677 - Implements text rendering
- Updates tests
- Updates newsletter router and sending queue worker to reflect changes to
  the renderer
2016-01-22 22:07:02 -05:00
a6b64a1c5d - Implements automated latest content rendering 2016-01-22 19:59:50 -05:00
5019131b21 Bump up version to 0.0.12 2016-01-22 16:44:09 +02:00
da483fb88f Merge pull request #296 from mailpoet/form_custom_fields
Form custom fields
2016-01-22 14:56:16 +02:00
788bed4622 moved const definition inside render method 2016-01-22 13:45:52 +01:00
3fbe5423d0 added constant for years range + added comment for month quirk 2016-01-22 13:38:43 +01:00
8357295be2 Merge pull request #295 from mailpoet/import_review_fixes
Various fixes based on Rafael's import review comments
2016-01-22 12:28:52 +02:00
8072b162d4 Unit tests for new methods in model subscriber 2016-01-22 11:28:26 +01:00
3f2f0ec1a9 Merge pull request #294 from mailpoet/mailchimp_import_fix
Fixes MailChimp import error
2016-01-22 12:20:44 +02:00
5a5a777b7d Added check for when the custom field doesn't exist
- suppressed cron supervisor error
2016-01-22 11:02:48 +01:00
6cac7f3652 Saving of date custom fields in React & PHP 2016-01-22 10:45:33 +01:00
6a9313107c - Fixes unit test 2016-01-21 12:21:22 -05:00
72c9d301b7 - Fixes issue with file extension warning
- Removes duplicate notices
- Updates Twig's localize function to escape double quotes
2016-01-21 12:08:51 -05:00
ad925de801 Custom fields (in Form & Edit subscriber) 2016-01-21 17:27:34 +01:00
1da28b7299 - Enforces CSV file extension during import file selection
- Updates "no records found" error message
2016-01-20 15:45:56 -05:00
e837ad7014 - Fixes MailChimp import error 2016-01-20 13:54:20 -05:00
daec56191f Save custom fields on subscribe
- added methods to get/set a specific custom field
- added method to get all custom fields (and assign each custom field to the subscriber's instance)
- fixed zIndex of form editor's toolbar (footer was positioned above, preventing click)
2016-01-19 17:02:05 +01:00
7bd25660df Merge pull request #293 from mailpoet/form_editor
Form editor update
2016-01-19 13:22:21 +02:00
3b9821fbe1 Remove matching form block when custom field is deleted 2016-01-18 17:46:42 +01:00
cabe2d61b7 Form editor update
- when a custom field is updated, the matching form field is now also updated
- ability to toggle "is_required" on First name & Last name
- fixed position of validation errors on segment selection, checkbox and radio
- fixed form subscription not working when using custom fields
- fixed sortable in segment selection after list update (add/remove)
- updated position of messages in form subscription
2016-01-18 17:23:10 +01:00
a6d802e2fa Bump up release version information 2016-01-15 18:16:39 +02:00
1732c4f634 Merge pull request #292 from mailpoet/total_subscriber_shortcode
Shortcodes (Archives & Total subscribers) & MailPoet page (create on install)
2016-01-15 17:07:11 +02:00
bb77134224 Unit tests for Settings getValue/setValue
- fixed typo in Shortcodes
- changed for -> foreach
2016-01-15 15:50:23 +01:00
f1cb64b240 Merge pull request #291 from mailpoet/animations
Editor: Animations
2016-01-15 14:15:49 +01:00
3689545589 Default page on install + Setting::setValue() dot notation + cleanup 2016-01-15 13:37:37 +01:00
9b67c56281 Make "Delete" tool animation less janky 2016-01-15 14:06:44 +02:00
dc38b19667 Change transition timings and easings based on feedback 2016-01-15 12:05:43 +02:00
a574733217 archives page 2016-01-14 20:00:15 +01:00
b90aaa629e Merge pull request #290 from mailpoet/subscribe_on_register
Subscribe on register
2016-01-14 20:21:57 +02:00
8de186c0e6 fixed subscribe on registration not using the proper setting 2016-01-14 17:41:04 +01:00
e3719967f9 renaming + casting 2016-01-14 17:13:02 +01:00
138a631ed7 Fix unit tests + enable signup confirmation by default
- minor cleanup
2016-01-14 15:57:46 +01:00
07b7636a72 Update Setting::getValue() to use dot syntax in Hooks
- fixed styling on welcome page (again)
2016-01-14 15:34:13 +01:00
a63ce3cdac Subscribe on registration 2016-01-14 15:30:22 +01:00
f5c7bb87af Merge pull request #288 from mailpoet/settings_round_1
Subscribe in comments
2016-01-14 16:28:52 +02:00
2c8d925971 fixed fatal error 2016-01-14 15:23:22 +01:00
0c5beb2511 Refactor & Improvements
- fixed position of checkbox in comment form
- refactored Subscriber::subscribe() method
- removed Form\Subscribe class in favor of Subscription\Comment (I'll create a similar class for Registration)
- added labels in Settings > Basics for Subscribe in comments & registration
- added method in Setting model to check whether signup confirmation is enabled
2016-01-14 14:11:25 +01:00
9c0316a87d Merge pull request #289 from mailpoet/newsletter_preview
Hook up sending newsletter previews
2016-01-14 12:48:12 +01:00
46c1b682fa Add option to scroll to notices 2016-01-14 13:39:48 +02:00
7954346a3f Fix left padding for .mailpoet_notice on editor pages 2016-01-14 13:31:40 +02:00
d87ff67f50 Remove whitespace after if and catch keywords 2016-01-13 18:50:52 +02:00
6642bb3bfa Verify preview input data, remove "Sender" inputs 2016-01-13 14:28:43 +02:00
2cb32e7a78 Add a method for sending newsletters via new Mailer class 2016-01-13 13:04:21 +02:00
fcea9adbd9 Unit tests + minor bugfix
- added unit tests for new methods in Models\Subscriber
- removed check for "isNew()". It was an unecessary check. Also isNew() always returns false once the new model is saved.
2016-01-13 11:54:23 +01:00
bbdd0dbb6e Subscribe in comments
- added Subscriber::subscribe($user, $segment_ids)
- refactored Router\Subscribers->subscribe() method to account for new method
- added Form\Subscribe class to handle subscription in comments
- updated Basics settings page (changed "list" to "segment")
2016-01-12 18:46:31 +01:00
1b2cf7bd16 Merge pull request #287 from mailpoet/newsletter_save
JSON encode newsletter body when sending it to server
2016-01-12 12:34:38 +01:00
b7cfa549d5 Change newsletter and template saving to JSON encode body separately 2016-01-11 18:27:30 +02:00
ffc1d0a61c Set MailPoet version to 0.0.10 for release 2016-01-08 19:38:57 +02:00
d1b160def7 Merge pull request #286 from mailpoet/cron_update
Cron update
2016-01-08 19:23:50 +02:00
493fd01754 Merge pull request #285 from mailpoet/import_export_fix
Import export fix
2016-01-08 19:21:56 +02:00
9ced4b1757 Fix export test after temp URL changed 2016-01-08 19:21:32 +02:00
17010e5ba9 Merge pull request #281 from mailpoet/rendering_engine_update
Rendering engine update
2016-01-08 19:00:12 +02:00
42ad7584d4 - Refactors ColumnsHelper class 2016-01-08 11:35:30 -05:00
dbc0f9b238 - Removes header padding 2016-01-08 11:23:02 -05:00
e62e9a5892 - Fixes issue with temp folder
- Updates formatting
2016-01-08 10:55:09 -05:00
bc25fa61b4 - Updates render method
- Removes unused/commented out code
2016-01-08 10:38:46 -05:00
2590967183 - Formats new line identation
- Formats identations in general
2016-01-08 09:00:09 -05:00
86eafd3c17 - Removes tabs 2016-01-08 08:47:41 -05:00
90a6f160c2 - Removes space after function 2016-01-08 07:53:26 -05:00
c774aec6a2 Revert "- Fixes minor issue when daemon has not yet been created"
This reverts commit 8f2fd1d76e.
2016-01-08 07:49:15 -05:00
8f2fd1d76e - Fixes minor issue when daemon has not yet been created 2016-01-08 07:40:32 -05:00
4df11163a1 automatically update 'updated_at' when saving daemon 2016-01-08 12:23:15 +01:00
82a736ffbb Cron update + removing console.log
- use Setting::getValue for getDaemon method
- added "updated_at" property within cron_daemon value (instead of using the setting's column)
- converted line endings to Unix in notice.js and removed console.log
2016-01-08 12:02:11 +01:00
87052986e8 - Rebases master
- Updates template
2016-01-07 23:53:15 -05:00
0c73c0fadc - Resolves issues identified by @rafaehlers during testing 2016-01-07 22:47:59 -05:00
5c7e11076d - WIP on updating import 2016-01-07 18:11:59 -05:00
d1df94c759 - Addresses issues identified during code review 2016-01-07 17:24:46 -05:00
53cc39c6f5 - Removes spacer from social icon elements 2016-01-07 17:24:45 -05:00
4955c72ee1 - Removes transparent background from divider element 2016-01-07 17:24:44 -05:00
16661af8c3 - Updates text alignemnt for buttons 2016-01-07 17:24:44 -05:00
bc80f69e41 - Updates the template 2016-01-07 17:24:43 -05:00
0192934e65 - Removes debug leftovers 2016-01-07 17:24:42 -05:00
2793e74858 - Rewrites the rendering engine
- Updates tests
Closes #280
2016-01-07 17:24:32 -05:00
5996696cc9 Merge pull request #284 from mailpoet/animations
Animations
2016-01-07 17:31:17 +01:00
7f6cf5bbf3 Remove obsolete clear divs 2016-01-07 18:28:37 +02:00
c0ef2254cd Merge pull request #283 from mailpoet/queue_refactor
Queue refactoring
2016-01-07 13:11:47 +01:00
0dbe04c3f8 - Addresses issues identified during code review 2016-01-06 19:19:06 -05:00
ef1805d9b5 Change obsolete "Arial Black" fonts to "Arial", add Velocity to tests 2016-01-06 16:43:39 +02:00
514f539e83 Remove obsolete Velocity dependence due to it being injected elsewhere 2016-01-06 15:58:14 +02:00
50f072705e Fix double animations when ALC block changes, remove console.log lines 2016-01-06 12:48:45 +02:00
f8f7bc3d3d Handle sidebar animations with Velocity, fix delete button transitions 2016-01-06 12:29:32 +02:00
f1bf2bb097 - Refactors Mailer class
- Refactors SendingQueue worker class
- Adds Maier router with a send() method + ability to specify sending method
- Updates tests
- Introduces 'stopping' and 'starting' cron states
- Improves cron control mechanism
Closes #276
2016-01-05 10:34:57 -05:00
bbe2f69a7f Clean up unused and speed up animations, fix sidebar transitions 2016-01-05 17:32:59 +02:00
c844488b0b Switch to VelocityJS for view transitions, slow down some transitions 2016-01-05 15:01:30 +02:00
112fe0cd6e Merge pull request #282 from mailpoet/sticky_kit
Fix sticky-kit dependency usage
2016-01-04 16:25:59 +01:00
c9e6dce785 Rename vendor_static/ to vendor/ 2016-01-04 17:02:46 +02:00
d1c09c015a Remove remote sticky-kit dep, use static local patched package instead 2016-01-04 13:19:40 +02:00
319 changed files with 18538 additions and 17513 deletions

View File

@ -1,2 +1,16 @@
WP_TEST_PATH="/var/www/wordpress"
WP_TEST_ENABLE_NETWORK_TESTS="true"
WP_TEST_IMPORT_MAILCHIMP_API=""
WP_TEST_IMPORT_MAILCHIMP_LISTS="" // (separated with comma)
WP_TEST_MAILER_ENABLE_SENDING="true"
WP_TEST_MAILER_AMAZON_ACCESS=""
WP_TEST_MAILER_AMAZON_SECRET=""
WP_TEST_MAILER_AMAZON_REGION=""
WP_TEST_MAILER_ELASTICEMAIL_API=""
WP_TEST_MAILER_MAILGUN_API=""
WP_TEST_MAILER_MAILGUN_DOMAIN=""
WP_TEST_MAILER_MAILPOET_API=""
WP_TEST_MAILER_SENDGRID_API=""
WP_TEST_MAILER_SMTP_HOST=""
WP_TEST_MAILER_SMTP_LOGIN=""
WP_TEST_MAILER_SMTP_PASSWORD=""

2
.gitignore vendored
View File

@ -1,7 +1,7 @@
.DS_Store
TODO
composer.phar
vendor
/vendor
tests/_output/*
tests/acceptance.suite.yml
tests/_support/_generated/*

View File

@ -89,14 +89,14 @@ class RoboFile extends \Robo\Tasks {
}
function makepot() {
$this->_exec('grunt makepot'.
$this->_exec('./node_modules/.bin/grunt makepot'.
' --gruntfile '.__DIR__.'/tasks/makepot/makepot.js'.
' --base_path '.__DIR__
);
}
function pushpot() {
$this->_exec('grunt pushpot'.
$this->_exec('./node_modules/.bin/grunt pushpot'.
' --gruntfile '.__DIR__.'/tasks/makepot/makepot.js'.
' --base_path '.__DIR__
);
@ -105,14 +105,25 @@ class RoboFile extends \Robo\Tasks {
function testUnit($file = null) {
$this->loadEnv();
$this->_exec('vendor/bin/codecept build');
$this->_exec('vendor/bin/codecept run unit '.(($file) ? $file : ''));
$this->_exec('vendor/bin/codecept run unit -f '.(($file) ? $file : ''));
}
function testCoverage($file = null) {
$this->loadEnv();
$this->_exec('vendor/bin/codecept build');
$this->_exec(join(' ', array(
'vendor/bin/codecept run',
(($file) ? $file : ''),
'--coverage',
'--coverage-html'
)));
}
function testJavascript() {
$this->compileJs();
$this->_exec(join(' ', array(
'./node_modules/mocha/bin/mocha',
'./node_modules/.bin/mocha',
'-r tests/javascript/mochaTestHelper.js',
'tests/javascript/testBundles/**/*.js'
)));

View File

@ -17,3 +17,5 @@
@require 'settings'
@require 'progress_bar'
@require 'subscribers'

View File

@ -43,7 +43,7 @@
bottom: 0
background-color: rgba(255, 255, 255, 0.0)
opacity: 0
transition: all 200ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
transition: all 250ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
&:hover
background-color: rgba(255, 255, 255, 0.7)

View File

@ -19,6 +19,8 @@ a:focus
// select 2
.select2-container
width: 25em !important
// textareas
textarea.regular-text
width: 25em !important

View File

@ -125,6 +125,7 @@ handle_icon = '../img/handle.png'
float: none
#mailpoet_form_toolbar
z-index: 999
position: absolute
width: 400px
@ -562,3 +563,6 @@ handle_icon = '../img/handle.png'
.CodeMirror
border: 1px solid #eee
/* Settings */
#mailpoet_form_segments.parsley-error + span .select2-selection
border: 1px solid #b94a48

View File

@ -90,7 +90,7 @@ body.mailpoet_modal_opened
padding: 0
margin: 0
width: 100%
transition: margin 0.3s ease-out
transition: margin 350ms ease-out
.mailpoet_panel_wrapper
background-color: #f1f1f1
@ -200,4 +200,4 @@ body.mailpoet_modal_opened
0%
50%
background-color: #064E6D
100%
100%

View File

@ -7,7 +7,6 @@ $tool-active-secondary-color = #ffffff
$tool-width = 20px
$master-column-tool-width = 24px
$layer-selector-width = 30px
.mailpoet_tools
position: absolute
@ -33,10 +32,35 @@ $layer-selector-width = 30px
width: $master-column-tool-width
height: $master-column-tool-width
.mailpoet_delete_block_activate
max-width: 100%
max-height: $master-column-tool-width
opacity: 1
display: block
.mailpoet_delete_block_confirm,
.mailpoet_delete_block_cancel
max-width: 100%
max-height: 0
opacity: 0
overflow: hidden
display: block
.mailpoet_delete_block_activated
width: auto
height: auto
.mailpoet_delete_block_activate
overflow: hidden
max-height: 0
opacity: 0
.mailpoet_delete_block_confirm,
.mailpoet_delete_block_cancel
max-height: $master-column-tool-width*2
opacity: 1
.mailpoet_tool
display: inline-block
width: $tool-width
@ -82,7 +106,7 @@ $layer-selector-width = 30px
padding: 0
.mailpoet_delete_block_activate
max-width: 100%
max-width: $tool-width
display: inline-block
opacity: 1
animation-fade-in-and-scale-horizontally()
@ -96,12 +120,12 @@ $layer-selector-width = 30px
animation-fade-in-and-scale-horizontally()
.mailpoet_delete_block_activated
height: auto
width: auto
border-radius(3px)
background-color: $warning-background-color
padding: 3px 5px
line-height: 1.2em
height: auto
.mailpoet_delete_block_activate
overflow: hidden
@ -113,6 +137,9 @@ $layer-selector-width = 30px
max-width: 100%
opacity: 1
.mailpoet_delete_block_cancel
margin-left: 3px
.mailpoet_delete_block_confirm
color: $warning-text-color

View File

@ -52,7 +52,7 @@ $draggable-widget-z-index = 2
padding: 0
margin: 0
z-index: $draggable-widget-z-index
animation-fade-in-and-scale-up()
animation-fade-in()
.mailpoet_widget_icon
padding: 0

View File

@ -37,3 +37,6 @@
input[type=text]
vertical-align: middle
.mailpoet_form_field_block
display: block

View File

@ -38,9 +38,7 @@
content: '\f142'
.mailpoet_save_show_options_icon
width: auto
height: auto
line-height: auto
vertical-align: middle
&::before
content: '\f140'

View File

@ -26,13 +26,9 @@ $widget-icon-width = 30px
border-right: 0
&.closed .mailpoet_region_content
max-height: 0px
overflow: hidden
margin-top: 0
display: none
.mailpoet_region_content
max-height: 2000px
transition: max-height 300ms ease
padding: 0 20px
margin-top: 12px

View File

@ -21,3 +21,9 @@
.mailpoet_automated_latest_content_display_options
animation-slide-open-downwards()
.mailpoet_automated_latest_content_show_amount
width: 25px
.mailpoet_automated_latest_content_content_type
width: 180px

View File

@ -30,8 +30,8 @@ $block-hover-highlight-color = $primary-active-color
.mailpoet_content
position: relative
line-height: 1.61803398875
.mailpoet_block_transition_in
animation-fade-in-and-scale-up()
.mailpoet_block_transition_out
animation-fade-out-and-scale-down()
p, h1, h2, h3, h4, h5, h6
line-height: 1.61803398875
font-style: normal

View File

@ -1,6 +1,10 @@
.mailpoet_footer_block
padding-left: 0
padding-right: 0
margin-bottom: 0
.mailpoet_content
padding: 5px 20px
padding: 10px 20px
p
margin: 0

View File

@ -1,6 +1,10 @@
.mailpoet_header_block
padding-left: 0
padding-right: 0
margin-bottom: 0
.mailpoet_content
padding: 5px 20px
padding: 10px 20px
p
margin: 0

View File

@ -11,9 +11,6 @@
padding-right: 0
margin-bottom: 0
img
width: 100%
.mailpoet_content a:hover
cursor: all-scroll

View File

@ -1,16 +1,23 @@
$text-vertical-padding = 3px
.mailpoet_text_block
padding-left: 0
padding-right: 0
& > .mailpoet_content
overflow: hidden
padding-top: 13px
padding-bottom: 13px
padding-top: 0
padding-bottom: 0px
padding-left: 20px
padding-right: 20px
h1, h2, h3, h4, h5, h6
padding: 0
margin: 0
font-weight: normal
p
margin-top: 0
font-weight: normal
blockquote
margin: 1em
padding-left: 1em

View File

@ -129,3 +129,21 @@ body
#mailpoet_modal_close
display: none
.wrap > .mailpoet_notice,
.update-nag
margin-left: 2px + 15px !important
/* Make a button group */
.mailpoet_button_group
.button:first-child
border-right: 0
border-top-right-radius: 0
border-bottom-right-radius: 0
.button:last-child
border-left: 0
border-top-left-radius: 0
border-bottom-left-radius: 0

View File

@ -1,5 +1,5 @@
animation-slide-open-downwards()
transition: all 300ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
transition: all 250ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
max-height: 2000px
opacity: 1
@ -9,45 +9,23 @@ animation-slide-open-downwards()
overflow-y: hidden
animation-background-color()
transition: background 300ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
transition: background 250ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
animation-fade-in-and-scale-up()
animation-name: fadeInAndScaleUp
animation-duration: 500ms
animation-fill-mode: forwards
animation-fade-out-and-scale-down()
animation-name: fadeOutAndScaleDown
animation-duration: 500ms
animation-fade-in()
animation-name: fadeIn
animation-duration: 300ms
animation-fill-mode: forwards
animation-timing-function: ease-in
animation-fade-in-and-scale-horizontally()
transition: all 300ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
transition: all 250ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
@keyframes fadeInAndScaleUp {
@keyframes fadeIn {
0% {
opacity: 0.3
max-height: 0
overflow: hidden
}
100% {
opacity: 1
max-height: 5000px
overflow: hidden
}
}
@keyframes fadeOutAndScaleDown {
0% {
opacity: 1
max-height: 5000px
overflow: hidden
}
100% {
opacity: 0.3
max-height: 0
overflow: hidden
}
}

View File

@ -2,3 +2,13 @@
@require 'parsley'
@require 'form_validation'
/* labels */
.mailpoet_text_label
.mailpoet_textarea_label
.mailpoet_select_label
.mailpoet_radio_label
.mailpoet_checkbox_label
.mailpoet_list_label
.mailpoet_date_label
display:block

View File

@ -0,0 +1,3 @@
#subscribers_container
.mailpoet_segments_unsubscribed
color: lighten(#555, 33)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -15,10 +15,10 @@ define(
status: 'loading'
};
},
getDaemonData: function() {
getCronData: function() {
MailPoet.Ajax.post({
endpoint: 'cron',
action: 'getDaemonStatus'
action: 'getStatus'
})
.done(function(response) {
jQuery('.button-primary')
@ -32,58 +32,56 @@ define(
},
componentDidMount: function() {
if(this.isMounted()) {
this.getDaemonData();
setInterval(this.getDaemonData, 5000);
this.getCronData();
setInterval(this.getCronData, 5000);
}
},
controlDaemon: function(action) {
controlCron: function(action) {
if(jQuery('.button-primary').hasClass('disabled')) {
return;
}
jQuery('.button-primary')
.addClass('disabled');
MailPoet.Ajax.post({
endpoint: 'cron',
action: 'controlDaemon',
data: {
'action': action
}
action: action,
})
.done(function(response) {
if(!response.result) {
//this.replaceState();
} else {
//this.setState(response);
MailPoet.Notice.error(MailPoet.I18n.t('daemonControlError'));
}
}.bind(this));
},
render: function() {
if(this.state.status === 'loading') {
return(<div>Loading daemon status...</div>);
return(<div>{MailPoet.I18n.t('loadingDaemonStatus')}</div>);
}
switch(this.state.status) {
case 'started':
return(
<div>
Cron daemon is running.
{MailPoet.I18n.t('cronDaemonIsRunning')}
<br/>
<br/>
It was started
<strong> {this.state.timeSinceStart} </strong> and last executed
<strong> {this.state.timeSinceUpdate} </strong> for a total of
<strong> {this.state.counter} </strong> times (once every 30 seconds, unless it was interrupted and restarted).
<br />
<br />
<a href="#" className="button-primary" onClick={this.controlDaemon.bind(null, 'stop')}>Stop</a>&nbsp;&nbsp;
<a href="#" className="button-primary" onClick={this.controlDaemon.bind(null, 'pause')}>Pause</a>
<a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'stop')}>{MailPoet.I18n.t('stop')}</a>
</div>
);
break;
case 'starting':
case 'stopping':
return(
<div>
{MailPoet.I18n.t('cronDaemonState').replace('%$1s', this.state.status)}
</div>
);
break;
case 'paused':
case 'stopped':
return(
<div>
Daemon is {this.state.status}
{MailPoet.I18n.t('cronDaemonState').replace('%$1s', this.state.status)}
<br />
<br />
<a href="#" className="button-primary" onClick={this.controlDaemon.bind(null, 'start')}>Start</a>
<a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'start')}>{MailPoet.I18n.t('start')}</a>
</div>
);
break;
@ -99,4 +97,4 @@ define(
container
);
}
});
});

136
assets/js/src/date.js Normal file
View File

@ -0,0 +1,136 @@
define('date',
[
'mailpoet',
'jquery',
'moment'
], function(
MailPoet,
jQuery,
Moment
) {
'use strict';
MailPoet.Date = {
version: 0.1,
options: {},
defaults: {
offset: 0,
format: 'F, d Y H:i:s'
},
init: function(options) {
options = options || {};
// set UTC offset
if (
options.offset === undefined
&& window.mailpoet_date_offset !== undefined
) {
options.offset = window.mailpoet_date_offset;
}
// set date format
if (
options.format === undefined
&& window.mailpoet_date_format !== undefined
) {
options.format = window.mailpoet_date_format;
}
// merge options
this.options = jQuery.extend({}, this.defaults, options);
return this;
},
format: function(date, options) {
this.init(options);
return Moment.utc(date)
.local()
.format(this.convertFormat(this.options.format));
},
short: function(date) {
return this.format(date, {
format: 'F, j Y'
});
},
full: function(date) {
return this.format(date, {
format: 'F, j Y H:i:s'
});
},
time: function(date) {
return this.format(date, {
format: 'H:i:s'
});
},
convertFormat: function(format) {
const format_mappings = {
date: {
D: 'ddd',
l: 'dddd',
d: 'DD',
j: 'D',
z: 'DDDD',
N: 'E',
S: '',
M: 'MMM',
F: 'MMMM',
m: 'MM',
n: '',
t: '',
y: 'YY',
Y: 'YYYY',
H: 'HH',
h: 'hh',
g: 'h',
A: 'A',
i: 'mm',
s: 'ss',
T: 'z',
O: 'ZZ',
w: 'd',
W: 'WW'
},
strftime: {
a: 'ddd',
A: 'dddd',
b: 'MMM',
B: 'MMMM',
d: 'DD',
e: 'D',
F: 'YYYY-MM-DD',
H: 'HH',
I: 'hh',
j: 'DDDD',
k: 'H',
l: 'h',
m: 'MM',
M: 'mm',
p: 'A',
S: 'ss',
u: 'E',
w: 'd',
W: 'WW',
y: 'YY',
Y: 'YYYY',
z: 'ZZ',
Z: 'z'
}
};
const replacements = format_mappings['date'];
let outputFormat = '';
Object.keys(replacements).forEach(function(key) {
if (format.indexOf(key) !== -1) {
format = format.replace(key, '%'+key);
}
});
outputFormat = format;
Object.keys(replacements).forEach(function(key) {
if (outputFormat.indexOf('%'+key) !== -1) {
outputFormat = outputFormat.replace('%'+key, replacements[key]);
}
});
return outputFormat;
}
};
});

View File

@ -1,62 +1,45 @@
define([
'react',
'react-checkbox-group'
'react'
],
function(
React,
CheckboxGroup
React
) {
var FormFieldCheckbox = React.createClass({
const FormFieldCheckbox = React.createClass({
onValueChange: function(e) {
e.target.value = this.refs.checkbox.checked ? '1' : '';
return this.props.onValueChange(e);
},
render: function() {
var selected_values = this.props.item[this.props.field.name] || '';
if(
selected_values !== undefined
&& selected_values.constructor !== Array
) {
selected_values = selected_values.split(';').map(function(value) {
return value.trim();
});
if (this.props.field.values === undefined) {
return false;
}
var count = Object.keys(this.props.field.values).length;
var options = Object.keys(this.props.field.values).map(
function(value, index) {
const isChecked = !!(this.props.item[this.props.field.name]);
const options = Object.keys(this.props.field.values).map(
(value, index) => {
return (
<p key={ 'checkbox-' + index }>
<label>
<input type="checkbox" value={ value } />
&nbsp;{ this.props.field.values[value] }
<input
ref="checkbox"
type="checkbox"
value="1"
checked={ isChecked }
onChange={ this.onValueChange }
name={ this.props.field.name }
/>
{ this.props.field.values[value] }
</label>
</p>
);
}.bind(this)
}
);
return (
<CheckboxGroup
name={ this.props.field.name }
value={ selected_values }
ref={ this.props.field.name }
onChange={ this.handleValueChange }>
<div>
{ options }
</CheckboxGroup>
</div>
);
},
handleValueChange: function() {
var field = this.props.field.name;
var group = this.refs[field];
var selected_values = [];
if(group !== undefined) {
selected_values = group.getCheckedValues();
}
return this.props.onValueChange({
target: {
name: field,
value: selected_values.join(';')
}
});
}
});

View File

@ -0,0 +1,196 @@
define([
'react',
'moment',
], function(
React,
Moment
) {
class FormFieldDateYear extends React.Component {
render() {
const yearsRange = 100;
const years = [];
const currentYear = Moment().year();
for (let i = currentYear; i >= currentYear - yearsRange; i--) {
years.push((
<option
key={ i }
value={ i }
>{ i }</option>
));
}
return (
<select
name={ this.props.name + '[year]' }
value={ this.props.year }
onChange={ this.props.onValueChange }
>
{ years }
</select>
);
}
}
class FormFieldDateMonth extends React.Component {
render() {
const months = [];
for (let i = 1; i <= 12; i++) {
months.push((
<option
key={ i }
value={ i }
>{ this.props.monthNames[i - 1] }</option>
));
}
return (
<select
name={ this.props.name + '[month]' }
value={ this.props.month }
onChange={ this.props.onValueChange }
>
{ months }
</select>
);
}
}
class FormFieldDateDay extends React.Component {
render() {
const days = [];
for (let i = 1; i <= 31; i++) {
days.push((
<option
key={ i }
value={ i }
>{ i }</option>
));
}
return (
<select
name={ this.props.name + '[day]' }
value={ this.props.day }
onChange={ this.props.onValueChange }
>
{ days }
</select>
);
}
}
class FormFieldDate extends React.Component {
constructor(props) {
super(props);
this.state = {
year: Moment().year(),
month: 1,
day: 1
}
}
componentDidMount() {
}
componentDidUpdate(prevProps, prevState) {
if (
(this.props.item !== undefined && prevProps.item !== undefined)
&& (this.props.item.id !== prevProps.item.id)
) {
this.extractTimeStamp();
}
}
extractTimeStamp() {
const timeStamp = parseInt(this.props.item[this.props.field.name], 10);
this.setState({
year: Moment.unix(timeStamp).year(),
// Moment returns the month as [0..11]
// We increment it to match PHP's mktime() which expects [1..12]
month: Moment.unix(timeStamp).month() + 1,
day: Moment.unix(timeStamp).date()
});
}
updateTimeStamp(field) {
let newTimeStamp = Moment(
`${this.state.month}/${this.state.day}/${this.state.year}`,
'M/D/YYYY'
).valueOf();
if (!isNaN(newTimeStamp) && parseInt(newTimeStamp, 10) > 0) {
// convert milliseconds to seconds
newTimeStamp /= 1000;
return this.props.onValueChange({
target: {
name: field,
value: newTimeStamp
}
});
}
}
onValueChange(e) {
// extract property from name
const matches = e.target.name.match(/(.*?)\[(.*?)\]/);
let field = null;
let property = null;
if (matches !== null && matches.length === 3) {
field = matches[1];
property = matches[2];
let value = parseInt(e.target.value, 10);
this.setState({
[`${property}`]: value
}, () => {
this.updateTimeStamp(field);
});
}
}
render() {
const monthNames = window.mailpoet_month_names || [];
const dateType = this.props.field.params.date_type;
const dateSelects = dateType.split('_');
const fields = dateSelects.map(type => {
switch(type) {
case 'year':
return (<FormFieldDateYear
onValueChange={ this.onValueChange.bind(this) }
ref={ 'year' }
key={ 'year' }
name={ this.props.field.name }
year={ this.state.year }
/>);
break;
case 'month':
return (<FormFieldDateMonth
onValueChange={ this.onValueChange.bind(this) }
ref={ 'month' }
key={ 'month' }
name={ this.props.field.name }
month={ this.state.month }
monthNames={ monthNames }
/>);
break;
case 'day':
return (<FormFieldDateDay
onValueChange={ this.onValueChange.bind(this) }
ref={ 'day' }
key={ 'day' }
name={ this.props.field.name }
day={ this.state.day }
/>);
break;
}
});
return (
<div>
{fields}
</div>
);
}
};
return FormFieldDate;
});

View File

@ -5,7 +5,8 @@ define([
'form/fields/select.jsx',
'form/fields/radio.jsx',
'form/fields/checkbox.jsx',
'form/fields/selection.jsx'
'form/fields/selection.jsx',
'form/fields/date.jsx',
],
function(
React,
@ -14,7 +15,8 @@ function(
FormFieldSelect,
FormFieldRadio,
FormFieldCheckbox,
FormFieldSelection
FormFieldSelection,
FormFieldDate
) {
var FormField = React.createClass({
renderField: function(data, inline = false) {
@ -55,6 +57,10 @@ function(
case 'selection':
field = (<FormFieldSelection {...data} />);
break;
case 'date':
field = (<FormFieldDate {...data} />);
break;
}
if(inline === true) {
@ -66,10 +72,10 @@ function(
);
} else {
return (
<p key={ 'field-' + (data.index || 0) }>
<div key={ 'field-' + (data.index || 0) }>
{ field }
{ description }
</p>
</div>
);
}
},

View File

@ -4,13 +4,15 @@ define([
function(
React
) {
var FormFieldRadio = React.createClass({
const FormFieldRadio = React.createClass({
render: function() {
var selected_value = this.props.item[this.props.field.name];
var count = Object.keys(this.props.field.values).length;
if (this.props.field.values === undefined) {
return false;
}
var options = Object.keys(this.props.field.values).map(
function(value, index) {
const selected_value = this.props.item[this.props.field.name];
const options = Object.keys(this.props.field.values).map(
(value, index) => {
return (
<p key={ 'radio-' + index }>
<label>
@ -20,11 +22,11 @@ function(
value={ value }
onChange={ this.props.onValueChange }
name={ this.props.field.name } />
&nbsp;{ this.props.field.values[value] }
{ this.props.field.values[value] }
</label>
</p>
);
}.bind(this)
}
);
return (

View File

@ -4,10 +4,14 @@ define([
function(
React
) {
var FormFieldSelect = React.createClass({
const FormFieldSelect = React.createClass({
render: function() {
var options =
Object.keys(this.props.field.values).map(function(value, index) {
if (this.props.field.values === undefined) {
return false;
}
const options = Object.keys(this.props.field.values).map(
(value, index) => {
return (
<option
key={ 'option-' + index }
@ -15,7 +19,7 @@ function(
{ this.props.field.values[value] }
</option>
);
}.bind(this)
}
);
return (

View File

@ -26,7 +26,7 @@ function(
&& (this.props.item.id !== prevProps.item.id)
) {
jQuery('#'+this.refs.select.id)
.val(this.props.item[this.props.field.name])
.val(this.getSelectedValues())
.trigger('change');
}
},
@ -45,7 +45,11 @@ function(
if(item.element && item.element.selected) {
return null;
} else {
return item.text;
if(item.title) {
return item.title;
} else {
return item.text;
}
}
}
});
@ -65,15 +69,25 @@ function(
select2.select2(
'val',
this.props.item[this.props.field.name]
this.getSelectedValues()
);
this.setState({ initialized: true });
},
getSelectedValues: function() {
if(this.props.field['selected'] !== undefined) {
return this.props.field['selected'](this.props.item);
} else if(this.props.item !== undefined && this.props.field.name !== undefined) {
return this.props.item[this.props.field.name];
} else {
return null;
}
},
loadCachedItems: function() {
if(typeof(window['mailpoet_'+this.props.field.endpoint]) !== 'undefined') {
var items = window['mailpoet_'+this.props.field.endpoint];
if(this.props.field['filter'] !== undefined) {
items = items.filter(this.props.field.filter);
}
@ -98,31 +112,48 @@ function(
});
}
},
getLabel: function(item) {
if(this.props.field['getLabel'] !== undefined) {
return this.props.field.getLabel(item, this.props.item);
}
return item.name;
},
getSearchLabel: function(item) {
if(this.props.field['getSearchLabel'] !== undefined) {
return this.props.field.getSearchLabel(item, this.props.item);
}
return null;
},
getValue: function(item) {
if(this.props.field['getValue'] !== undefined) {
return this.props.field.getValue(item, this.props.item);
}
return item.id;
},
render: function() {
var options = this.state.items.map(function(item, index) {
const options = this.state.items.map((item, index) => {
let label = this.getLabel(item);
let searchLabel = this.getSearchLabel(item);
let value = this.getValue(item);
return (
<option
key={ item.id }
value={ item.id }
key={ 'option-'+index }
value={ value }
title={ searchLabel }
>
{ item.name }
{ label }
</option>
);
});
var default_value = (
(this.props.item !== undefined && this.props.field.name !== undefined)
? this.props.item[this.props.field.name]
: null
);
return (
<select
id={ this.props.field.id || this.props.field.name }
ref="select"
placeholder={ this.props.field.placeholder }
data-placeholder={ this.props.field.placeholder }
multiple={ this.props.field.multiple }
defaultValue={ default_value }
defaultValue={ this.getSelectedValues() }
{...this.props.field.validation}
>{ options }</select>
);

View File

@ -48,7 +48,7 @@ define(
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'get',
data: { id: id }
data: id
}).done(function(response) {
if(response === false) {
this.setState({
@ -167,7 +167,7 @@ define(
<input
className="button button-primary"
type="submit"
value="Save"
value={MailPoet.I18n.t('save')}
disabled={this.state.loading} />
);
}
@ -199,4 +199,4 @@ define(
return Form;
}
);
);

View File

@ -402,11 +402,30 @@ var WysijaForm = {
}
});
// hide list selection if a list widget has been dragged into the editor
$('mailpoet_settings_segment_selection')[
(($$('#' + WysijaForm.options.editor + ' [wysija_id="segments"]').length > 0) === true)
? 'hide' : 'show'
]();
var hasSegmentSelection = WysijaForm.hasSegmentSelection();
if(hasSegmentSelection) {
$('mailpoet_form_segments').writeAttribute('required', false).disable();
$('mailpoet_settings_segment_selection').hide();
} else {
$('mailpoet_form_segments').writeAttribute('required', true).enable();
$('mailpoet_settings_segment_selection').show();
}
},
hasSegmentSelection: function() {
return ($$('#' + WysijaForm.options.editor + ' [wysija_id="segments"]').length > 0);
},
isSegmentSelectionValid: function() {
var segment_selection = $$('#' + WysijaForm.options.editor + ' [wysija_id="segments"]')[0];
if(segment_selection !== undefined) {
var block = WysijaForm.get(segment_selection).block.getData();
return (
(block.params.values !== undefined)
&&
(block.params.values.length > 0)
);
}
return false;
},
setBlockPositions: function(event, target) {
// release dragging lock
@ -617,6 +636,28 @@ var WysijaForm = {
// this is a url, so do not encode the protocol
return encodeURI(str).replace(/[!'()*]/g, escape);
}
},
updateBlock: function(field) {
var hasUpdated = false;
WysijaForm.getBlocks().each(function(b) {
if(b.block.getData().id === field.id) {
hasUpdated = true;
b.block.redraw(field);
}
});
return hasUpdated;
},
removeBlock: function(field, callback) {
var hasRemoved = false;
WysijaForm.getBlocks().each(function(b) {
if(b.block.getData().id === field.id) {
hasRemoved = true;
b.block.removeBlock(callback);
}
});
return hasRemoved;
}
};
@ -825,10 +866,6 @@ WysijaForm.Block = Class.create({
Effect.Fade(this.element.identify(), {
duration: 0.2,
afterFinish: function(effect) {
if(effect.element.next('.mailpoet_form_block') !== undefined && callback !== false) {
// show controls of next block to allow mass delete
WysijaForm.get(effect.element.next('.mailpoet_form_block')).block.showControls();
}
// remove placeholder
if(effect.element.previous('.block_placeholder') !== undefined) {
effect.element.previous('.block_placeholder').remove();

View File

@ -8,91 +8,82 @@ import MailPoet from 'mailpoet'
const columns = [
{
name: 'name',
label: 'Name',
label: MailPoet.I18n.t('formName'),
sortable: true
},
{
name: 'segments',
label: 'Lists',
label: MailPoet.I18n.t('segments'),
sortable: false
},
{
name: 'created_at',
label: 'Created on',
label: MailPoet.I18n.t('createdOn'),
sortable: true
}
];
const messages = {
onTrash: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 form was moved to the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d forms were moved to the trash.'
).replace('%$1d', ~~response);
}
var count = ~~response;
var message = null;
if(message !== null) {
MailPoet.Notice.success(message);
}
if(count === 1) {
message = (
MailPoet.I18n.t('oneFormTrashed')
);
} else {
message = (
MailPoet.I18n.t('multipleFormsTrashed')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
},
onDelete: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 form was permanently deleted.'
);
} else if(~~response > 1) {
message = (
'%$1d forms were permanently deleted.'
).replace('%$1d', ~~response);
}
var count = ~~response;
var message = null;
if(message !== null) {
MailPoet.Notice.success(message);
}
if(count === 1) {
message = (
MailPoet.I18n.t('oneFormDeleted')
);
} else {
message = (
MailPoet.I18n.t('multipleFormsDeleted')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
},
onRestore: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 form has been restored from the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d forms have been restored from the trash.'
).replace('%$1d', ~~response);
}
var count = ~~response;
var message = null;
if(message !== null) {
MailPoet.Notice.success(message);
}
if(count === 1) {
message = (
MailPoet.I18n.t('oneFormRestored')
);
} else {
message = (
MailPoet.I18n.t('multipleFormsRestored')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
}
};
const item_actions = [
{
name: 'edit',
label: 'Edit',
label: MailPoet.I18n.t('edit'),
link: function(item) {
return (
<a href={ `admin.php?page=mailpoet-form-editor&id=${item.id}` }>Edit</a>
<a href={ `admin.php?page=mailpoet-form-editor&id=${item.id}` }>{MailPoet.I18n.t('edit')}</a>
);
}
},
{
name: 'duplicate_form',
label: 'Duplicate',
label: MailPoet.I18n.t('duplicate'),
onClick: function(item, refresh) {
return MailPoet.Ajax.post({
endpoint: 'forms',
@ -100,7 +91,7 @@ const item_actions = [
data: item.id
}).done(function(response) {
MailPoet.Notice.success(
('Form "%$1s" has been duplicated.').replace('%$1s', response.name)
(MailPoet.I18n.t('formDuplicated')).replace('%$1s', response.name)
);
refresh();
});
@ -114,7 +105,7 @@ const item_actions = [
const bulk_actions = [
{
name: 'trash',
label: 'Trash',
label: MailPoet.I18n.t('trash'),
onSuccess: messages.onTrash
}
];
@ -125,8 +116,8 @@ const FormList = React.createClass({
endpoint: 'forms',
action: 'create'
}).done(function(response) {
if(response !== false) {
window.location = response;
if(response.result && response.form_id) {
window.location = mailpoet_form_edit_url + response.form_id;
}
});
},
@ -151,11 +142,11 @@ const FormList = React.createClass({
</strong>
{ actions }
</td>
<td className="column-format" data-colname="Lists">
<td className="column-format" data-colname={MailPoet.I18n.t('segments')}>
{ segments }
</td>
<td className="column-date" data-colname="Created on">
<abbr>{ form.created_at }</abbr>
<td className="column-date" data-colname={MailPoet.I18n.t('createdOn')}>
<abbr>{ MailPoet.Date.full(form.created_at) }</abbr>
</td>
</div>
);
@ -164,11 +155,11 @@ const FormList = React.createClass({
return (
<div>
<h2 className="title">
Forms <a
{MailPoet.I18n.t('pageTitle')} <a
className="add-new-h2"
href="javascript:;"
onClick={ this.createForm }
>New</a>
>{MailPoet.I18n.t('new')}</a>
</h2>
<Listing
@ -188,4 +179,4 @@ const FormList = React.createClass({
}
});
module.exports = FormList;
module.exports = FormList;

25
assets/js/src/i18n.js Normal file
View File

@ -0,0 +1,25 @@
define('i18n',
[
'mailpoet',
'underscore',
], function(
MailPoet,
_
) {
'use strict';
var translations = {};
MailPoet.I18n = {
add: function(key, value) {
translations[key] = value;
},
t: function(key) {
return translations[key] || 'TRANSLATION "%$1s" NOT FOUND'.replace("%$1s", key);
},
all: function() {
return translations;
}
};
});

23
assets/js/src/iframe.js Normal file
View File

@ -0,0 +1,23 @@
define('iframe', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
'use strict';
MailPoet.Iframe = {
marginY: 20,
autoSize: function(iframe) {
if(!iframe) return;
this.setSize(
iframe,
iframe.contentWindow.document.body.scrollHeight
);
},
setSize: function(iframe, i) {
if(!iframe) return;
iframe.style.height = (
parseInt(i) + this.marginY
) + "px";
}
};
return MailPoet;
});

View File

@ -1,8 +1,10 @@
define([
'react'
'react',
'mailpoet'
],
function(
React
React,
MailPoet
) {
var ListingBulkActions = React.createClass({
getInitialState: function() {
@ -82,7 +84,7 @@ function(
<label
className="screen-reader-text"
htmlFor="bulk-action-selector-top">
Select bulk action
{MailPoet.I18n.t('selectBulkAction')}
</label>
<select
@ -91,7 +93,7 @@ function(
value={ this.state.action }
onChange={this.handleChangeAction}
>
<option value="">Bulk Actions</option>
<option value="">{MailPoet.I18n.t('bulkActions')}</option>
{ this.props.bulk_actions.map(function(action, index) {
return (
<option
@ -104,7 +106,7 @@ function(
<input
onClick={ this.handleApplyAction }
type="submit"
defaultValue="Apply"
defaultValue={MailPoet.I18n.t('apply')}
className="button action" />
{ this.state.extra }
@ -114,4 +116,4 @@ function(
});
return ListingBulkActions;
});
});

View File

@ -1,10 +1,12 @@
define([
'react',
'jquery'
'jquery',
'mailpoet'
],
function(
React,
jQuery
jQuery,
MailPoet
) {
var ListingFilters = React.createClass({
handleFilterAction: function() {
@ -14,6 +16,9 @@ function(
})
return this.props.onSelectFilter(filters);
},
handleEmptyTrash: function() {
return this.props.onEmptyTrash();
},
getAvailableFilters: function() {
let filters = this.props.filters;
@ -34,7 +39,7 @@ function(
const available_filters = this.getAvailableFilters()
.map(function(filter, i) {
let default_value = false;
if(selected_filters[filter] !== undefined && selected_filters[filter]) {
if (selected_filters[filter] !== undefined && selected_filters[filter]) {
default_value = selected_filters[filter]
} else {
jQuery(`select[name="${filter}"]`).val('');
@ -60,20 +65,34 @@ function(
let button = false;
if(available_filters.length > 0) {
if (available_filters.length > 0) {
button = (
<input
id="post-query-submit"
onClick={ this.handleFilterAction }
type="submit"
defaultValue="Filter"
defaultValue={MailPoet.I18n.t('filter')}
className="button" />
);
}
let empty_trash = false;
if (this.props.group === 'trash') {
empty_trash = (
<input
onClick={ this.handleEmptyTrash }
type="submit"
value={MailPoet.I18n.t('emptyTrash')}
className="button"
/>
);
}
return (
<div className="alignleft actions actions">
{ available_filters }
{ button }
{ empty_trash }
</div>
);
}

View File

@ -1,4 +1,12 @@
define(['react', 'classnames'], function(React, classNames) {
define([
'react',
'classnames',
'mailpoet'
], function(
React,
classNames,
MailPoet
) {
var ListingHeader = React.createClass({
handleSelectItems: function() {
@ -28,7 +36,7 @@ define(['react', 'classnames'], function(React, classNames) {
<th
className="manage-column column-cb check-column">
<label className="screen-reader-text">
{ 'Select All' }
{MailPoet.I18n.t('selectAll')}
</label>
<input
type="checkbox"
@ -87,4 +95,4 @@ define(['react', 'classnames'], function(React, classNames) {
});
return ListingHeader;
});
});

View File

@ -74,10 +74,11 @@ define(
);
}
var custom_actions = this.props.item_actions;
var item_actions = false;
const custom_actions = this.props.item_actions;
let item_actions = false;
if(custom_actions.length > 0) {
let is_first = true;
item_actions = custom_actions.map(function(action, index) {
if(action.display !== undefined) {
if(action.display(this.props.item) === false) {
@ -85,42 +86,44 @@ define(
}
}
let custom_action = null;
if(action.name === 'trash') {
return (
custom_action = (
<span key={ 'action-'+index } className="trash">
{(index > 0) ? ' | ' : ''}
{(!is_first) ? ' | ' : ''}
<a
href="javascript:;"
onClick={ this.handleTrashItem.bind(
null,
this.props.item.id
) }>
Trash
{MailPoet.I18n.t('trash')}
</a>
</span>
);
} else if(action.refresh) {
return (
custom_action = (
<span
onClick={ this.props.onRefreshItems }
key={ 'action-'+index } className={ action.name }>
{(index > 0) ? ' | ' : ''}
{(!is_first) ? ' | ' : ''}
{ action.link(this.props.item) }
</span>
);
} else if(action.link) {
return (
custom_action = (
<span
key={ 'action-'+index } className={ action.name }>
{(index > 0) ? ' | ' : ''}
{(!is_first) ? ' | ' : ''}
{ action.link(this.props.item) }
</span>
);
} else {
return (
custom_action = (
<span
key={ 'action-'+index } className={ action.name }>
{(index > 0) ? ' | ' : ''}
{(!is_first) ? ' | ' : ''}
<a href="javascript:;" onClick={
(action.onClick !== undefined)
? action.onClick.bind(null,
@ -132,11 +135,17 @@ define(
</span>
);
}
if(custom_action !== null && is_first === true) {
is_first = false;
}
return custom_action;
}.bind(this));
} else {
item_actions = (
<span className="edit">
<Link to={ `/edit/${ this.props.item.id }` }>Edit</Link>
<Link to={ `/edit/${ this.props.item.id }` }>{MailPoet.I18n.t('edit')}</Link>
</span>
);
}
@ -152,7 +161,7 @@ define(
null,
this.props.item.id
)}
>Restore</a>
>{MailPoet.I18n.t('restore')}</a>
</span>
{ ' | ' }
<span className="delete">
@ -163,13 +172,13 @@ define(
null,
this.props.item.id
)}
>Delete permanently</a>
>{MailPoet.I18n.t('deletePermanently')}</a>
</span>
</div>
<button
onClick={ this.handleToggleItem.bind(null, this.props.item.id) }
className="toggle-row" type="button">
<span className="screen-reader-text">Show more details</span>
<span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span>
</button>
</div>
);
@ -182,7 +191,7 @@ define(
<button
onClick={ this.handleToggleItem.bind(null, this.props.item.id) }
className="toggle-row" type="button">
<span className="screen-reader-text">Show more details</span>
<span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span>
</button>
</div>
);
@ -214,8 +223,8 @@ define(
className="colspanchange">
{
(this.props.loading === true)
? MailPoetI18n.loadingItems
: MailPoetI18n.noItemsFound
? MailPoet.I18n.t('loadingItems')
: MailPoet.I18n.t('noItemsFound')
}
</td>
</tr>
@ -241,8 +250,8 @@ define(
}>
{
(this.props.selection !== 'all')
? MailPoetI18n.selectAllLabel
: MailPoetI18n.selectedAllLabel.replace(
? MailPoet.I18n.t('selectAllLabel')
: MailPoet.I18n.t('selectedAllLabel').replace(
'%d',
this.props.count
)
@ -252,8 +261,8 @@ define(
onClick={ this.props.onSelectAll }
href="javascript:;">{
(this.props.selection !== 'all')
? MailPoetI18n.selectAllLink
: MailPoetI18n.clearSelection
? MailPoet.I18n.t('selectAllLink')
: MailPoet.I18n.t('clearSelection')
}</a>
</td>
</tr>
@ -504,10 +513,21 @@ define(
this.getItems();
}.bind(this));
},
handleEmptyTrash: function() {
this.handleBulkAction('all', {
action: 'delete',
group: 'trash'
}, function(response) {
MailPoet.Notice.success(
MailPoet.I18n.t('permanentlyDeleted').replace('%d', response)
);
});
},
handleBulkAction: function(selected_ids, params, callback) {
if(
this.state.selection === false
&& this.state.selected_ids.length === 0
&& selected_ids !== 'all'
) {
return;
}
@ -520,8 +540,10 @@ define(
limit: 0,
filter: this.state.filter,
group: this.state.group,
search: this.state.search,
selection: selected_ids
search: this.state.search
}
if(selected_ids !== 'all') {
data.listing.selection = selected_ids;
}
MailPoet.Ajax.post({
@ -656,12 +678,12 @@ define(
bulk_actions = [
{
name: 'restore',
label: 'Restore',
label: MailPoet.I18n.t('restore'),
onSuccess: this.props.messages.onRestore
},
{
name: 'delete',
label: 'Delete permanently',
label: MailPoet.I18n.t('deletePermanently'),
onSuccess: this.props.messages.onDelete
}
];
@ -715,7 +737,10 @@ define(
<ListingFilters
filters={ this.state.filters }
filter={ this.state.filter }
onSelectFilter={ this.handleFilter } />
group={ this.state.group }
onSelectFilter={ this.handleFilter }
onEmptyTrash={ this.handleEmptyTrash }
/>
<ListingPages
count={ this.state.count }
page={ this.state.page }

View File

@ -1,4 +1,12 @@
define(['react', 'classnames'], function(React, classNames) {
define([
'react',
'classnames',
'mailpoet'
], function(
React,
classNames,
MailPoet
) {
var ListingPages = React.createClass({
getInitialState: function() {
@ -7,7 +15,11 @@ define(['react', 'classnames'], function(React, classNames) {
}
},
setPage: function(page) {
this.props.onSetPage(page);
this.setState({
page: null
}, function () {
this.props.onSetPage(this.constrainPage(page));
}.bind(this));
},
setFirstPage: function() {
this.setPage(1);
@ -16,10 +28,14 @@ define(['react', 'classnames'], function(React, classNames) {
this.setPage(this.getLastPage());
},
setPreviousPage: function() {
this.setPage(this.constrainPage(this.props.page - 1));
this.setPage(this.constrainPage(
parseInt(this.props.page, 10) - 1)
);
},
setNextPage: function() {
this.setPage(this.constrainPage(this.props.page + 1));
this.setPage(this.constrainPage(
parseInt(this.props.page, 10) + 1)
);
},
constrainPage: function(page) {
return Math.min(Math.max(1, Math.abs(~~page)), this.getLastPage());
@ -27,14 +43,16 @@ define(['react', 'classnames'], function(React, classNames) {
handleSetManualPage: function(e) {
if(e.which === 13) {
this.setPage(this.state.page);
this.setState({ page: null });
}
},
handleChangeManualPage: function(e) {
this.setState({
page: this.constrainPage(e.target.value)
page: e.target.value
});
},
handleBlurManualPage: function(e) {
this.setPage(e.target.value);
},
getLastPage: function() {
return Math.ceil(this.props.count / this.props.limit);
},
@ -62,7 +80,7 @@ define(['react', 'classnames'], function(React, classNames) {
<a href="javascript:;"
onClick={ this.setPreviousPage }
className="prev-page">
<span className="screen-reader-text">Previous page</span>
<span className="screen-reader-text">{MailPoet.I18n.t('previousPage')}</span>
<span aria-hidden="true"></span>
</a>
);
@ -73,7 +91,7 @@ define(['react', 'classnames'], function(React, classNames) {
<a href="javascript:;"
onClick={ this.setFirstPage }
className="first-page">
<span className="screen-reader-text">First page</span>
<span className="screen-reader-text">{MailPoet.I18n.t('firstPage')}</span>
<span aria-hidden="true">«</span>
</a>
);
@ -84,7 +102,7 @@ define(['react', 'classnames'], function(React, classNames) {
<a href="javascript:;"
onClick={ this.setNextPage }
className="next-page">
<span className="screen-reader-text">Next page</span>
<span className="screen-reader-text">{MailPoet.I18n.t('nextPage')}</span>
<span aria-hidden="true"></span>
</a>
);
@ -95,12 +113,17 @@ define(['react', 'classnames'], function(React, classNames) {
<a href="javascript:;"
onClick={ this.setLastPage }
className="last-page">
<span className="screen-reader-text">Last page</span>
<span className="screen-reader-text">{MailPoet.I18n.t('lastPage')}</span>
<span aria-hidden="true">»</span>
</a>
);
}
let pageValue = this.props.page;
if(this.state.page !== null) {
pageValue = this.state.page;
}
pagination = (
<span className="pagination-links">
{firstPage}
@ -110,19 +133,20 @@ define(['react', 'classnames'], function(React, classNames) {
<span className="paging-input">
<label
className="screen-reader-text"
htmlFor="current-page-selector">Current Page</label>
htmlFor="current-page-selector">{MailPoet.I18n.t('currentPage')}</label>
<input
type="text"
onChange={ this.handleChangeManualPage }
onKeyUp={ this.handleSetManualPage }
onBlur={ this.handleBlurManualPage }
aria-describedby="table-paging"
size="1"
size="2"
ref="page"
value={ this.state.page || this.props.page }
value={ pageValue }
name="paged"
id="current-page-selector"
className="current-page" />
&nbsp;of&nbsp;
&nbsp;{MailPoet.I18n.t('pageOutOf')}&nbsp;
<span className="total-pages">
{Math.ceil(this.props.count / this.props.limit)}
</span>
@ -142,7 +166,9 @@ define(['react', 'classnames'], function(React, classNames) {
return (
<div className={ classes }>
<span className="displaying-num">{ this.props.count } items</span>
<span className="displaying-num">{
MailPoet.I18n.t('numberOfItems').replace('%$1d', this.props.count)
}</span>
{ pagination }
</div>
);
@ -151,4 +177,4 @@ define(['react', 'classnames'], function(React, classNames) {
});
return ListingPages;
});
});

View File

@ -1,4 +1,10 @@
define(['react'], function(React) {
define([
'mailpoet',
'react'
], function(
MailPoet,
React
) {
var ListingSearch = React.createClass({
handleSearch: function(e) {
@ -18,7 +24,7 @@ define(['react'], function(React) {
<form name="search" onSubmit={this.handleSearch}>
<p className="search-box">
<label htmlFor="search_input" className="screen-reader-text">
Search
{MailPoet.I18n.t('searchLabel')}
</label>
<input
type="search"
@ -28,7 +34,7 @@ define(['react'], function(React) {
defaultValue={this.props.search} />
<input
type="submit"
defaultValue={MailPoetI18n.searchLabel}
defaultValue={MailPoet.I18n.t('searchLabel')}
className="button" />
</p>
</form>

View File

@ -12,9 +12,19 @@ define([
'newsletter_editor/blocks/button',
'newsletter_editor/blocks/divider',
'newsletter_editor/components/communication',
'mailpoet',
'underscore',
'jquery'
], function(App, BaseBlock, ButtonBlock, DividerBlock, CommunicationComponent, _, jQuery) {
], function(
App,
BaseBlock,
ButtonBlock,
DividerBlock,
CommunicationComponent,
MailPoet,
_,
jQuery
) {
"use strict";
@ -32,10 +42,10 @@ define([
inclusionType: 'include', // 'include'|'exclude'
displayType: 'excerpt', // 'excerpt'|'full'|'titleOnly'
titleFormat: 'h1', // 'h1'|'h2'|'h3'|'ul'
titlePosition: 'inTextBlock', // 'inTextBlock'|'aboveBlock',
titleAlignment: 'left', // 'left'|'center'|'right'
titleIsLink: false, // false|true
imagePadded: true, // true|false
imageFullWidth: false, // true|false
featuredImagePosition: 'belowTitle', // 'aboveTitle'|'belowTitle'|'none'
//imageAlignment: 'centerPadded', // 'centerFull'|'centerPadded'|'left'|'right'|'alternate'|'none'
showAuthor: 'no', // 'no'|'aboveText'|'belowText'
authorPrecededBy: 'Author:',
@ -63,17 +73,17 @@ define([
initialize: function() {
base.BlockView.prototype.initialize.apply(this, arguments);
this.fetchPosts();
this.on('change:amount change:contentType change:terms change:inclusionType change:displayType change:titleFormat change:titlePosition change:titleAlignment change:titleIsLink change:imagePadded change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:sortBy change:showDivider', this._scheduleFetchPosts, this);
this.on('change:amount change:contentType change:terms change:inclusionType change:displayType change:titleFormat change:featuredImagePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:sortBy change:showDivider', this._scheduleFetchPosts, this);
this.listenTo(this.get('readMoreButton'), 'change', this._scheduleFetchPosts);
this.listenTo(this.get('divider'), 'change', this._scheduleFetchPosts);
},
fetchPosts: function() {
var that = this;
CommunicationComponent.getTransformedPosts(this.toJSON()).done(function(content) {
console.log('ALC fetched', arguments);
that.get('_container').get('blocks').reset(content, {parse: true});
that.trigger('postsChanged');
}).fail(function(error) {
console.log('ALC fetchPosts error', arguments);
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts'));
});
},
/**
@ -81,7 +91,7 @@ define([
* ALC posts on each model change
*/
_scheduleFetchPosts: function() {
var timeout = 2000,
var timeout = 500,
that = this;
if (this._fetchPostsTimer !== undefined) {
clearTimeout(this._fetchPostsTimer);
@ -100,6 +110,11 @@ define([
toolsRegion: '.mailpoet_tools',
postsRegion: '.mailpoet_automated_latest_content_block_posts',
},
modelEvents: _.extend(
_.omit(base.BlockView.prototype.modelEvents, 'change'),
{
'postsChanged': 'render',
}),
events: _.extend(base.BlockView.prototype.events, {
'click .mailpoet_automated_latest_content_block_overlay': 'showSettings',
}),
@ -137,9 +152,9 @@ define([
"keyup .mailpoet_automated_latest_content_show_amount": _.partial(this.changeField, "amount"),
"change .mailpoet_automated_latest_content_content_type": _.partial(this.changeField, "contentType"),
"change .mailpoet_automated_latest_content_include_or_exclude": _.partial(this.changeField, "inclusionType"),
"change .mailpoet_automated_latest_content_title_position": _.partial(this.changeField, "titlePosition"),
"change .mailpoet_automated_latest_content_title_alignment": _.partial(this.changeField, "titleAlignment"),
"change .mailpoet_automated_latest_content_image_padded": _.partial(this.changeBoolField, "imagePadded"),
"change .mailpoet_automated_latest_content_image_full_width": _.partial(this.changeBoolField, "imageFullWidth"),
"change .mailpoet_automated_latest_content_featured_image_position": _.partial(this.changeField, "featuredImagePosition"),
"change .mailpoet_automated_latest_content_show_author": _.partial(this.changeField, "showAuthor"),
"keyup .mailpoet_automated_latest_content_author_preceded_by": _.partial(this.changeField, "authorPrecededBy"),
"change .mailpoet_automated_latest_content_show_categories": _.partial(this.changeField, "showCategories"),
@ -268,12 +283,13 @@ define([
},
changeDisplayType: function(event) {
var value = jQuery(event.target).val();
if (value == 'titleOnly') {
this.$('.mailpoet_automated_latest_content_title_position_container').addClass('mailpoet_hidden');
this.$('.mailpoet_automated_latest_content_title_as_list').removeClass('mailpoet_hidden');
this.$('.mailpoet_automated_latest_content_image_full_width_option').addClass('mailpoet_hidden');
} else {
this.$('.mailpoet_automated_latest_content_title_position_container').removeClass('mailpoet_hidden');
this.$('.mailpoet_automated_latest_content_title_as_list').addClass('mailpoet_hidden');
this.$('.mailpoet_automated_latest_content_image_full_width_option').removeClass('mailpoet_hidden');
// Reset titleFormat if it was set to List when switching away from displayType=titleOnly
if (this.model.get('titleFormat') === 'ul') {
@ -282,6 +298,12 @@ define([
this.$('.mailpoet_automated_latest_content_title_as_link').removeClass('mailpoet_hidden');
}
}
if (value === 'excerpt') {
this.$('.mailpoet_automated_latest_content_featured_image_position_container').removeClass('mailpoet_hidden');
} else {
this.$('.mailpoet_automated_latest_content_featured_image_position_container').addClass('mailpoet_hidden');
}
this.changeField('displayType', event);
},
changeTitleFormat: function(event) {

View File

@ -128,24 +128,37 @@ define([
}
},
deleteBlock: function() {
this.transitionOut().done(function() {
this.transitionOut().then(function() {
this.model.destroy();
}.bind(this));
},
transitionIn: function() {
return this._transition('mailpoet_block_transition_in');
return this._transition('slideDown', 'fadeIn', 'easeOut');
},
transitionOut: function() {
return this._transition('mailpoet_block_transition_out');
return this._transition('slideUp', 'fadeOut', 'easeIn');
},
_transition: function(className) {
_transition: function(slideDirection, fadeDirection, easing) {
var promise = jQuery.Deferred();
this.$el.addClass(className);
this.$el.one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd animationend', function() {
this.$el.removeClass(className);
promise.resolve();
}.bind(this));
this.$el.velocity(
slideDirection,
{
duration: 250,
easing: easing,
complete: function() {
promise.resolve();
}.bind(this),
}
).velocity(
fadeDirection,
{
duration: 250,
easing: easing,
queue: false, // Do not enqueue, trigger animation in parallel
}
);
return promise;
},
});

View File

@ -32,6 +32,7 @@ define([
fontColor: '#000000',
fontFamily: 'Arial',
fontSize: '16px',
fontWeight: 'normal', // 'normal'|'bold'
textAlign: 'center',
},
},
@ -72,6 +73,7 @@ define([
"change .mailpoet_field_button_font_size": _.partial(this.changeField, "styles.block.fontSize"),
"change .mailpoet_field_button_background_color": _.partial(this.changeColorField, "styles.block.backgroundColor"),
"change .mailpoet_field_button_border_color": _.partial(this.changeColorField, "styles.block.borderColor"),
"change .mailpoet_field_button_font_weight": "changeFontWeight",
"input .mailpoet_field_button_border_width": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width_input', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
"change .mailpoet_field_button_border_width": _.partial(this.updateValueAndCall, '.mailpoet_field_button_border_width_input', _.partial(this.changePixelField, "styles.block.borderWidth").bind(this)),
@ -128,6 +130,13 @@ define([
this.$(fieldToUpdate).val(jQuery(event.target).val());
callable(event);
},
changeFontWeight: function(event) {
var checked = !!jQuery(event.target).prop('checked');
this.model.set(
'styles.block.fontWeight',
(checked) ? jQuery(event.target).val() : 'normal'
);
}
});
Module.ButtonWidgetView = base.WidgetView.extend({

View File

@ -248,20 +248,32 @@ define([
}.bind(this));
},
transitionIn: function() {
return this._transition('mailpoet_block_transition_in');
return this._transition('slideDown', 'fadeIn', 'easeIn');
},
transitionOut: function() {
return this._transition('mailpoet_block_transition_out');
return this._transition('slideUp', 'fadeOut', 'easeOut');
},
_transition: function(className) {
var that = this,
promise = jQuery.Deferred();
_transition: function(slideDirection, fadeDirection, easing) {
var promise = jQuery.Deferred();
this.$el.velocity(
slideDirection,
{
duration: 250,
easing: easing,
complete: function() {
promise.resolve();
}.bind(this),
}
).velocity(
fadeDirection,
{
duration: 250,
easing: easing,
queue: false, // Do not enqueue, trigger animation in parallel
}
);
this.$el.addClass(className);
this.$el.one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd animationend', function() {
that.$el.removeClass(className);
promise.resolve();
});
return promise;
},
});

View File

@ -16,7 +16,7 @@ define([
defaults: function() {
return this._getDefaults({
type: 'footer',
text: '<a href="[unsubscribeUrl]">Unsubscribe</a> | <a href="[manageSubscriptionUrl]">Manage subscription</a><br /><b>Add your postal address here!</b>',
text: '<a href="[subscription:unsubscribe_url]">Unsubscribe</a> | <a href="[subscription:manage_url]">Manage subscription</a><br /><b>Add your postal address here!</b>',
styles: {
block: {
backgroundColor: 'transparent',
@ -41,7 +41,7 @@ define([
getTemplate: function() { return templates.footerBlock; },
modelEvents: _.extend({
'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render',
}, base.BlockView.prototype.modelEvents),
}, _.omit(base.BlockView.prototype.modelEvents, 'change')),
onDragSubstituteBy: function() { return Module.FooterWidgetView; },
onRender: function() {
this.toolsView = new Module.FooterBlockToolsView({ model: this.model });
@ -60,11 +60,9 @@ define([
valid_elements: "p[class|style],span[class|style],a[href|class|title|target|style],strong[class|style],em[class|style],strike,br",
invalid_elements: "script",
style_formats: [
{title: 'Paragraph', block: 'p'},
],
block_formats: 'Paragraph=p',
plugins: "link textcolor mailpoet_custom_fields",
plugins: "link textcolor colorpicker mailpoet_custom_fields",
setup: function(editor) {
editor.on('change', function(e) {
@ -81,7 +79,7 @@ define([
},
mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(),
mailpoet_custom_fields_window_title: App.getConfig().get('translations.customFieldsWindowTitle'),
mailpoet_custom_fields_window_title: MailPoet.I18n.t('customFieldsWindowTitle'),
});
},
});

View File

@ -16,7 +16,7 @@ define([
defaults: function() {
return this._getDefaults({
type: 'header',
text: 'Display problems? <a href="[viewInBrowserUrl]">View it in your browser</a>',
text: 'Display problems? <a href="[newsletter:view_in_browser_url]">View it in your browser</a>',
styles: {
block: {
backgroundColor: 'transparent',
@ -41,7 +41,7 @@ define([
getTemplate: function() { return templates.headerBlock; },
modelEvents: _.extend({
'change:styles.block.backgroundColor change:styles.text.fontColor change:styles.text.fontFamily change:styles.text.fontSize change:styles.text.textAlign change:styles.link.fontColor change:styles.link.textDecoration': 'render',
}, base.BlockView.prototype.modelEvents),
}, _.omit(base.BlockView.prototype.modelEvents, 'change')),
onDragSubstituteBy: function() { return Module.HeaderWidgetView; },
onRender: function() {
this.toolsView = new Module.HeaderBlockToolsView({ model: this.model });
@ -60,11 +60,9 @@ define([
valid_elements: "p[class|style],span[class|style],a[href|class|title|target|style],strong[class|style],em[class|style],strike,br",
invalid_elements: "script",
style_formats: [
{title: 'Paragraph', block: 'p'},
],
block_formats: 'Paragraph=p',
plugins: "link textcolor mailpoet_custom_fields",
plugins: "link textcolor colorpicker mailpoet_custom_fields",
setup: function(editor) {
editor.on('change', function(e) {
@ -81,7 +79,7 @@ define([
},
mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(),
mailpoet_custom_fields_window_title: App.getConfig().get('translations.customFieldsWindowTitle'),
mailpoet_custom_fields_window_title: MailPoet.I18n.t('customFieldsWindowTitle'),
});
},
});

View File

@ -20,7 +20,7 @@ define([
link: 'http://example.org',
src: 'no-image.png',
alt: 'An image of...',
padded: true, // true | false - Padded or full width
fullWidth: true, // true | false
width: '64px',
height: '64px',
styles: {
@ -45,10 +45,10 @@ define([
this.toolsView = new Module.ImageBlockToolsView({ model: this.model });
this.toolsRegion.show(this.toolsView);
if (this.model.get('padded')) {
this.$el.removeClass('mailpoet_full_image');
} else {
if (this.model.get('fullWidth')) {
this.$el.addClass('mailpoet_full_image');
} else {
this.$el.removeClass('mailpoet_full_image');
}
},
});
@ -64,7 +64,7 @@ define([
"keyup .mailpoet_field_image_link": _.partial(this.changeField, "link"),
"keyup .mailpoet_field_image_address": _.partial(this.changeField, "src"),
"keyup .mailpoet_field_image_alt_text": _.partial(this.changeField, "alt"),
"change .mailpoet_field_image_padded": _.partial(this.changeBoolCheckboxField, "padded"),
"change .mailpoet_field_image_full_width": _.partial(this.changeBoolCheckboxField, "fullWidth"),
"change .mailpoet_field_image_alignment": _.partial(this.changeField, "styles.block.textAlign"),
"click .mailpoet_field_image_select_another_image": "showMediaManager",
"click .mailpoet_done_editing": "close",

View File

@ -43,10 +43,10 @@ define([
inclusionType: 'include', // 'include'|'exclude'
displayType: 'excerpt', // 'excerpt'|'full'|'titleOnly'
titleFormat: 'h1', // 'h1'|'h2'|'h3'|'ul'
titlePosition: 'inTextBlock', // 'inTextBlock'|'aboveBlock',
titleAlignment: 'left', // 'left'|'center'|'right'
titleIsLink: false, // false|true
imagePadded: true, // true|false
imageFullWidth: false, // true|false
featuredImagePosition: 'belowTitle', // 'aboveTitle'|'belowTitle'|'none'
//imageAlignment: 'centerPadded', // 'centerFull'|'centerPadded'|'left'|'right'|'alternate'|'none'
showAuthor: 'no', // 'no'|'aboveText'|'belowText'
authorPrecededBy: 'Author:',
@ -88,7 +88,7 @@ define([
this.on('change:amount change:contentType change:terms change:inclusionType change:postStatus change:search change:sortBy', refreshAvailablePosts);
this.listenTo(this.get('_selectedPosts'), 'add remove reset', refreshTransformedPosts);
this.on('change:displayType change:titleFormat change:titlePosition change:titleAlignment change:titleIsLink change:imagePadded change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:showDivider', refreshTransformedPosts);
this.on('change:displayType change:titleFormat change:featuredImagePosition change:titleAlignment change:titleIsLink change:imageFullWidth change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:showDivider', refreshTransformedPosts);
this.listenTo(this.get('readMoreButton'), 'change', refreshTransformedPosts);
this.listenTo(this.get('divider'), 'change', refreshTransformedPosts);
@ -97,12 +97,11 @@ define([
fetchAvailablePosts: function() {
var that = this;
CommunicationComponent.getPosts(this.toJSON()).done(function(posts) {
console.log('Posts fetched', arguments);
that.get('_availablePosts').reset(posts);
that.get('_selectedPosts').reset(); // Empty out the collection
that.trigger('change:_availablePosts');
}).fail(function() {
console.log('Posts fetchPosts error', arguments);
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchAvailablePosts'));
});
},
_refreshTransformedPosts: function() {
@ -112,15 +111,14 @@ define([
data.posts = this.get('_selectedPosts').pluck('ID');
if (data.posts.length === 0) {
this.get('_transformedPosts.blocks').reset();
this.get('_transformedPosts').get('blocks').reset();
return;
}
CommunicationComponent.getTransformedPosts(data).done(function(posts) {
console.log('Transformed posts fetched', arguments);
that.get('_transformedPosts').get('blocks').reset(posts, {parse: true});
}).fail(function() {
console.log('Posts _refreshTransformedPosts error', arguments);
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts'));
});
},
_insertSelectedPosts: function() {
@ -134,10 +132,9 @@ define([
if (data.posts.length === 0) return;
CommunicationComponent.getTransformedPosts(data).done(function(posts) {
console.log('Available posts fetched', arguments);
collection.add(posts, { at: index });
}).fail(function() {
console.log('Posts fetchPosts error', arguments);
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts'));
});
},
});
@ -397,9 +394,9 @@ define([
"keyup .mailpoet_posts_show_amount": _.partial(this.changeField, "amount"),
"change .mailpoet_posts_content_type": _.partial(this.changeField, "contentType"),
"change .mailpoet_posts_include_or_exclude": _.partial(this.changeField, "inclusionType"),
"change .mailpoet_posts_title_position": _.partial(this.changeField, "titlePosition"),
"change .mailpoet_posts_title_alignment": _.partial(this.changeField, "titleAlignment"),
"change .mailpoet_posts_image_padded": _.partial(this.changeBoolField, "imagePadded"),
"change .mailpoet_posts_image_full_width": _.partial(this.changeBoolField, "imageFullWidth"),
"change .mailpoet_posts_featured_image_position": _.partial(this.changeField, "featuredImagePosition"),
"change .mailpoet_posts_show_author": _.partial(this.changeField, "showAuthor"),
"keyup .mailpoet_posts_author_preceded_by": _.partial(this.changeField, "authorPrecededBy"),
"change .mailpoet_posts_show_categories": _.partial(this.changeField, "showCategories"),
@ -451,11 +448,11 @@ define([
changeDisplayType: function(event) {
var value = jQuery(event.target).val();
if (value == 'titleOnly') {
this.$('.mailpoet_posts_title_position_container').addClass('mailpoet_hidden');
this.$('.mailpoet_posts_title_as_list').removeClass('mailpoet_hidden');
this.$('.mailpoet_posts_image_full_width_option').addClass('mailpoet_hidden');
} else {
this.$('.mailpoet_posts_title_position_container').removeClass('mailpoet_hidden');
this.$('.mailpoet_posts_title_as_list').addClass('mailpoet_hidden');
this.$('.mailpoet_posts_image_full_width_option').removeClass('mailpoet_hidden');
// Reset titleFormat if it was set to List when switching away from displayType=titleOnly
if (this.model.get('titleFormat') === 'ul') {
@ -464,6 +461,13 @@ define([
this.$('.mailpoet_posts_title_as_link').removeClass('mailpoet_hidden');
}
}
if (value === 'excerpt') {
this.$('.mailpoet_posts_featured_image_position_container').removeClass('mailpoet_hidden');
} else {
this.$('.mailpoet_posts_featured_image_position_container').addClass('mailpoet_hidden');
}
this.changeField('displayType', event);
},
changeTitleFormat: function(event) {

View File

@ -208,20 +208,32 @@ define([
}.bind(this));
},
transitionIn: function() {
return this._transition('mailpoet_block_transition_in');
return this._transition('slideDown', 'fadeIn', 'easeIn');
},
transitionOut: function() {
return this._transition('mailpoet_block_transition_out');
return this._transition('slideUp', 'fadeOut', 'easeOut');
},
_transition: function(className) {
var that = this,
promise = jQuery.Deferred();
_transition: function(slideDirection, fadeDirection, easing) {
var promise = jQuery.Deferred();
this.$el.velocity(
slideDirection,
{
duration: 250,
easing: easing,
complete: function() {
promise.resolve();
}.bind(this),
}
).velocity(
fadeDirection,
{
duration: 250,
easing: easing,
queue: false, // Do not enqueue, trigger animation in parallel
}
);
this.$el.addClass(className);
this.$el.one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd animationend', function() {
that.$el.removeClass(className);
promise.resolve();
});
return promise;
},
});

View File

@ -52,21 +52,15 @@ define([
inline: true,
menubar: false,
toolbar1: "styleselect bold italic forecolor | link unlink",
toolbar1: "formatselect bold italic forecolor | link unlink",
toolbar2: "alignleft aligncenter alignright alignjustify | bullist numlist blockquote | code mailpoet_custom_fields",
//forced_root_block: 'p',
valid_elements: "p[class|style],span[class|style],a[href|class|title|target|style],h1[class|style],h2[class|style],h3[class|style],ol[class|style],ul[class|style],li[class|style],strong[class|style],em[class|style],strike,br,blockquote[class|style],table[class|style],tr[class|style],th[class|style],td[class|style]",
invalid_elements: "script",
style_formats: [
{title: 'Heading 1', block: 'h1'},
{title: 'Heading 2', block: 'h2'},
{title: 'Heading 3', block: 'h3'},
block_formats: 'Heading 1=h1;Heading 2=h2;Heading 3=h3;Paragraph=p',
{title: 'Paragraph', block: 'p'},
],
plugins: "link code textcolor mailpoet_custom_fields",
plugins: "link code textcolor colorpicker mailpoet_custom_fields",
setup: function(editor) {
editor.on('change', function(e) {
@ -83,7 +77,7 @@ define([
},
mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(),
mailpoet_custom_fields_window_title: App.getConfig().get('translations.customFieldsWindowTitle'),
mailpoet_custom_fields_window_title: MailPoet.I18n.t('customFieldsWindowTitle'),
});
}
},

View File

@ -9,7 +9,7 @@ define([
Module._query = function(args) {
return MailPoet.Ajax.post({
endpoint: 'wordpress',
endpoint: 'automatedLatestContent',
action: args.action,
data: args.options || {},
});
@ -63,16 +63,17 @@ define([
};
Module.saveNewsletter = function(options) {
return Module._query({
return MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'save',
options: options,
data: options || {},
});
};
Module.previewNewsletter = function(options) {
return MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'preview',
action: 'sendPreview',
data: options || {},
});
};

View File

@ -10,7 +10,6 @@ define([
availableStyles: {},
socialIcons: {},
blockDefaults: {},
translations: {},
sidepanelWidth: '331px',
validation: {},
urls: {},

View File

@ -11,7 +11,7 @@ define([
// Does not hold newsletter content nor newsletter styles, those are
// handled by other components.
Module.NewsletterModel = SuperModel.extend({
stale: ['body'],
stale: ['body', 'created_at', 'deleted_at', 'updated_at'],
initialize: function(options) {
this.on('change', function() {
App.getChannel().trigger('autoSave');

View File

@ -1,5 +1,6 @@
define([
'newsletter_editor/App',
'newsletter_editor/components/communication',
'mailpoet',
'notice',
'backbone',
@ -8,7 +9,18 @@ define([
'blob',
'filesaver',
'html2canvas'
], function(App, MailPoet, Notice, Backbone, Marionette, jQuery, Blob, FileSaver, html2canvas) {
], function(
App,
CommunicationComponent,
MailPoet,
Notice,
Backbone,
Marionette,
jQuery,
Blob,
FileSaver,
html2canvas
) {
"use strict";
@ -17,26 +29,33 @@ define([
// Save editor contents to server
Module.save = function() {
App.getChannel().trigger('beforeEditorSave');
var json = App.toJSON();
// Stringify to enable transmission of primitive non-string value types
if (!_.isUndefined(json.body)) {
json.body = JSON.stringify(json.body);
}
App.getChannel().trigger('beforeEditorSave', json);
// save newsletter
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'save',
data: json,
}).done(function(response) {
CommunicationComponent.saveNewsletter(json).done(function(response) {
if(response.success !== undefined && response.success === true) {
// TODO: Handle translations
//MailPoet.Notice.success("<?php _e('Newsletter has been saved.'); ?>");
} else if(response.error !== undefined) {
if(response.error.length === 0) {
// TODO: Handle translations
MailPoet.Notice.error("<?php _e('An unknown error occurred, please check your settings.'); ?>");
MailPoet.Notice.error(
"An unknown error occurred, please check your settings.",
{
scroll: true,
}
);
} else {
$(response.error).each(function(i, error) {
MailPoet.Notice.error(error);
MailPoet.Notice.error(error, { scroll: true });
});
}
}
@ -48,7 +67,28 @@ define([
};
Module.getThumbnail = function(element, options) {
return html2canvas(element, options || {});
var promise = html2canvas(element, options || {});
return promise.then(function(oldCanvas) {
// Temporary workaround for html2canvas-alpha2.
// Removes 1px left transparent border from resulting canvas.
var oldContext = oldCanvas.getContext('2d'),
newCanvas = document.createElement("canvas"),
newContext = newCanvas.getContext("2d"),
leftBorderWidth = 1;
newCanvas.width = oldCanvas.width;
newCanvas.height = oldCanvas.height;
newContext.drawImage(
oldCanvas,
leftBorderWidth, 0, oldCanvas.width - leftBorderWidth, oldCanvas.height,
0, 0, oldCanvas.width, oldCanvas.height
);
return newCanvas;
});
};
Module.saveTemplate = function(options) {
@ -58,7 +98,7 @@ define([
promise.then(function(thumbnail) {
var data = _.extend(options || {}, {
thumbnail: thumbnail.toDataURL('image/jpeg'),
body: App.getBody(),
body: JSON.stringify(App.getBody()),
});
return MailPoet.Ajax.post({
@ -151,37 +191,38 @@ define([
if (templateName === '') {
MailPoet.Notice.error(
App.getConfig().get('translations.templateNameMissing'),
MailPoet.I18n.t('templateNameMissing'),
{
positionAfter: that.$el,
scroll: true,
}
);
} else if (templateDescription === '') {
MailPoet.Notice.error(
App.getConfig().get('translations.templateDescriptionMissing'),
MailPoet.I18n.t('templateDescriptionMissing'),
{
positionAfter: that.$el,
scroll: true,
}
);
} else {
console.log('Saving template with ', templateName, templateDescription);
Module.saveTemplate({
name: templateName,
description: templateDescription,
}).done(function() {
console.log('Template saved', arguments);
MailPoet.Notice.success(
App.getConfig().get('translations.templateSaved'),
MailPoet.I18n.t('templateSaved'),
{
positionAfter: that.$el,
scroll: true,
}
);
}).fail(function() {
console.log('Template save failed', arguments);
MailPoet.Notice.error(
App.getConfig().get('translations.templateSaveFailed'),
MailPoet.I18n.t('templateSaveFailed'),
{
positionAfter: that.$el,
scroll: true,
}
);
});
@ -203,20 +244,21 @@ define([
if (templateName === '') {
MailPoet.Notice.error(
App.getConfig().get('translations.templateNameMissing'),
MailPoet.I18n.t('templateNameMissing'),
{
positionAfter: that.$el,
scroll: true,
}
);
} else if (templateDescription === '') {
MailPoet.Notice.error(
App.getConfig().get('translations.templateDescriptionMissing'),
MailPoet.I18n.t('templateDescriptionMissing'),
{
positionAfter: that.$el,
scroll: true,
}
);
} else {
console.log('Exporting template with ', templateName, templateDescription);
Module.exportTemplate({
name: templateName,
description: templateDescription,
@ -232,7 +274,6 @@ define([
next: function() {
this.hideOptionContents();
if(!this.$('.mailpoet_save_next').hasClass('button-disabled')) {
console.log('Next');
window.location.href = App.getConfig().get('urls.send');
}
},
@ -243,8 +284,8 @@ define([
}
if (App.getConfig().get('validation.validateUnsubscribeLinkPresent') &&
JSON.stringify(jsonObject).indexOf("[unsubscribeUrl]") < 0) {
this.showValidationError(App.getConfig().get('translations.unsubscribeLinkMissing'));
JSON.stringify(jsonObject).indexOf("[subscription:unsubscribe_url]") < 0) {
this.showValidationError(MailPoet.I18n.t('unsubscribeLinkMissing'));
return;
}

View File

@ -1,13 +1,24 @@
define([
'newsletter_editor/App',
'newsletter_editor/components/communication',
'mailpoet',
'backbone',
'backbone.marionette',
'backbone.supermodel',
'underscore',
'jquery',
'sticky-kit'
], function(App, CommunicationComponent, Backbone, Marionette, SuperModel, _, jQuery, StickyKit) {
], function(
App,
CommunicationComponent,
MailPoet,
Backbone,
Marionette,
SuperModel,
_,
jQuery,
StickyKit
) {
"use strict";
@ -51,8 +62,33 @@ define([
},
events: {
'click .mailpoet_sidebar_region h3, .mailpoet_sidebar_region .handlediv': function(event) {
this.$el.find('.mailpoet_sidebar_region').addClass('closed');
this.$el.find(event.target).parent().parent().removeClass('closed');
var $openRegion = this.$el.find('.mailpoet_sidebar_region:not(.closed)'),
$targetRegion = this.$el.find(event.target).closest('.mailpoet_sidebar_region');
if ($openRegion.get(0) === $targetRegion.get(0)) {
return;
}
$openRegion.find('.mailpoet_region_content').velocity(
'slideUp',
{
duration: 250,
easing: "easeOut",
complete: function() {
$openRegion.addClass('closed');
}.bind(this)
}
);
$targetRegion.find('.mailpoet_region_content').velocity(
'slideDown',
{
duration: 250,
easing: "easeIn",
complete: function() {
$targetRegion.removeClass('closed');
},
}
);
},
},
initialize: function(options) {
@ -200,43 +236,63 @@ define([
showPreview: function() {
var json = App.toJSON();
// Stringify to enable transmission of primitive non-string value types
if (!_.isUndefined(json.body)) {
json.body = JSON.stringify(json.body);
}
MailPoet.Modal.loading(true);
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'render',
data: json,
}).done(function(response){
console.log('Should open a new window');
window.open('data:text/html,' + encodeURIComponent(response.rendered_body), '_blank');
MailPoet.Modal.loading(false);
window.open('data:text/html;charset=utf-8,' + encodeURIComponent(response.rendered_body), '_blank');
}).fail(function(error) {
console.log('Preview error', json);
MailPoet.Modal.loading(false);
alert('Something went wrong, check console');
});
},
sendPreview: function() {
// testing sending method
console.log('trying to send a preview');
// get form data
var $emailField = this.$('#mailpoet_preview_to_email');
var data = {
from_name: this.$('#mailpoet_preview_from_name').val(),
from_email: this.$('#mailpoet_preview_from_email').val(),
to_email: this.$('#mailpoet_preview_to_email').val(),
newsletter: App.newsletterId,
subscriber: $emailField.val(),
id: App.getNewsletter().get('id'),
};
if (data.subscriber.length <= 0) {
MailPoet.Notice.error(
MailPoet.I18n.t('newsletterPreviewEmailMissing'),
{
positionAfter: $emailField,
scroll: true,
}
);
return false;
}
// send test email
MailPoet.Modal.loading(true);
// TODO: Migrate logic to new AJAX format
CommunicationComponent.previewNewsletter(data).done(function(response) {
if(response.success !== undefined && response.success === true) {
MailPoet.Notice.success(App.getConfig().get('translations.testEmailSent'));
} else if(response.error !== undefined) {
if(response.error.length === 0) {
MailPoet.Notice.error(App.getConfig().get('translations.unknownErrorOccurred'));
} else {
$(response.error).each(function(i, error) {
MailPoet.Notice.error(error);
if(response.result !== undefined && response.result === true) {
MailPoet.Notice.success(MailPoet.I18n.t('newsletterPreviewSent'), { scroll: true });
} else {
if (_.isArray(response.errors)) {
response.errors.map(function(error) {
MailPoet.Notice.error(error, { scroll: true });
});
} else {
MailPoet.Notice.error(
MailPoet.I18n.t('newsletterPreviewFailedToSend'),
{
scroll: true,
static: true,
}
);
}
}
MailPoet.Modal.loading(false);

View File

@ -17,7 +17,7 @@ define([
},
h1: {
fontColor: '#111111',
fontFamily: 'Arial Black',
fontFamily: 'Arial',
fontSize: '40px'
},
h2: {

View File

@ -2,12 +2,14 @@ define(
[
'react',
'react-router',
'classnames'
'classnames',
'mailpoet'
],
function(
React,
Router,
classNames
classNames,
MailPoet
) {
var Link = Router.Link;
@ -21,20 +23,20 @@ define(
steps: [
{
name: 'type',
label: 'Select type',
label: MailPoet.I18n.t('selectType'),
link: '/new'
},
{
name: 'template',
label: 'Template'
label: MailPoet.I18n.t('template')
},
{
name: 'editor',
label: 'Designer'
label: MailPoet.I18n.t('designer')
},
{
name: 'send',
label: 'Send'
label: MailPoet.I18n.t('send')
}
]
};
@ -73,4 +75,4 @@ define(
return Breadcrumb;
}
);
);

View File

@ -4,104 +4,97 @@ define(
'react-router',
'listing/listing.jsx',
'classnames',
'jquery'
'jquery',
'mailpoet'
],
function(
React,
Router,
Listing,
classNames,
jQuery
jQuery,
MailPoet
) {
var Link = Router.Link;
var columns = [
{
name: 'subject',
label: 'Subject',
label: MailPoet.I18n.t('subject'),
sortable: true
},
{
name: 'status',
label: 'Status'
label: MailPoet.I18n.t('status')
},
{
name: 'segments',
label: 'Lists'
label: MailPoet.I18n.t('lists')
},
{
name: 'created_at',
label: 'Created on',
label: MailPoet.I18n.t('createdOn'),
sortable: true
},
{
name: 'updated_at',
label: 'Last modified on',
label: MailPoet.I18n.t('lastModifiedOn'),
sortable: true
}
];
var messages = {
onTrash: function(response) {
var count = ~~response.newsletters;
var count = ~~response;
var message = null;
if(count === 1 || response === true) {
if(count === 1) {
message = (
'1 newsletter was moved to the trash.'
MailPoet.I18n.t('oneNewsletterTrashed')
);
} else if(count > 1) {
} else {
message = (
'%$1d newsletters were moved to the trash.'
MailPoet.I18n.t('multipleNewslettersTrashed')
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
MailPoet.Notice.success(message);
},
onDelete: function(response) {
var count = ~~response.newsletters;
var count = ~~response;
var message = null;
if(count === 1 || response === true) {
if(count === 1) {
message = (
'1 newsletter was permanently deleted.'
MailPoet.I18n.t('oneNewsletterDeleted')
);
} else if(count > 1) {
} else {
message = (
'%$1d newsletters were permanently deleted.'
MailPoet.I18n.t('multipleNewslettersDeleted')
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
MailPoet.Notice.success(message);
},
onRestore: function(response) {
var count = ~~response.newsletters;
var count = ~~response;
var message = null;
if(count === 1 || response === true) {
if(count === 1) {
message = (
'1 newsletter has been restored from the trash.'
MailPoet.I18n.t('oneNewsletterRestored')
);
} else if(count > 1) {
} else {
message = (
'%$1d newsletters have been restored from the trash.'
MailPoet.I18n.t('multipleNewslettersRestored')
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
MailPoet.Notice.success(message);
}
};
var bulk_actions = [
{
name: 'trash',
label: 'Trash',
label: MailPoet.I18n.t('trash'),
onSuccess: messages.onTrash
}
];
@ -112,7 +105,7 @@ define(
link: function(item) {
return (
<a href={ `?page=mailpoet-newsletter-editor&id=${ item.id }` }>
Edit
{MailPoet.I18n.t('edit')}
</a>
);
}
@ -144,9 +137,9 @@ define(
});
},
renderStatus: function(item) {
if(item.queue === null) {
if(!item.queue) {
return (
<span>Not sent yet.</span>
<span>{MailPoet.I18n.t('notSentYet')}</span>
);
} else {
var progressClasses = classNames(
@ -164,9 +157,11 @@ define(
if(item.queue.status === 'completed') {
label = (
<span>
Sent to {
item.queue.count_processed - item.queue.count_failed
} out of { item.queue.count_total }.
{
MailPoet.I18n.t('newsletterQueueCompleted')
.replace("%$1d", item.queue.count_processed - item.queue.count_failed)
.replace("%$2d", item.queue.count_total)
}
</span>
);
} else {
@ -180,14 +175,14 @@ define(
style={{ display: (item.queue.status === 'paused') ? 'inline-block': 'none' }}
href="javascript:;"
onClick={ this.resumeSending.bind(null, item) }
>Resume</a>
>{MailPoet.I18n.t('resume')}</a>
<a
id={ 'pause_'+item.id }
className="button mailpoet_pause"
style={{ display: (item.queue.status === null) ? 'inline-block': 'none' }}
href="javascript:;"
onClick={ this.pauseSending.bind(null, item) }
>Pause</a>
>{MailPoet.I18n.t('pause')}</a>
</span>
);
}
@ -217,10 +212,8 @@ define(
'has-row-actions'
);
var segments = mailpoet_segments.filter(function(segment) {
return (jQuery.inArray(segment.id, newsletter.segments) !== -1);
}).map(function(segment) {
return segment.name;
var segments = newsletter.segments.map(function(segment) {
return segment.name
}).join(', ');
return (
@ -238,10 +231,10 @@ define(
{ segments }
</td>
<td className="column-date" data-colname="Subscribed on">
<abbr>{ newsletter.created_at }</abbr>
<abbr>{ MailPoet.Date.full(newsletter.created_at) }</abbr>
</td>
<td className="column-date" data-colname="Last modified on">
<abbr>{ newsletter.updated_at }</abbr>
<abbr>{ MailPoet.Date.full(newsletter.updated_at) }</abbr>
</td>
</div>
);
@ -250,7 +243,7 @@ define(
return (
<div>
<h2 className="title">
Newsletters <Link className="add-new-h2" to="/new">New</Link>
{MailPoet.I18n.t('pageTitle')} <Link className="add-new-h2" to="/new">{MailPoet.I18n.t('new')}</Link>
</h2>
<Listing
@ -269,4 +262,4 @@ define(
return NewsletterList;
}
);
);

View File

@ -16,22 +16,25 @@ define(
Breadcrumb
) {
var settings = window.mailpoet_settings || {};
var settings = window.mailpoet_settings || {};
var fields = [
{
name: 'subject',
label: 'Subject line',
tip: "Be creative! It's the first thing your subscribers see."+
"Tempt them to open your email.",
type: 'text'
label: MailPoet.I18n.t('subjectLine'),
tip: MailPoet.I18n.t('subjectLineTip'),
type: 'text',
validation: {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('emptySubjectLineError')
}
},
{
name: 'segments',
label: 'Lists',
tip: "The subscriber list that will be used for this campaign.",
label: MailPoet.I18n.t('segments'),
tip: MailPoet.I18n.t('segmentsTip'),
type: 'selection',
placeholder: "Select a list",
placeholder: MailPoet.I18n.t('selectSegmentPlaceholder'),
id: "mailpoet_segments",
endpoint: "segments",
multiple: true,
@ -39,18 +42,19 @@ define(
return !!(!segment.deleted_at);
},
validation: {
'data-parsley-required': true
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('noSegmentsSelectedError')
}
},
{
name: 'sender',
label: 'Sender',
tip: "Name & email of yourself or your company.",
label: MailPoet.I18n.t('sender'),
tip: MailPoet.I18n.t('senderTip'),
fields: [
{
name: 'sender_name',
type: 'text',
placeholder: 'John Doe',
placeholder: MailPoet.I18n.t('senderNamePlaceholder'),
defaultValue: (settings.sender !== undefined) ? settings.sender.name : '',
validation: {
'data-parsley-required': true
@ -59,7 +63,7 @@ define(
{
name: 'sender_address',
type: 'text',
placeholder: 'john.doe@email.com',
placeholder: MailPoet.I18n.t('senderAddressPlaceholder'),
defaultValue: (settings.sender !== undefined) ? settings.sender.address : '',
validation: {
'data-parsley-required': true,
@ -70,21 +74,20 @@ define(
},
{
name: 'reply-to',
label: 'Reply-to',
tip: 'When the subscribers hit "reply" this is who will receive their '+
'email.',
label: MailPoet.I18n.t('replyTo'),
tip: MailPoet.I18n.t('replyToTip'),
inline: true,
fields: [
{
name: 'reply_to_name',
type: 'text',
placeholder: 'John Doe',
placeholder: MailPoet.I18n.t('replyToNamePlaceholder'),
defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.name : '',
},
{
name: 'reply_to_address',
type: 'text',
placeholder: 'john.doe@email.com',
placeholder: MailPoet.I18n.t('replyToAddressPlaceholder'),
defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.address : ''
},
]
@ -93,10 +96,10 @@ define(
var messages = {
onUpdate: function() {
MailPoet.Notice.success('Newsletter successfully updated!');
MailPoet.Notice.success(MailPoet.I18n.t('newsletterUpdated'));
},
onCreate: function() {
MailPoet.Notice.success('Newsletter successfully added!');
MailPoet.Notice.success(MailPoet.I18n.t('newsletterAdded'));
}
};
@ -104,49 +107,54 @@ define(
mixins: [
Router.History
],
componentDidMount: function() {
jQuery('#mailpoet_newsletter').parsley();
},
isValid: function() {
return jQuery('#mailpoet_newsletter').parsley().isValid();
},
handleSend: function() {
if(jQuery('#mailpoet_newsletter').parsley().validate() === true) {
if(!this.isValid()) {
jQuery('#mailpoet_newsletter').parsley().validate();
} else {
MailPoet.Ajax.post({
endpoint: 'sendingQueue',
action: 'add',
data: {
newsletter_id: this.props.params.id,
segments: jQuery('#mailpoet_segments').val()
segments: jQuery('#mailpoet_segments').val(),
sender: {
'name': jQuery('#mailpoet_newsletter [name="sender_name"]').val(),
'address': jQuery('#mailpoet_newsletter [name="sender_address"]').val()
},
reply_to: {
'name': jQuery('#mailpoet_newsletter [name="reply_to_name"]').val(),
'address': jQuery('#mailpoet_newsletter [name="reply_to_address"]').val()
}
}
}).done(function(response) {
if(response.result === true) {
this.history.pushState(null, '/');
MailPoet.Notice.success(
'The newsletter is being sent...'
MailPoet.I18n.t('newsletterIsBeingSent')
);
} else {
if(response.errors) {
MailPoet.Notice.error(
response.errors.join("<br />")
);
MailPoet.Notice.error(response.errors);
} else {
MailPoet.Notice.error(
'An error occurred while trying to send. '+
'<a href="?page=mailpoet-settings">Check your settings.</a>'
MailPoet.I18n.t('newsletterSendingError').replace("%$1s", '?page=mailpoet-settings')
);
}
}
}.bind(this));
}
},
componentDidMount: function() {
if(this.isMounted()) {
jQuery('#mailpoet_newsletter').parsley();
}
},
isValid: function() {
return (jQuery('#mailpoet_newsletter').parsley().validate());
return false;
},
render: function() {
return (
<div>
<h1>Final step: last details</h1>
<h1>{MailPoet.I18n.t('finalNewsletterStep')}</h1>
<Breadcrumb step="send" />
@ -156,25 +164,25 @@ define(
fields={ fields }
params={ this.props.params }
messages={ messages }
isValid={ this.isValid }>
isValid={ this.isValid }
>
<p className="submit">
<input
className="button button-primary"
type="button"
onClick={ this.handleSend }
value="Send" />
value={MailPoet.I18n.t('send')} />
&nbsp;
<input
className="button button-secondary"
type="submit"
value="Save as draft and close" />
&nbsp;or simply&nbsp;
value={MailPoet.I18n.t('saveDraftAndClose')} />
&nbsp;{MailPoet.I18n.t('orSimply')}&nbsp;
<a
href={
'?page=mailpoet-newsletter-editor&id='+this.props.params.id
}>
go back to design
{MailPoet.I18n.t('goBackToDesign')}
</a>.
</p>
</Form>
@ -185,4 +193,4 @@ define(
return NewsletterSend;
}
);
);

View File

@ -18,12 +18,21 @@ define(
var ImportTemplate = React.createClass({
saveTemplate: function(template) {
// Stringify to enable transmission of primitive non-string value types
if (!_.isUndefined(template.body)) {
template.body = JSON.stringify(template.body);
}
MailPoet.Modal.loading(true);
MailPoet.Ajax.post({
endpoint: 'newsletterTemplates',
action: 'save',
data: template
}).done(function(response) {
if(response === true) {
MailPoet.Modal.loading(false);
if(response.result === true) {
this.props.onImport(template);
} else {
response.map(function(error) {
@ -45,7 +54,7 @@ define(
try {
saveTemplate(JSON.parse(e.target.result));
} catch (err) {
MailPoet.Notice.error('This template file appears to be malformed. Please try another one.');
MailPoet.Notice.error(MailPoet.I18n.t('templateFileMalformedError'));
}
}.bind(this);
@ -54,15 +63,15 @@ define(
render: function() {
return (
<div>
<h2>Import a template</h2>
<h2>{MailPoet.I18n.t('importTemplateTitle')}</h2>
<form onSubmit={this.handleSubmit}>
<input type="file" placeholder="Select a .json file to upload" ref="templateFile" />
<input type="file" placeholder={MailPoet.I18n.t('selectJsonFileToUpload')} ref="templateFile" />
<p className="submit">
<input
className="button button-primary"
type="submit"
value="Upload" />
value={MailPoet.I18n.t('upload')} />
</p>
</form>
</div>
@ -86,19 +95,22 @@ define(
getTemplates: function() {
this.setState({ loading: true });
MailPoet.Modal.loading(true);
MailPoet.Ajax.post({
endpoint: 'newsletterTemplates',
action: 'getAll',
}).done(function(response) {
MailPoet.Modal.loading(false);
if(this.isMounted()) {
if(response.length === 0) {
response = [
{
name:
"MailPoet's Guide",
MailPoet.I18n.t('mailpoetGuideTemplateTitle'),
description:
"This is the standard template that comes with MailPoet.",
MailPoet.I18n.t('mailpoetGuideTemplateDescription'),
readonly: "1"
}
]
@ -111,12 +123,19 @@ define(
}.bind(this));
},
handleSelectTemplate: function(template) {
var body = template.body;
// Stringify to enable transmission of primitive non-string value types
if (!_.isUndefined(body)) {
body = JSON.stringify(body);
}
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'save',
data: {
id: this.props.params.id,
body: template.body
body: body
}
}).done(function(response) {
if(response.result === true) {
@ -136,7 +155,9 @@ define(
this.setState({ loading: true });
if(
window.confirm(
'You are about to delete the template named "'+ template.name +'"'
(
MailPoet.I18n.t('confirmTemplateDeletion')
).replace("%$1s", template.name)
)
) {
MailPoet.Ajax.post({
@ -168,7 +189,7 @@ define(
href="javascript:;"
onClick={ this.handleDeleteTemplate.bind(null, template) }
>
Delete
{MailPoet.I18n.t('delete')}
</a>
</div>
), thumbnail = '';
@ -199,7 +220,7 @@ define(
className="button button-primary"
onClick={ this.handleSelectTemplate.bind(null, template) }
>
Select
{MailPoet.I18n.t('select')}
</a>
&nbsp;
<a
@ -207,7 +228,7 @@ define(
className="button button-secondary"
onClick={ this.handlePreviewTemplate.bind(null, template) }
>
Preview
{MailPoet.I18n.t('preview')}
</a>
</div>
{ (template.readonly === "1") ? false : deleteLink }
@ -223,7 +244,7 @@ define(
return (
<div>
<h1>Select a template</h1>
<h1>{MailPoet.I18n.t('selectTemplateTitle')}</h1>
<Breadcrumb step="template" />

View File

@ -26,22 +26,24 @@ define(
action: 'create',
data: {
type: type,
subject: 'Draft newsletter',
subject: MailPoet.I18n.t('draftNewsletterTitle'),
}
}).done(function(response) {
if(response.id !== undefined) {
this.history.pushState(null, `/template/${response.id}`);
if(response.result && response.newsletter.id) {
this.history.pushState(null, `/template/${response.newsletter.id}`);
} else {
response.map(function(error) {
MailPoet.Notice.error(error);
});
if(response.errors.length > 0) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
}
}.bind(this));
},
render: function() {
return (
<div>
<h1>Pick a type of campaign</h1>
<h1>{MailPoet.I18n.t('pickCampaignType')}</h1>
<Breadcrumb step="type" />
@ -50,10 +52,9 @@ define(
<div className="mailpoet_thumbnail"></div>
<div className="mailpoet_description">
<h3>Newsletter</h3>
<h3>{MailPoet.I18n.t('regularNewsletterTypeTitle')}</h3>
<p>
Send a newsletter with images, buttons, dividers,
and social bookmarks. Or a simple email.
{MailPoet.I18n.t('regularNewsletterTypeDescription')}
</p>
</div>
@ -62,7 +63,7 @@ define(
className="button button-primary"
onClick={ this.createNewsletter.bind(null, 'standard') }
>
Create
{MailPoet.I18n.t('create')}
</a>
</div>
</li>
@ -71,9 +72,9 @@ define(
<div className="mailpoet_thumbnail"></div>
<div className="mailpoet_description">
<h3>Welcome email</h3>
<h3>{MailPoet.I18n.t('welcomeNewsletterTypeTitle')}</h3>
<p>
Send an email for new users.
{MailPoet.I18n.t('welcomeNewsletterTypeDescription')}
</p>
</div>
@ -82,7 +83,7 @@ define(
className="button button-primary"
onClick={ this.setupNewsletter.bind(null, 'welcome') }
>
Set up
{MailPoet.I18n.t('setUp')}
</a>
</div>
</li>
@ -91,9 +92,9 @@ define(
<div className="mailpoet_thumbnail"></div>
<div className="mailpoet_description">
<h3>Post notifications</h3>
<h3>{MailPoet.I18n.t('postNotificationNewsletterTypeTitle')}</h3>
<p>
Automatically send posts immediately, daily, weekly or monthly. Filter by categories, if you like.
{MailPoet.I18n.t('postNotificationsNewsletterTypeDescription')}
</p>
</div>
@ -102,7 +103,7 @@ define(
className="button button-primary"
onClick={ this.setupNewsletter.bind(null, 'notification') }
>
Set up
{MailPoet.I18n.t('setUp')}
</a>
</div>
</li>

View File

@ -25,11 +25,11 @@ define(
var intervalField = {
name: 'interval',
values: {
'daily': 'Once a day at...',
'weekly': 'Weekly on...',
'monthly': 'Monthly on the...',
'nthWeekDay': 'Monthly every...',
'immediately': 'Immediately.',
'daily': MailPoet.I18n.t('daily'),
'weekly': MailPoet.I18n.t('weekly'),
'monthly': MailPoet.I18n.t('monthly'),
'nthWeekDay': MailPoet.I18n.t('monthlyEvery'),
'immediately': MailPoet.I18n.t('immediately'),
},
};
@ -53,13 +53,13 @@ define(
var weekDayField = {
name: 'weekDay',
values: {
0: 'Monday',
1: 'Tuesday',
2: 'Wednesday',
3: 'Thursday',
4: 'Friday',
5: 'Saturday',
6: 'Sunday',
0: MailPoet.I18n.t('sunday'),
1: MailPoet.I18n.t('monday'),
2: MailPoet.I18n.t('tuesday'),
3: MailPoet.I18n.t('wednesday'),
4: MailPoet.I18n.t('thursday'),
5: MailPoet.I18n.t('friday'),
6: MailPoet.I18n.t('saturday')
},
};
@ -69,14 +69,19 @@ define(
values: _.object(_.map(
_.times(NUMBER_OF_DAYS_IN_MONTH, function(day) { return day; }),
function(day) {
var suffixes = {
0: 'st',
1: 'nd',
2: 'rd'
};
var suffix = suffixes[day] || 'th';
var labels = {
0: MailPoet.I18n.t('first'),
1: MailPoet.I18n.t('second'),
2: MailPoet.I18n.t('third')
},
label;
if (labels[day] !== undefined) {
label = labels[day];
} else {
label = MailPoet.I18n.t('nth').replace("%$1d", day + 1);
}
return [day, (day + 1).toString() + suffix];
return [day, label];
},
)),
};
@ -84,10 +89,10 @@ define(
var nthWeekDayField = {
name: 'nthWeekDay',
values: {
'0': '1st',
'1': '2nd',
'2': '3rd',
'3': 'last',
'1': MailPoet.I18n.t('first'),
'2': MailPoet.I18n.t('second'),
'3': MailPoet.I18n.t('third'),
'L': MailPoet.I18n.t('last'),
},
};
@ -99,9 +104,9 @@ define(
return {
intervalType: 'immediate', // 'immediate'|'daily'|'weekly'|'monthly'
timeOfDay: 0,
weekDay: 0,
weekDay: 1,
monthDay: 0,
nthWeekDay: 0,
nthWeekDay: 1,
};
},
handleIntervalChange: function(event) {
@ -138,12 +143,14 @@ define(
options: this.state,
},
}).done(function(response) {
if(response.id !== undefined) {
this.showTemplateSelection(response.id);
if(response.result && response.newsletter.id) {
this.showTemplateSelection(response.newsletter.id);
} else {
response.map(function(error) {
MailPoet.Notice.error(error);
});
if(response.errors.length > 0) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
}
}.bind(this));
},
@ -195,7 +202,7 @@ define(
return (
<div>
<h1>Post notifications</h1>
<h1>{MailPoet.I18n.t('postNotificationNewsletterTypeTitle')}</h1>
<Breadcrumb step="type" />
<Select
@ -213,7 +220,7 @@ define(
className="button button-primary"
type="button"
onClick={ this.handleNext }
value="Next" />
value={MailPoet.I18n.t('next')} />
</p>
</div>
);

View File

@ -32,19 +32,22 @@ define(
type: 'standard',
}
}).done(function(response) {
if(response.id !== undefined) {
this.showTemplateSelection(response.id);
console.log(response);
if(response.result && response.newsletter.id) {
this.showTemplateSelection(response.newsletter.id);
} else {
response.map(function(error) {
MailPoet.Notice.error(error);
});
if(response.errors.length > 0) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
}
}.bind(this));
},
render: function() {
return (
<div>
<h1>Newsletter</h1>
<h1>{MailPoet.I18n.t('regularNewsletterTypeTitle')}</h1>
<Breadcrumb step="type" />
</div>
);

View File

@ -28,8 +28,8 @@ define(
var events = {
name: 'event',
values: {
'segment': 'When someone subscribes to the list...',
'user': 'When a new Wordrpess user is added to your site...',
'segment': MailPoet.I18n.t('onSubscriptionToList'),
'user': MailPoet.I18n.t('onWordpressUserRegistration'),
}
};
@ -57,10 +57,10 @@ define(
var afterTimeTypeField = {
name: 'afterTimeType',
values: {
'immediate': 'immediately',
'hours': 'hour(s) after',
'days': 'day(s) after',
'weeks': 'week(s) after',
'immediate': MailPoet.I18n.t('delayImmediately'),
'hours': MailPoet.I18n.t('delayHoursAfter'),
'days': MailPoet.I18n.t('delayDaysAfter'),
'weeks': MailPoet.I18n.t('delayWeeksAfter'),
}
};
@ -111,12 +111,14 @@ define(
options: this.state,
},
}).done(function(response) {
if(response.id !== undefined) {
this.showTemplateSelection(response.id);
if(response.result && response.newsletter.id) {
this.showTemplateSelection(response.newsletter.id);
} else {
response.map(function(error) {
MailPoet.Notice.error(error);
});
if(response.errors.length > 0) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
}
}.bind(this));
},
@ -150,9 +152,11 @@ define(
}
return (
<div>
<h1>Welcome email</h1>
<h1>{MailPoet.I18n.t('welcomeNewsletterTypeTitle')}</h1>
<Breadcrumb step="type" />
<h3>{MailPoet.I18n.t('selectEventToSendWelcomeEmail')}</h3>
<Select
field={events}
item={this.state}
@ -172,7 +176,7 @@ define(
className="button button-primary"
type="button"
onClick={ this.handleNext }
value="Next" />
value={MailPoet.I18n.t('next')} />
</p>
</div>
);

View File

@ -1,75 +1,75 @@
define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
"use strict";
/*==================================================================================================
MailPoet Notice:
description: Handles notices
version: 0.2
author: Jonathan Labreuille
company: Wysija
dependencies: jQuery
Usage:
// success message (static: false)
MailPoet.Notice.success('Yatta!');
// error message (static: false)
MailPoet.Notice.error('Boo!');
// system message (static: true)
MailPoet.Notice.system('You need to updated ASAP!');
Examples:
MailPoet.Notice.success('- success #1 -');
setTimeout(function() {
MailPoet.Notice.success('- success #2 -');
setTimeout(function() {
MailPoet.Notice.error('- error -');
setTimeout(function() {
MailPoet.Notice.system('- system -');
setTimeout(function() {
MailPoet.Notice.hide();
}, 2500);
}, 300);
}, 400);
}, 500);
==================================================================================================*/
MailPoet.Notice = {
version: 0.2,
// default options
defaults: {
type: 'success',
message: '',
static: false,
hideClose: false,
id: null,
define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
"use strict";
/*==================================================================================================
MailPoet Notice:
description: Handles notices
version: 0.2
author: Jonathan Labreuille
company: Wysija
dependencies: jQuery
Usage:
// success message (static: false)
MailPoet.Notice.success('Yatta!');
// error message (static: false)
MailPoet.Notice.error('Boo!');
// system message (static: true)
MailPoet.Notice.system('You need to updated ASAP!');
Examples:
MailPoet.Notice.success('- success #1 -');
setTimeout(function() {
MailPoet.Notice.success('- success #2 -');
setTimeout(function() {
MailPoet.Notice.error('- error -');
setTimeout(function() {
MailPoet.Notice.system('- system -');
setTimeout(function() {
MailPoet.Notice.hide();
}, 2500);
}, 300);
}, 400);
}, 500);
==================================================================================================*/
MailPoet.Notice = {
version: 0.2,
// default options
defaults: {
type: 'success',
message: '',
static: false,
hideClose: false,
id: null,
positionAfter: false,
scroll: false,
timeout: 2000,
onOpen: null,
onClose: null
},
options: {},
init: function(options) {
// set options
this.options = jQuery.extend({}, this.defaults, options);
// clone element
this.element = jQuery('#mailpoet_notice_'+this.options.type).clone();
// add data-id to the element
if (this.options.id) this.element.attr('data-id', 'notice_' + this.options.id);
// remove id from clone
this.element.removeAttr('id');
// insert notice after its parent
scroll: false,
timeout: 2000,
onOpen: null,
onClose: null
},
options: {},
init: function(options) {
// set options
this.options = jQuery.extend({}, this.defaults, options);
// clone element
this.element = jQuery('#mailpoet_notice_'+this.options.type).clone();
// add data-id to the element
if (this.options.id) this.element.attr('data-id', 'notice_' + this.options.id);
// remove id from clone
this.element.removeAttr('id');
// insert notice after its parent
var positionAfter;
if (typeof this.options.positionAfter === 'object') {
positionAfter = this.options.positionAfter;
@ -78,136 +78,142 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
} else {
positionAfter = jQuery('#mailpoet_notice_'+this.options.type);
}
console.log('positionAfter', typeof this.options.positionAfter);
positionAfter.after(this.element);
// setup onClose callback
var onClose = null;
if(this.options.onClose !== null) {
onClose = this.options.onClose;
}
// listen to remove event
jQuery(this.element).on('close', function() {
jQuery(this).fadeOut(200, function() {
// on close callback
if(onClose !== null) {
onClose();
}
// remove notice
jQuery(this).remove();
});
}.bind(this.element));
// listen to message event
jQuery(this.element).on('message', function(e, message) {
MailPoet.Notice.setMessage(message);
}.bind(this.element));
return this;
},
isHTML: function(str) {
var a = document.createElement('div');
a.innerHTML = str;
for(var c = a.childNodes, i = c.length; i--;) {
if(c[i].nodeType == 1) return true;
}
return false;
},
setMessage: function(message) {
// if it's not an html message, let's sugar coat the message with a fancy <p>
if(this.isHTML(message) === false) {
message = '<p>'+message+'</p>';
}
// set message
return this.element.html(message);
},
show: function(options) {
// initialize
this.init(options);
// show notice
this.showNotice();
// return this;
},
showNotice: function() {
// set message
this.setMessage(this.options.message);
// position notice
this.element.insertAfter(jQuery('h2.title'));
// set class name
switch(this.options.type) {
case 'success':
this.element.addClass('updated');
break;
case 'system':
this.element.addClass('update-nag');
break;
case 'error':
this.element.addClass('error');
break;
}
// make the notice appear
this.element.fadeIn(200);
// if scroll option is enabled, scroll to the notice
if(this.options.scroll === true) {
this.element.get(0).scrollIntoView(false);
}
// if the notice is not static, it has to disappear after a timeout
if(this.options.static === false) {
this.element.delay(this.options.timeout).trigger('close');
} else if (this.options.hideClose === false) {
this.element.append('<a href="javascript:;" class="mailpoet_notice_close"><span class="dashicons dashicons-dismiss"></span></a>');
this.element.find('.mailpoet_notice_close').on('click', function() {
jQuery(this).trigger('close');
});
}
// call onOpen callback
if(this.options.onOpen !== null) {
this.options.onOpen(this.element);
}
},
hide: function(all) {
if(all !== undefined && all === true) {
jQuery('.mailpoet_notice:not([id])').trigger('close');
} else if (all !== undefined && jQuery.isArray(all)) {
for (var id in all) {
jQuery('[data-id="notice_' + all[id] + '"]')
.trigger('close');
}
} if (all !== undefined) {
jQuery('[data-id="notice_' + all + '"]')
.trigger('close');
} else {
jQuery('.mailpoet_notice.updated:not([id]), .mailpoet_notice.error:not([id])')
.trigger('close');
}
},
error: function(message, options) {
this.show(jQuery.extend({}, {
type: 'error',
message: '<p>'+message+'</p>'
}, options));
},
success: function(message, options) {
this.show(jQuery.extend({}, {
type: 'success',
message: '<p>'+message+'</p>'
}, options));
},
system: function(message, options) {
this.show(jQuery.extend({}, {
type: 'system',
static: true,
message: message
}, options));
}
};
positionAfter.after(this.element);
// setup onClose callback
var onClose = null;
if(this.options.onClose !== null) {
onClose = this.options.onClose;
}
// listen to remove event
jQuery(this.element).on('close', function() {
jQuery(this).fadeOut(200, function() {
// on close callback
if(onClose !== null) {
onClose();
}
// remove notice
jQuery(this).remove();
});
}.bind(this.element));
// listen to message event
jQuery(this.element).on('message', function(e, message) {
MailPoet.Notice.setMessage(message);
}.bind(this.element));
return this;
},
isHTML: function(str) {
var a = document.createElement('div');
a.innerHTML = str;
for(var c = a.childNodes, i = c.length; i--;) {
if(c[i].nodeType == 1) return true;
}
return false;
},
setMessage: function(message) {
// if it's not an html message, let's sugar coat the message with a fancy <p>
if(this.isHTML(message) === false) {
message = '<p>'+message+'</p>';
}
// set message
return this.element.html(message);
},
show: function(options) {
// initialize
this.init(options);
// show notice
this.showNotice();
// return this;
},
showNotice: function() {
// set message
this.setMessage(this.options.message);
// position notice
this.element.insertAfter(jQuery('h2.title'));
// set class name
switch(this.options.type) {
case 'success':
this.element.addClass('updated');
break;
case 'system':
this.element.addClass('update-nag');
break;
case 'error':
this.element.addClass('error');
break;
}
// make the notice appear
this.element.fadeIn(200);
// if scroll option is enabled, scroll to the notice
if(this.options.scroll === true) {
this.element.get(0).scrollIntoView(false);
}
// if the notice is not static, it has to disappear after a timeout
if(this.options.static === false) {
this.element.delay(this.options.timeout).trigger('close');
} else if (this.options.hideClose === false) {
this.element.append('<a href="javascript:;" class="mailpoet_notice_close"><span class="dashicons dashicons-dismiss"></span></a>');
this.element.find('.mailpoet_notice_close').on('click', function() {
jQuery(this).trigger('close');
});
}
// call onOpen callback
if(this.options.onOpen !== null) {
this.options.onOpen(this.element);
}
},
hide: function(all) {
if(all !== undefined && all === true) {
jQuery('.mailpoet_notice:not([id])').trigger('close');
} else if (all !== undefined && jQuery.isArray(all)) {
for (var id in all) {
jQuery('[data-id="notice_' + all[id] + '"]')
.trigger('close');
}
} if (all !== undefined) {
jQuery('[data-id="notice_' + all + '"]')
.trigger('close');
} else {
jQuery('.mailpoet_notice.updated:not([id]), .mailpoet_notice.error:not([id])')
.trigger('close');
}
},
error: function(message, options) {
this.show(jQuery.extend({}, {
type: 'error',
message: '<p>'+this.formatMessage(message)+'</p>'
}, options));
},
success: function(message, options) {
this.show(jQuery.extend({}, {
type: 'success',
message: '<p>'+this.formatMessage(message)+'</p>'
}, options));
},
system: function(message, options) {
this.show(jQuery.extend({}, {
type: 'system',
static: true,
message: '<p>'+this.formatMessage(message)+'</p>'
}, options));
},
formatMessage: function(message) {
if(Array.isArray(message)) {
return message.join('<br />');
} else {
return message;
}
}
};
});

View File

@ -20,16 +20,18 @@ function(
$('form.mailpoet_form').each(function() {
var form = $(this);
form.parsley({
errorsWrapper: '<p></p>',
errorTemplate: '<span></span>'
}).on('form:submit', function(parsley) {
var data = form.serializeObject() || {};
form.parsley().on('form:validated', function(parsley) {
// clear messages
form.find('.mailpoet_message').html('');
// resize iframe
if(window.frameElement !== null) {
MailPoet.Iframe.autoSize(window.frameElement);
}
});
form.parsley().on('form:submit', function(parsley) {
var data = form.serializeObject() || {};
// check if we're on the same domain
if(isSameDomain(MailPoetForm.ajax_url) === false) {
// non ajax post request
@ -71,6 +73,11 @@ function(
// reset validation
parsley.reset();
}
// resize iframe
if(window.frameElement !== null) {
MailPoet.Iframe.autoSize(window.frameElement);
}
});
}
return false;

View File

@ -12,29 +12,29 @@ define(
Form
) {
var fields = [
let fields = [
{
name: 'name',
label: 'Name',
label: MailPoet.I18n.t('name'),
type: 'text'
},
{
name: 'description',
label: 'Description',
label: MailPoet.I18n.t('description'),
type: 'textarea'
}
];
var messages = {
const messages = {
onUpdate: function() {
MailPoet.Notice.success('Segment successfully updated!');
MailPoet.Notice.success(MailPoet.I18n.t('segmentUpdated'));
},
onCreate: function() {
MailPoet.Notice.success('Segment successfully added!');
MailPoet.Notice.success(MailPoet.I18n.t('segmentAdded'));
}
};
var SegmentForm = React.createClass({
const SegmentForm = React.createClass({
mixins: [
Router.History
],
@ -42,7 +42,7 @@ define(
return (
<div>
<h2 className="title">
Segment
{MailPoet.I18n.t('segment')}
</h2>
<Form

View File

@ -10,101 +10,95 @@ import Listing from 'listing/listing.jsx'
var columns = [
{
name: 'name',
label: 'Name',
label: MailPoet.I18n.t('name'),
sortable: true
},
{
name: 'description',
label: 'Description',
label: MailPoet.I18n.t('description'),
sortable: false
},
{
name: 'subscribed',
label: 'Subscribed',
label: MailPoet.I18n.t('subscribed'),
sortable: false
},
{
name: 'unconfirmed',
label: 'Unconfirmed',
label: MailPoet.I18n.t('unconfirmed'),
sortable: false
},
{
name: 'unsubscribed',
label: 'Unsubscribed',
label: MailPoet.I18n.t('unsubscribed'),
sortable: false
},
{
name: 'created_at',
label: 'Created on',
label: MailPoet.I18n.t('createdOn'),
sortable: true
}
];
const messages = {
onTrash: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 segment was moved to the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d segments were moved to the trash.'
).replace('%$1d', ~~response);
}
var count = ~~response;
var message = null;
if(message !== null) {
MailPoet.Notice.success(message);
}
if(count === 1) {
message = (
MailPoet.I18n.t('oneSegmentTrashed')
);
} else {
message = (
MailPoet.I18n.t('multipleSegmentsTrashed')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
},
onDelete: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 segment was permanently deleted.'
);
} else if(~~response > 1) {
message = (
'%$1d segments were permanently deleted.'
).replace('%$1d', ~~response);
}
var count = ~~response;
var message = null;
if(message !== null) {
MailPoet.Notice.success(message);
}
if(count === 1) {
message = (
MailPoet.I18n.t('oneSegmentDeleted')
);
} else {
message = (
MailPoet.I18n.t('multipleSegmentsDeleted')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
},
onRestore: function(response) {
if(response) {
let message = null;
if(~~response === 1) {
message = (
'1 segment has been restored from the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d segments have been restored from the trash.'
).replace('%$1d', ~~response);
}
var count = ~~response;
var message = null;
if(message !== null) {
MailPoet.Notice.success(message);
}
if(count === 1) {
message = (
MailPoet.I18n.t('oneSegmentRestored')
);
} else {
message = (
MailPoet.I18n.t('multipleSegmentsRestored')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
}
};
const item_actions = [
{
name: 'edit',
label: 'Edit',
label: MailPoet.I18n.t('edit'),
link: function(item) {
return (
<Link to={ `/edit/${item.id}` }>Edit</Link>
<Link to={ `/edit/${item.id}` }>{MailPoet.I18n.t('edit')}</Link>
);
},
display: function(segment) {
return (segment.type !== 'wp_users');
}
},
{
@ -117,7 +111,7 @@ const item_actions = [
data: item.id
}).done(function(response) {
MailPoet.Notice.success(
('List "%$1s" has been duplicated.').replace('%$1s', response.name)
(MailPoet.I18n.t('listDuplicated')).replace('%$1s', response.name)
);
refresh();
});
@ -128,7 +122,7 @@ const item_actions = [
},
{
name: 'synchronize_segment',
label: 'Update',
label: MailPoet.I18n.t('update'),
className: 'update',
onClick: function(item, refresh) {
MailPoet.Modal.loading(true);
@ -139,7 +133,7 @@ const item_actions = [
MailPoet.Modal.loading(false);
if(response === true) {
MailPoet.Notice.success(
('List "%$1s" has been synchronized.').replace('%$1s', item.name)
(MailPoet.I18n.t('listSynchronized')).replace('%$1s', item.name)
);
refresh();
}
@ -153,7 +147,7 @@ const item_actions = [
name: 'view_subscribers',
link: function(item) {
return (
<a href={ item.subscribers_url }>View subscribers</a>
<a href={ item.subscribers_url }>{MailPoet.I18n.t('viewSubscribers')}</a>
);
}
},
@ -187,16 +181,16 @@ const SegmentList = React.createClass({
<abbr>{ segment.description }</abbr>
</td>
<td className="column-date" data-colname="Subscribed">
<abbr>{ segment.subscribed || 0 }</abbr>
<abbr>{ segment.subscribers_count.subscribed || 0 }</abbr>
</td>
<td className="column-date" data-colname="Unconfirmed">
<abbr>{ segment.unconfirmed || 0 }</abbr>
<abbr>{ segment.subscribers_count.unconfirmed || 0 }</abbr>
</td>
<td className="column-date" data-colname="Unsubscribed">
<abbr>{ segment.unsubscribed || 0 }</abbr>
<abbr>{ segment.subscribers_count.unsubscribed || 0 }</abbr>
</td>
<td className="column-date" data-colname="Created on">
<abbr>{ segment.created_at }</abbr>
<abbr>{ MailPoet.Date.full(segment.created_at) }</abbr>
</td>
</div>
);
@ -205,7 +199,7 @@ const SegmentList = React.createClass({
return (
<div>
<h2 className="title">
Segments <Link className="add-new-h2" to="/new">New</Link>
{MailPoet.I18n.t('pageTitle')} <Link className="add-new-h2" to="/new">{MailPoet.I18n.t('new')}</Link>
</h2>
<Listing
@ -225,4 +219,4 @@ const SegmentList = React.createClass({
}
});
module.exports = SegmentList;
module.exports = SegmentList;

View File

@ -34,13 +34,16 @@ define(
// show sending methods
jQuery('.mailpoet_sending_methods').fadeIn();
} else {
// hide DKIM option when using MailPoet's API
jQuery('#mailpoet_mta_dkim')[
// toggle SPF/DKIM (hidden if the sending method is MailPoet)
jQuery('#mailpoet_mta_spf')[
(group === 'mailpoet')
? 'hide'
: 'show'
]();
// (HIDDEN FOR BETA)
jQuery('#mailpoet_mta_dkim').hide();
// hide sending methods
jQuery('.mailpoet_sending_methods').hide();

View File

@ -11,52 +11,104 @@ define(
MailPoet,
Form
) {
var fields = [
{
name: 'email',
label: 'E-mail',
label: MailPoet.I18n.t('email'),
type: 'text'
},
{
name: 'first_name',
label: 'Firstname',
label: MailPoet.I18n.t('firstname'),
type: 'text'
},
{
name: 'last_name',
label: 'Lastname',
label: MailPoet.I18n.t('lastname'),
type: 'text'
},
{
name: 'status',
label: 'Status',
label: MailPoet.I18n.t('status'),
type: 'select',
values: {
'unconfirmed': 'Unconfirmed',
'subscribed': 'Subscribed',
'unsubscribed': 'Unsubscribed'
'unconfirmed': MailPoet.I18n.t('unconfirmed'),
'subscribed': MailPoet.I18n.t('subscribed'),
'unsubscribed': MailPoet.I18n.t('unsubscribed')
}
},
{
name: 'segments',
label: 'Lists',
label: MailPoet.I18n.t('lists'),
type: 'selection',
placeholder: "Select a list",
placeholder: MailPoet.I18n.t('selectList'),
endpoint: "segments",
multiple: true,
selected: function(subscriber) {
if (Array.isArray(subscriber.subscriptions) === false) {
return null;
}
return subscriber.subscriptions.map(function(subscription) {
if (subscription.status === 'subscribed') {
return subscription.segment_id;
}
});
},
filter: function(segment) {
return !!(!segment.deleted_at);
},
getSearchLabel: function(segment, subscriber) {
let label = '';
if (subscriber.subscriptions !== undefined) {
subscriber.subscriptions.map(function(subscription) {
if (segment.id === subscription.segment_id) {
label = segment.name;
if (subscription.status === 'unsubscribed') {
const unsubscribed_at = MailPoet.Date
.format(subscription.updated_at);
label += ' (%$1s)'.replace(
'%$1s',
MailPoet.I18n.t('unsubscribedOn').replace(
'%$1s',
unsubscribed_at
)
);
}
}
});
}
return label;
}
}
];
var custom_fields = window.mailpoet_custom_fields || [];
custom_fields.map(custom_field => {
let field = {
name: 'cf_' + custom_field.id,
label: custom_field.name,
type: custom_field.type
};
if (custom_field.params) {
field.params = custom_field.params;
}
if (custom_field.params.values) {
field.values = custom_field.params.values;
}
fields.push(field);
});
var messages = {
onUpdate: function() {
MailPoet.Notice.success('Subscriber successfully updated!');
MailPoet.Notice.success(MailPoet.I18n.t('subscriberUpdated'));
},
onCreate: function() {
MailPoet.Notice.success('Subscriber successfully added!');
MailPoet.Notice.success(MailPoet.I18n.t('subscriberAdded'));
}
};
@ -70,7 +122,7 @@ define(
return (
<div>
<h2 className="title">
Subscriber
{MailPoet.I18n.t('subscriber')}
</h2>
<Form

View File

@ -62,7 +62,7 @@ define(
if (_.contains(fieldsToExclude, selectedOptionId)) {
selectEvent.preventDefault();
if (selectedOptionId === 'deselect') {
jQuery(selectElement).select2('val', '');
jQuery(selectElement).val('').trigger('change');
} else {
var allOptions = [];
_.each(container.find('option'), function (field) {
@ -70,7 +70,7 @@ define(
allOptions.push(field.value);
}
});
jQuery(selectElement).select2('val', allOptions);
jQuery(selectElement).val(allOptions).trigger('change');
}
jQuery(selectElement).select2('close');
}
@ -115,7 +115,7 @@ define(
exportData.exportConfirmedOption = false;
renderSegmentsAndFields(segmentsContainerElement, segments);
}
segmentsContainerElement.select2('val', selectedSegments);
segmentsContainerElement.val(selectedSegments).trigger('change');
});
function toggleNextStepButton(condition) {
@ -138,19 +138,19 @@ define(
endpoint: 'ImportExport',
action: 'processExport',
data: JSON.stringify({
'exportConfirmedOption': exportData.exportConfirmedOption,
'exportFormatOption': jQuery(':radio[name="option_format"]:checked').val(),
'groupBySegmentOption': (groupBySegmentOptionElement.is(":visible")) ? groupBySegmentOptionElement.prop('checked') : false,
'export_confirmed_option': exportData.exportConfirmedOption,
'export_format_option': jQuery(':radio[name="option_format"]:checked').val(),
'group_by_segment_option': (groupBySegmentOptionElement.is(":visible")) ? groupBySegmentOptionElement.prop('checked') : false,
'segments': (exportData.segments) ? segmentsContainerElement.val() : false,
'subscriberFields': subscriberFieldsContainerElement.val()
'subscriber_fields': subscriberFieldsContainerElement.val()
})
})
.done(function (response) {
MailPoet.Modal.loading(false);
if (response.result === false) {
MailPoet.Notice.error(response.error);
MailPoet.Notice.error(response.errors);
} else {
resultMessage = MailPoetI18n.exportMessage
resultMessage = MailPoet.I18n.t('exportMessage')
.replace('%1$s', '<strong>' + response.data.totalExported + '</strong>')
.replace('[link]', '<a href="' + response.data.exportFileURL + '" target="_blank" >')
.replace('[/link]', '</a>');
@ -162,9 +162,9 @@ define(
.error(function (error) {
MailPoet.Modal.loading(false);
MailPoet.Notice.error(
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.'
MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.'
);
});
});
});
});
});

File diff suppressed because it is too large Load Diff

View File

@ -11,28 +11,28 @@ import Selection from 'form/fields/selection.jsx'
const columns = [
{
name: 'email',
label: 'Subscriber',
label: MailPoet.I18n.t('subscriber'),
sortable: true
},
{
name: 'status',
label: 'Status',
label: MailPoet.I18n.t('status'),
sortable: true
},
{
name: 'segments',
label: 'Lists',
label: MailPoet.I18n.t('lists'),
sortable: false
},
{
name: 'created_at',
label: 'Subscribed on',
label: MailPoet.I18n.t('subscribedOn'),
sortable: true
},
{
name: 'updated_at',
label: 'Last modified on',
label: MailPoet.I18n.t('lastModifiedOn'),
sortable: true
},
];
@ -43,11 +43,11 @@ const messages = {
var message = null;
if(~~response === 1) {
message = (
'1 subscriber was moved to the trash.'
MailPoet.I18n.t('oneSubscriberTrashed')
);
} else if(~~response > 1) {
message = (
'%$1d subscribers were moved to the trash.'
MailPoet.I18n.t('multipleSubscribersTrashed')
).replace('%$1d', ~~response);
}
@ -61,11 +61,11 @@ const messages = {
var message = null;
if(~~response === 1) {
message = (
'1 subscriber was permanently deleted.'
MailPoet.I18n.t('oneSubscriberDeleted')
);
} else if(~~response > 1) {
message = (
'%$1d subscribers were permanently deleted.'
MailPoet.I18n.t('multipleSubscribersDeleted')
).replace('%$1d', ~~response);
}
@ -79,11 +79,11 @@ const messages = {
var message = null;
if(~~response === 1) {
message = (
'1 subscriber has been restored from the trash.'
MailPoet.I18n.t('oneSubscriberRestored')
);
} else if(~~response > 1) {
message = (
'%$1d subscribers have been restored from the trash.'
MailPoet.I18n.t('multipleSubscribersRestored')
).replace('%$1d', ~~response);
}
@ -97,7 +97,7 @@ const messages = {
const bulk_actions = [
{
name: 'moveToList',
label: 'Move to list...',
label: MailPoet.I18n.t('moveToList'),
onSelect: function() {
let field = {
id: 'move_to_segment',
@ -120,7 +120,7 @@ const bulk_actions = [
},
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were moved to list <strong>%$2s</strong>.'
MailPoet.I18n.t('multipleSubscribersMovedToList')
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
@ -128,7 +128,7 @@ const bulk_actions = [
},
{
name: 'addToList',
label: 'Add to list...',
label: MailPoet.I18n.t('addToList'),
onSelect: function() {
let field = {
id: 'add_to_segment',
@ -151,7 +151,7 @@ const bulk_actions = [
},
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were added to list <strong>%$2s</strong>.'
MailPoet.I18n.t('multipleSubscribersAddedToList')
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
@ -159,7 +159,7 @@ const bulk_actions = [
},
{
name: 'removeFromList',
label: 'Remove from list...',
label: MailPoet.I18n.t('removeFromList'),
onSelect: function() {
let field = {
id: 'remove_from_segment',
@ -182,7 +182,7 @@ const bulk_actions = [
},
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were removed from list <strong>%$2s</strong>.'
MailPoet.I18n.t('multipleSubscribersRemovedFromList')
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
);
@ -190,27 +190,37 @@ const bulk_actions = [
},
{
name: 'removeFromAllLists',
label: 'Remove from all lists',
label: MailPoet.I18n.t('removeFromAllLists'),
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were removed from all lists.'
MailPoet.I18n.t('multipleSubscribersRemovedFromAllLists')
.replace('%$1d', ~~response)
);
}
},
{
name: 'confirmUnconfirmed',
label: 'Confirm unconfirmed',
label: MailPoet.I18n.t('confirmUnconfirmed'),
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers have been confirmed.'
MailPoet.I18n.t('multipleSubscribersConfirmed')
.replace('%$1d', ~~response)
);
}
},
{
name: 'sendConfirmationEmail',
label: MailPoet.I18n.t('resendConfirmationEmail'),
onSuccess: function(response) {
MailPoet.Notice.success(
MailPoet.I18n.t('multipleConfirmationEmailsSent')
.replace('%$1d', ~~response)
);
}
},
{
name: 'trash',
label: 'Trash',
label: MailPoet.I18n.t('trash'),
onSuccess: messages.onTrash
}
];
@ -218,10 +228,10 @@ const bulk_actions = [
const item_actions = [
{
name: 'edit',
label: 'Edit',
label: MailPoet.I18n.t('edit'),
link: function(item) {
return (
<Link to={ `/edit/${item.id}` }>Edit</Link>
<Link to={ `/edit/${item.id}` }>{MailPoet.I18n.t('edit')}</Link>
);
}
},
@ -231,6 +241,15 @@ const item_actions = [
];
const SubscriberList = React.createClass({
getSegmentFromId: function(segment_id) {
let result = false;
mailpoet_segments.map(function(segment) {
if (segment.id === segment_id) {
result = segment;
}
});
return result;
},
renderItem: function(subscriber, actions) {
let row_classes = classNames(
'manage-column',
@ -243,23 +262,53 @@ const SubscriberList = React.createClass({
switch(subscriber.status) {
case 'subscribed':
status = 'Subscribed';
status = MailPoet.I18n.t('subscribed');
break;
case 'unconfirmed':
status = 'Unconfirmed';
status = MailPoet.I18n.t('unconfirmed');
break;
case 'unsubscribed':
status = 'Unsubscribed';
status = MailPoet.I18n.t('unsubscribed');
break;
}
let segments = mailpoet_segments.filter(function(segment) {
return (jQuery.inArray(segment.id, subscriber.segments) !== -1);
}).map(function(segment) {
return segment.name;
}).join(', ');
let segments = false;
if (subscriber.subscriptions.length > 0) {
let subscribed_segments = [];
let unsubscribed_segments = [];
subscriber.subscriptions.map((subscription) => {
const segment = this.getSegmentFromId(subscription.segment_id);
if(segment === false) return;
if (subscription.status === 'subscribed') {
subscribed_segments.push(segment.name);
} else if (subscription.status === 'unsubscribed') {
unsubscribed_segments.push(segment.name);
}
});
segments = (
<span>
<span className="mailpoet_segments_subscribed">
{ subscribed_segments.join(', ') }
{
(
subscribed_segments.length > 0
&& unsubscribed_segments.length > 0
) ? ' / ' : ''
}
</span>
<span
className="mailpoet_segments_unsubscribed"
title={MailPoet.I18n.t('listsToWhichSubscriberWasSubscribed')}
>
{ unsubscribed_segments.join(', ') }
</span>
</span>
);
}
let avatar = false;
if(subscriber.avatar_url) {
@ -285,17 +334,17 @@ const SubscriberList = React.createClass({
</p>
{ actions }
</td>
<td className="column" data-colname="Status">
<td className="column" data-colname={MailPoet.I18n.t('status')}>
{ status }
</td>
<td className="column" data-colname="Lists">
<td className="column" data-colname={MailPoet.I18n.t('lists')}>
{ segments }
</td>
<td className="column-date" data-colname="Subscribed on">
<abbr>{ subscriber.created_at }</abbr>
<td className="column-date" data-colname={MailPoet.I18n.t('subscribedOn')}>
<abbr>{ MailPoet.Date.full(subscriber.created_at) }</abbr>
</td>
<td className="column-date" data-colname="Last modified on">
<abbr>{ subscriber.updated_at }</abbr>
<td className="column-date" data-colname={MailPoet.I18n.t('lastModifiedOn')}>
<abbr>{ MailPoet.Date.full(subscriber.updated_at) }</abbr>
</td>
</div>
);
@ -307,12 +356,13 @@ const SubscriberList = React.createClass({
return (
<div>
<h2 className="title">
Subscribers <Link className="add-new-h2" to="/new">New</Link>
<a className="add-new-h2" href="?page=mailpoet-import#step1">Import</a>
<a id="mailpoet_export_button" className="add-new-h2" href="?page=mailpoet-export">Export</a>
{MailPoet.I18n.t('pageTitle')} <Link className="add-new-h2" to="/new">{MailPoet.I18n.t('new')}</Link>
<a className="add-new-h2" href="?page=mailpoet-import#step1">{MailPoet.I18n.t('import')}</a>
<a id="mailpoet_export_button" className="add-new-h2" href="?page=mailpoet-export">{MailPoet.I18n.t('export')}</a>
</h2>
<Listing
limit={ mailpoet_listing_per_page }
location={ this.props.location }
params={ this.props.params }
endpoint="subscribers"
@ -328,4 +378,4 @@ const SubscriberList = React.createClass({
}
});
module.exports = SubscriberList;
module.exports = SubscriberList;

View File

@ -0,0 +1,79 @@
/*
* This file is part of the jquery plugin "asyncQueue".
*
* (c) Sebastien Roch <roch.sebastien@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
(function($){
$.AsyncQueue = function() {
var that = this,
queue = [],
failureFunc,
completeFunc,
paused = false,
lastCallbackData,
_run;
_run = function() {
var f = queue.shift();
if (f) {
f.apply(that, [that]);
if (paused === false) {
_run();
}
} else {
if(completeFunc){
completeFunc.apply(that);
}
}
}
this.onFailure = function(func) {
failureFunc = func;
}
this.onComplete = function(func) {
completeFunc = func;
}
this.add = function(func) {
queue.push(func);
return this;
}
this.storeData = function(dataObject) {
lastCallbackData = dataObject;
return this;
}
this.lastCallbackData = function () {
return lastCallbackData;
}
this.run = function() {
paused = false;
_run();
}
this.pause = function () {
paused = true;
return this;
}
this.failure = function() {
paused = true;
if (failureFunc) {
var args = [that];
for(i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
failureFunc.apply(that, args);
}
}
return this;
}
})(jQuery);

View File

@ -9,6 +9,7 @@ settings:
bootstrap: _bootstrap.php
colors: true
memory_limit: 1024M
log: true
extensions:
enabled:
- Codeception\Extension\RunFailed
@ -19,3 +20,12 @@ modules:
user: ''
password: ''
dump: tests/_data/dump.sql
coverage:
enabled: true
whitelist:
include:
- lib/*
exclude:
blacklist:
include:
exclude:

View File

@ -9,8 +9,9 @@
"j4mie/paris": "1.5.4",
"swiftmailer/swiftmailer": "^5.4",
"phpseclib/phpseclib": "*",
"mtdowling/cron-expression": "^1.0",
"nesbot/carbon": "^1.21"
"mtdowling/cron-expression": "^1.1",
"nesbot/carbon": "^1.21",
"soundasleep/html2text": "^0.3.0"
},
"require-dev": {
"codeception/codeception": "*",

763
composer.lock generated

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -9,13 +9,6 @@ class Activator {
function __construct() {
}
function init() {
register_activation_hook(
Env::$file,
array($this, 'activate')
);
}
function activate() {
$migrator = new Migrator();
$migrator->up();

View File

@ -1,8 +1,12 @@
<?php
namespace MailPoet\Config;
use \MailPoet\Models\Setting;
use \MailPoet\Util\Url;
class Changelog {
function __construct() {
}
function init() {
$doing_ajax = (bool)(defined('DOING_AJAX') && DOING_AJAX);
@ -42,20 +46,7 @@ class Changelog {
// save version number
Setting::setValue('version', Env::$version);
global $wp;
$current_url = home_url(add_query_arg($wp->query_string, $wp->request));
if($redirect_url !== $current_url) {
wp_safe_redirect(
add_query_arg(
array(
'mailpoet_redirect' => urlencode($current_url)
),
$redirect_url
)
);
exit;
}
Url::redirectWithReferer($redirect_url);
}
}
}

View File

@ -6,15 +6,14 @@ if(!defined('ABSPATH')) exit;
class Env {
static $version;
static $plugin_name;
static $plugin_url;
static $plugin_path;
static $file;
static $path;
static $views_path;
static $assets_path;
static $assets_url;
static $temp_name;
static $temp_path;
static $temp_url;
static $languages_path;
static $lib_path;
static $plugin_prefix;
@ -27,6 +26,7 @@ class Env {
static $db_username;
static $db_password;
static $db_charset;
static $db_timezone_offset;
static function init($file, $version) {
global $wpdb;
@ -34,12 +34,12 @@ class Env {
self::$file = $file;
self::$path = dirname(self::$file);
self::$plugin_name = 'mailpoet';
self::$plugin_url = plugin_dir_url(__FILE__);
self::$views_path = self::$path . '/views';
self::$assets_path = self::$path . '/assets';
self::$assets_url = plugins_url('/assets', $file);
self::$temp_name = 'temp';
self::$temp_path = self::$path . '/' . self::$temp_name;
$wp_upload_dir = wp_upload_dir();
self::$temp_path = $wp_upload_dir['path'];
self::$temp_url = $wp_upload_dir['url'];
self::$languages_path = self::$path . '/lang';
self::$lib_path = self::$path . '/lib';
self::$plugin_prefix = 'mailpoet_';
@ -59,6 +59,7 @@ class Env {
self::$db_password = DB_PASSWORD;
self::$db_charset = $wpdb->get_charset_collate();
self::$db_source_name = self::dbSourceName(self::$db_host, self::$db_socket, self::$db_port);
self::$db_timezone_offset = self::getDbTimezoneOffset();
}
private static function dbSourceName($host, $socket, $port) {
@ -75,18 +76,12 @@ class Env {
return implode('', $source_name);
}
static function isPluginActivated() {
$activatesPlugins = get_option('active_plugins');
$isActivated = (
in_array(
sprintf('%s/%s.php', basename(self::$path), self::$plugin_name),
$activatesPlugins
) ||
in_array(
sprintf('%s/%s.php', explode('/', plugin_basename(__FILE__))[0], self::$plugin_name),
$activatesPlugins
)
);
return ($isActivated) ? true : false;
private static function getDbTimezoneOffset() {
$mins = get_option('gmt_offset') * 60;
$sgn = ($mins < 0 ? -1 : 1);
$mins = abs($mins);
$hrs = floor($mins / 60);
$mins -= $hrs * 60;
return sprintf('%+d:%02d', $hrs * $sgn, $mins);
}
}

View File

@ -1,11 +1,88 @@
<?php
namespace MailPoet\Config;
use MailPoet\Cron\Workers\Scheduler;
use MailPoet\Cron\Workers\SendingQueue;
use \MailPoet\Models\Setting;
class Hooks {
function __construct() {
}
function init() {
$this->setupSubscribe();
$this->setupWPUsers();
$this->setupImageSize();
$this->setupListing();
}
function setupSubscribe() {
$subscribe = Setting::getValue('subscribe', array());
// Subscribe in comments
if(
isset($subscribe['on_comment']['enabled'])
&&
(bool)$subscribe['on_comment']['enabled']
) {
if(is_user_logged_in()) {
add_action(
'comment_form_field_comment',
'\MailPoet\Subscription\Comment::extendLoggedInForm'
);
} else {
add_action(
'comment_form_after_fields',
'\MailPoet\Subscription\Comment::extendLoggedOutForm'
);
}
add_action(
'comment_post',
'\MailPoet\Subscription\Comment::onSubmit',
60,
2
);
add_action(
'wp_set_comment_status',
'\MailPoet\Subscription\Comment::onStatusUpdate',
60,
2
);
}
// Subscribe in registration form
if(
isset($subscribe['on_register']['enabled'])
&&
(bool)$subscribe['on_register']['enabled']
) {
if(is_multisite()) {
add_action(
'signup_extra_fields',
'\MailPoet\Subscription\Registration::extendForm'
);
add_action(
'wpmu_validate_user_signup',
'\MailPoet\Subscription\Registration::onMultiSiteRegister',
60,
1
);
} else {
add_action(
'register_form',
'\MailPoet\Subscription\Registration::extendForm'
);
add_action(
'register_post',
'\MailPoet\Subscription\Registration::onRegister',
60,
3
);
}
}
}
function setupWPUsers() {
// WP Users synchronization
add_action(
'user_register',
@ -20,7 +97,7 @@ class Hooks {
add_action(
'profile_update',
'\MailPoet\Segments\WP::synchronizeUser',
1
1,2
);
add_action(
'delete_user',
@ -38,19 +115,35 @@ class Hooks {
'\MailPoet\Segments\WP::synchronizeUser',
1
);
}
function setupImageSize() {
add_filter(
'image_size_names_choose',
array(
$this,
'appendImageSizes'
)
array($this, 'appendImageSize'),
10, 1
);
}
function appendImageSizes($sizes) {
function appendImageSize($sizes) {
return array_merge($sizes, array(
'mailpoet_newsletter_max' => __('MailPoet Newsletter'),
'mailpoet_newsletter_max' => __('MailPoet Newsletter')
));
}
function setupListing() {
add_filter(
'set-screen-option',
array($this, 'setScreenOption'),
10, 3
);
}
function setScreenOption($status, $option, $value) {
if(preg_match('/^mailpoet_(.*)_per_page$/', $option)) {
return $value;
} else {
return $status;
}
}
}

View File

@ -4,10 +4,11 @@ namespace MailPoet\Config;
use MailPoet\Models;
use MailPoet\Cron\Supervisor;
use MailPoet\Router;
use MailPoet\Models\Setting;
if(!defined('ABSPATH')) exit;
require_once(ABSPATH . 'wp-admin/includes/plugin.php');
class Initializer {
function __construct($params = array(
'file' => '',
@ -18,20 +19,39 @@ class Initializer {
function init() {
$this->setupDB();
$this->setupActivator();
$this->setupRenderer();
$this->setupLocalizer();
$this->setupMenu();
register_activation_hook(Env::$file, array($this, 'runMigrator'));
register_activation_hook(Env::$file, array($this, 'runPopulator'));
add_action('plugins_loaded', array($this, 'setup'));
add_action('init', array($this, 'onInit'));
add_action('widgets_init', array($this, 'setupWidget'));
}
function setup() {
try {
$this->setupRenderer();
$this->setupLocalizer();
$this->setupMenu();
$this->setupPermissions();
$this->setupAnalytics();
$this->setupChangelog();
$this->setupShortcodes();
$this->setupHooks();
$this->setupImages();
$this->setupPublicAPI();
$this->runQueueSupervisor();
} catch(\Exception $e) {
// if anything goes wrong during init
// automatically deactivate the plugin
deactivate_plugins(Env::$file);
}
}
function onInit() {
$this->setupRouter();
$this->setupWidget();
$this->setupAnalytics();
$this->setupPermissions();
$this->setupChangelog();
$this->setupPublicAPI();
$this->setupPages();
$this->runQueueSupervisor();
$this->setupShortcodes();
$this->setupHooks();
$this->setupImages();
}
function setupDB() {
@ -39,9 +59,14 @@ class Initializer {
\ORM::configure('username', Env::$db_username);
\ORM::configure('password', Env::$db_password);
\ORM::configure('logging', WP_DEBUG);
\ORM::configure('logger', function($query, $time) {
// error_log("\n".$query."\n");
});
\ORM::configure('driver_options', array(
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET TIME_ZONE = "+00:00"'
\PDO::MYSQL_ATTR_INIT_COMMAND =>
'SET TIME_ZONE = "' . Env::$db_timezone_offset. '"'
));
$subscribers = Env::$db_prefix . 'subscribers';
@ -49,8 +74,6 @@ class Initializer {
$newsletters = Env::$db_prefix . 'newsletters';
$newsletter_templates = Env::$db_prefix . 'newsletter_templates';
$segments = Env::$db_prefix . 'segments';
$filters = Env::$db_prefix . 'filters';
$segment_filter = Env::$db_prefix . 'segment_filter';
$forms = Env::$db_prefix . 'forms';
$subscriber_segment = Env::$db_prefix . 'subscriber_segment';
$newsletter_segment = Env::$db_prefix . 'newsletter_segment';
@ -65,8 +88,6 @@ class Initializer {
define('MP_SETTINGS_TABLE', $settings);
define('MP_NEWSLETTERS_TABLE', $newsletters);
define('MP_SEGMENTS_TABLE', $segments);
define('MP_FILTERS_TABLE', $filters);
define('MP_SEGMENT_FILTER_TABLE', $segment_filter);
define('MP_FORMS_TABLE', $forms);
define('MP_SUBSCRIBER_SEGMENT_TABLE', $subscriber_segment);
define('MP_NEWSLETTER_TEMPLATES_TABLE', $newsletter_templates);
@ -75,13 +96,18 @@ class Initializer {
define('MP_SUBSCRIBER_CUSTOM_FIELD_TABLE', $subscriber_custom_field);
define('MP_NEWSLETTER_OPTION_FIELDS_TABLE', $newsletter_option_fields);
define('MP_NEWSLETTER_OPTION_TABLE', $newsletter_option);
define('MP_SENDING_QUEUE_TABLE', $sending_queues);
define('MP_SENDING_QUEUES_TABLE', $sending_queues);
define('MP_NEWSLETTER_STATISTICS_TABLE', $newsletter_statistics);
}
function setupActivator() {
$activator = new Activator();
$activator->init();
function runMigrator() {
$migrator = new Migrator();
$migrator->up();
}
function runPopulator() {
$populator = new Populator();
$populator->up();
}
function setupRenderer() {
@ -95,10 +121,7 @@ class Initializer {
}
function setupMenu() {
$menu = new Menu(
$this->renderer,
Env::$assets_url
);
$menu = new Menu($this->renderer, Env::$assets_url);
$menu->init();
}
@ -108,12 +131,11 @@ class Initializer {
}
function setupWidget() {
$widget = new Widget();
$widget = new Widget($this->renderer);
$widget->init();
}
function setupAnalytics() {
$widget = new Analytics();
$widget->init();
}
@ -128,10 +150,19 @@ class Initializer {
$changelog->init();
}
function setupPages() {
$pages = new \MailPoet\Settings\Pages();
$pages->init();
$subscription_pages = new \MailPoet\Subscription\Pages();
$subscription_pages->init();
}
function setupShortcodes() {
$shortcodes = new Shortcodes();
$shortcodes->init();
}
function setupHooks() {
$hooks = new Hooks();
$hooks->init();
@ -143,13 +174,15 @@ class Initializer {
}
function runQueueSupervisor() {
if(php_sapi_name() === 'cli') return;
try {
$supervisor = new Supervisor();
$supervisor->checkDaemon();
} catch (\Exception $e) {}
} catch(\Exception $e) {
}
}
function setupImages() {
add_image_size('mailpoet_newsletter_max', 1320);
}
}
}

View File

@ -11,8 +11,7 @@ class Localizer {
function init() {
add_action(
'init',
array($this, 'setup'),
0
array($this, 'setup')
);
}

View File

@ -13,6 +13,7 @@ use MailPoet\Settings\Pages;
use MailPoet\Subscribers\ImportExport\BootStrapMenu;
use MailPoet\Util\DKIM;
use MailPoet\Util\Permissions;
use MailPoet\Listing;
if(!defined('ABSPATH')) exit;
@ -67,7 +68,7 @@ class Menu {
'forms'
)
);
add_submenu_page(
$subscribers_page = add_submenu_page(
'mailpoet',
__('Subscribers'),
__('Subscribers'),
@ -78,6 +79,17 @@ class Menu {
'subscribers'
)
);
// add limit per page to screen options
add_action('load-'.$subscribers_page, function() {
add_screen_option('per_page', array(
'label' => _x(
'Number of subscribers per page',
'subscribers per page (screen options)'
),
'option' => 'mailpoet_subscribers_per_page'
));
});
add_submenu_page(
'mailpoet',
__('Segments'),
@ -190,6 +202,8 @@ class Menu {
}
function welcome() {
if((bool)(defined('DOING_AJAX') && DOING_AJAX)) return;
global $wp;
$current_url = home_url(add_query_arg($wp->query_string, $wp->request));
$redirect_url =
@ -240,6 +254,7 @@ class Menu {
function settings() {
$settings = Setting::getAll();
$flags = $this->_getFlags();
// dkim: check if public/private keys have been generated
if(
@ -258,10 +273,9 @@ class Menu {
$data = array(
'settings' => $settings,
'segments' => Segment::getPublished()
->findArray(),
'segments' => Segment::getPublic()->findArray(),
'pages' => Pages::getAll(),
'flags' => $this->_getFlags(),
'flags' => $flags,
'charsets' => Charsets::getAll(),
'current_user' => wp_get_current_user(),
'permissions' => Permissions::getAll(),
@ -294,7 +308,7 @@ class Menu {
} else {
// check if users can register
$flags['registration_enabled'] =
(bool) get_option('users_can_register', false);
(bool)get_option('users_can_register', false);
}
return $flags;
@ -303,8 +317,33 @@ class Menu {
function subscribers() {
$data = array();
// listing: limit per page
$listing_per_page = get_user_meta(
get_current_user_id(), 'mailpoet_subscribers_per_page', true
);
$data['per_page'] = (!empty($listing_per_page))
? (int)$listing_per_page
: Listing\Handler::DEFAULT_LIMIT_PER_PAGE;
$data['segments'] = Segment::findArray();
$data['custom_fields'] = array_map(function($field) {
$field['params'] = unserialize($field['params']);
if(!empty($field['params']['values'])) {
$values = array();
foreach($field['params']['values'] as $value) {
$values[$value['value']] = $value['value'];
}
$field['params']['values'] = $values;
}
return $field;
}, CustomField::findArray());
$data['date_formats'] = Block\Date::getDateFormats();
$data['month_names'] = Block\Date::getMonthNames();
echo $this->renderer->render('subscribers/subscribers.html', $data);
}
@ -328,6 +367,8 @@ class Menu {
$data['segments'] = Segment::findArray();
$data['settings'] = Setting::getAll();
$data['roles'] = $wp_roles->get_names();
$data['roles']['mailpoet_all'] = __('In any WordPress role');
echo $this->renderer->render('newsletters.html', $data);
}
@ -341,11 +382,12 @@ class Menu {
$data = array(
'customFields' => $custom_fields,
'settings' => Setting::getAll(),
);
wp_enqueue_media();
wp_enqueue_script('tinymce-wplink', includes_url('js/tinymce/plugins/wplink/plugin.js'));
wp_enqueue_style('editor', includes_url('css/editor.css'));
echo $this->renderer->render('newsletter/form.html', $data);
echo $this->renderer->render('newsletter/editor.html', $data);
}
function import() {

View File

@ -1,7 +1,7 @@
<?php
namespace MailPoet\Config;
if (!defined('ABSPATH')) exit;
if(!defined('ABSPATH')) exit;
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
@ -214,6 +214,9 @@ class Migrator {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'newsletter_rendered_body longtext,',
'newsletter_rendered_body_hash varchar(250) NULL DEFAULT NULL,',
'newsletter_rendered_subject varchar(250) NULL DEFAULT NULL,',
'subscribers longtext,',
'status varchar(12) NULL DEFAULT NULL,',
'priority mediumint(9) NOT NULL DEFAULT 0,',
@ -221,6 +224,7 @@ class Migrator {
'count_processed mediumint(9) NOT NULL DEFAULT 0,',
'count_to_process mediumint(9) NOT NULL DEFAULT 0,',
'count_failed mediumint(9) NOT NULL DEFAULT 0,',
'scheduled_at TIMESTAMP NOT NULL DEFAULT 0,',
'processed_at TIMESTAMP NOT NULL DEFAULT 0,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
@ -236,10 +240,7 @@ class Migrator {
'newsletter_id mediumint(9) NOT NULL,',
'subscriber_id mediumint(9) NOT NULL,',
'queue_id mediumint(9) NOT NULL,',
'sent_at TIMESTAMP NOT NULL DEFAULT 0,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'sent_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
@ -270,4 +271,4 @@ class Migrator {
return implode("\n", $sql);
}
}
}

View File

@ -8,6 +8,7 @@ use MailPoet\Config\PopulatorData\Templates\PostNotificationsBlankTemplate;
use \MailPoet\Models\Segment;
use \MailPoet\Segments\WP;
use \MailPoet\Models\Setting;
use \MailPoet\Settings\Pages;
if (!defined('ABSPATH')) exit;
@ -48,29 +49,56 @@ class Populator {
$this->createDefaultSegments();
$this->createDefaultSettings();
$this->createMailPoetPage();
}
private function createMailPoetPage() {
$pages = get_posts(array(
'posts_per_page' => 1,
'orderby' => 'date',
'order' => 'DESC',
'post_type' => 'mailpoet_page'
));
$page = null;
if(!empty($pages)) {
$page = array_shift($pages);
if(strpos($page->post_content, '[mailpoet_page]') === false) {
$page = null;
}
}
if($page === null) {
$mailpoet_page_id = Pages::createMailPoetPage();
} else {
$mailpoet_page_id = (int)$page->ID;
}
Setting::setValue('subscription.unsubscribe_page', $mailpoet_page_id);
Setting::setValue('subscription.manage_page', $mailpoet_page_id);
Setting::setValue('subscription.confirmation_page', $mailpoet_page_id);
}
private function createDefaultSettings() {
$current_user = wp_get_current_user();
// user name
$user_name = '';
if($current_user->user_firstname) {
$user_name = $current_user->user_firstname;
}
if($current_user->user_lastname) {
if($user_name) {
$user_name .= ' '.$current_user->user_lastname;
}
}
if(!$user_name) {
$user_name = $current_user->display_name;
}
// default sender info based on current user
$sender = array(
'name' => $current_user->display_name,
'address' => $current_user->user_email
);
// default from name & address
Setting::setValue('sender', array(
'name' => $user_name,
'address' => $current_user->user_email
Setting::setValue('sender', $sender);
// enable signup confirmation by default
Setting::setValue('signup_confirmation', array(
'enabled' => true,
'from' => array(
'name' => get_option('blogname'),
'address' => get_option('admin_email')
),
'reply_to' => $sender
));
}
@ -148,6 +176,14 @@ class Populator {
'name' => 'nthWeekDay',
'newsletter_type' => 'notification',
),
array(
'name' => 'schedule',
'newsletter_type' => 'notification',
),
array(
'name' => 'segments',
'newsletter_type' => 'notification',
)
);
}

View File

@ -142,7 +142,7 @@ class BlankTemplate {
"blocks" => array(
array(
"type" => "footer",
"text" => "<a href=\"[unsubscribeUrl]\">Unsubscribe</a> | <a href=\"[manageSubscriptionUrl]\">Manage subscription</a><br /><b>Add your postal address here!</b>",
"text" => "<a href=\"[subscription:unsubscribe_url]\">Unsubscribe</a> | <a href=\"[subscription:manage_url]\">Manage subscription</a><br /><b>Add your postal address here!</b>",
"styles" => array(
"block" => array(
"backgroundColor" => "transparent"
@ -179,7 +179,7 @@ class BlankTemplate {
),
"h1" => array(
"fontColor" => "#111111",
"fontFamily" => "Arial Black",
"fontFamily" => "Arial",
"fontSize" => "24px"
),
"h2" => array(

View File

@ -46,7 +46,7 @@ class FranksRoastHouseTemplate {
"blocks" => array(
array(
"type" => "header",
"text" => __("Display problems?&nbsp;<a href=\"[viewInBrowserUrl]\">View it in your browser</a>"),
"text" => __("Display problems?&nbsp;<a href=\"[newsletter:view_in_browser_url]\">View it in your browser</a>"),
"styles" => array(
"block" => array(
"backgroundColor" => "#ccc6c6"
@ -68,7 +68,7 @@ class FranksRoastHouseTemplate {
"link" => "http://www.example.com",
"src" => $this->template_image_url . "/header-v2.jpg",
"alt" => __("Frank's Roast House"),
"padded" => false,
"fullWidth" => true,
"width" => "600px",
"height" => "220px",
"styles" => array(
@ -95,7 +95,7 @@ class FranksRoastHouseTemplate {
"link" => "http://example.org",
"src" => $this->template_image_url . "/coffee-grain.jpg",
"alt" => __("coffee-grain-3-1329675-1599x941"),
"padded" => true,
"fullWidth" => false,
"width" => "1599px",
"height" => "777px",
"styles" => array(
@ -139,7 +139,7 @@ class FranksRoastHouseTemplate {
"link" => "http://example.org",
"src" => $this->template_image_url . "/sandwich.jpg",
"alt" => "sandwich",
"padded" => true,
"fullWidth" => false,
"width" => "640px",
"height" => "344px",
"styles" => array(
@ -168,6 +168,7 @@ class FranksRoastHouseTemplate {
"fontColor" => "#ffffff",
"fontFamily" => "Arial",
"fontSize" => "14px",
"fontWeight" => "normal",
"textAlign" => "center"
)
)
@ -238,7 +239,7 @@ class FranksRoastHouseTemplate {
"link" => "http://example.org",
"src" => $this->template_image_url . "/map-v2.jpg",
"alt" => __("map-v2"),
"padded" => true,
"fullWidth" => false,
"width" => "636px",
"height" => "342px",
"styles" => array(
@ -279,19 +280,19 @@ class FranksRoastHouseTemplate {
"blocks" => array(
array(
"type" => "footer",
"text" => __("<p><span style=\"color: #000000;\"><a href=\"[unsubscribeUrl]\" style=\"color: #000000;\">Unsubscribe</a> | <a href=\"[manageSubscriptionUrl]\" style=\"color: #000000;\">Manage subscription</a></span><br /><span style=\"color: #000000;\">12345 MailPoet Drive, EmailVille, 76543</span></p>"),
"text" => __("<p><a href=\"[subscription:unsubscribe_url]\">Unsubscribe</a> | <a href=\"[subscription:manage_url]\">Manage subscription</a><br />12345 MailPoet Drive, EmailVille, 76543</p>"),
"styles" => array(
"block" => array(
"backgroundColor" => "#a9a7a7"
),
"text" => array(
"fontColor" => "#ffffff",
"fontColor" => "#000000",
"fontFamily" => "Arial",
"fontSize" => "12px",
"textAlign" => "center"
),
"link" => array(
"fontColor" => "#ffffff",
"fontColor" => "#000000",
"textDecoration" => "underline"
)
)

View File

@ -90,7 +90,7 @@ class PostNotificationsBlankTemplate {
"link" => "http://example.org",
"src" => $this->template_image_url . "/ALC-widget-icon.png",
"alt" => __("ALC-widget-icon"),
"padded" => true,
"fullWidth" => false,
"width" => "200px",
"height" => "134px",
"styles" => array(
@ -242,7 +242,7 @@ class PostNotificationsBlankTemplate {
"blocks" => array(
array(
"type" => "footer",
"text" => __("<a href=\"[unsubscribeUrl]\">Unsubscribe</a> | <a href=\"[manageSubscriptionUrl]\">Manage subscription</a><br /><b>Add your postal address here!</b>"),
"text" => __("<a href=\"[subscription:unsubscribe_url]\">Unsubscribe</a> | <a href=\"[subscription:manage_url]\">Manage subscription</a><br /><b>Add your postal address here!</b>"),
"styles" => array(
"block" => array(
"backgroundColor" => "transparent"
@ -325,6 +325,7 @@ class PostNotificationsBlankTemplate {
"fontColor" => "#ffffff",
"fontFamily" => "Arial",
"fontSize" => "18px",
"fontWeight" => "normal",
"textAlign" => "center"
)
)

View File

@ -46,7 +46,7 @@ class WelcomeTemplate {
"blocks" => array(
array(
"type" => "header",
"text" => __("Display problems?&nbsp;<a href=\"[viewInBrowserUrl]\">View it in your browser</a>"),
"text" => __("Display problems?&nbsp;<a href=\"[newsletter:view_in_browser_url]\">View it in your browser</a>"),
"styles" => array(
"block" => array(
"backgroundColor" => "transparent"
@ -68,7 +68,7 @@ class WelcomeTemplate {
"link" => "http://example.org",
"src" => $this->template_image_url . "/logo-header.gif",
"alt" => "logo-header",
"padded" => true,
"fullWidth" => false,
"width" => "233px",
"height" => "118px",
"styles" => array(
@ -224,7 +224,7 @@ class WelcomeTemplate {
"blocks" => array(
array(
"type" => "footer",
"text" => __("<a href=\"[unsubscribeUrl]\">Unsubscribe</a> | <a href=\"[manageSubscriptionUrl]\">Manage subscription</a><br /><b>Add your postal address here!</b>"),
"text" => __("<a href=\"[subscription:unsubscribe_url]\">Unsubscribe</a> | <a href=\"[subscription:manage_url]\">Manage subscription</a><br /><b>Add your postal address here!</b>"),
"styles" => array(
"block" => array(
"backgroundColor" => "transparent"

View File

@ -7,35 +7,39 @@ use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
class PublicAPI {
public $api;
public $endpoint;
public $action;
public $data;
function __construct() {
# http://example.com/?mailpoet-api&section=&action=&payload=
$this->api = isset($_GET['mailpoet-api']) ? true : false;
$this->section = isset($_GET['section']) ? $_GET['section'] : false;
# http://example.com/?mailpoet&endpoint=&action=&data=
$this->api = isset($_GET['mailpoet']) ? true : false;
$this->endpoint = isset($_GET['endpoint']) ?
Helpers::underscoreToCamelCase($_GET['endpoint']) :
false;
$this->action = isset($_GET['action']) ?
Helpers::underscoreToCamelCase($_GET['action']) :
false;
$this->payload = isset($_GET['payload']) ?
json_decode(urldecode($_GET['payload']), true) :
false;
$this->data = isset($_GET['data']) ? $_GET['data'] : false;
}
function init() {
if(!$this->api && !$this->section) return;
$this->_checkAndCallMethod($this, $this->section, $terminate = true);
if(!$this->api && !$this->endpoint) return;
$this->_checkAndCallMethod($this, $this->endpoint, $terminate_request = true);
}
function queue() {
try {
$queue = new Daemon($this->payload);
$queue = new Daemon($this->data);
$this->_checkAndCallMethod($queue, $this->action);
} catch(\Exception $e) {
// mailer configuration error
}
}
private function _checkAndCallMethod($class, $method, $terminate = false) {
private function _checkAndCallMethod($class, $method, $terminate_request = false) {
if(!method_exists($class, $method)) {
if(!$terminate) return;
if(!$terminate_request) return;
header('HTTP/1.0 404 Not Found');
exit;
}

View File

@ -59,7 +59,7 @@ class Renderer {
}
function detectCache() {
$cache_path = Env::$views_path . '/cache';
$cache_path = Env::$temp_path . '/cache';
if(WP_DEBUG === false) {
return $cache_path;
}

View File

@ -1,5 +1,8 @@
<?php
namespace MailPoet\Config;
use \MailPoet\Models\Newsletter;
use \MailPoet\Models\Subscriber;
use \MailPoet\Models\SubscriberSegment;
class Shortcodes {
function __construct() {
@ -9,6 +12,26 @@ class Shortcodes {
// form widget shortcode
add_shortcode('mailpoet_form', array($this, 'formWidget'));
add_shortcode('wysija_form', array($this, 'formWidget'));
// subscribers count shortcode
add_shortcode('mailpoet_subscribers_count', array(
$this, 'getSubscribersCount'
));
add_shortcode('wysija_subscribers_count', array(
$this, 'getSubscribersCount'
));
// archives page
add_shortcode('mailpoet_archive', array(
$this, 'getArchive'
));
add_filter('mailpoet_archive_date', array(
$this, 'renderArchiveDate'
), 2);
add_filter('mailpoet_archive_subject', array(
$this, 'renderArchiveSubject'
), 2);
}
function formWidget($params = array()) {
@ -23,4 +46,76 @@ class Shortcodes {
));
}
}
function getSubscribersCount($params) {
if(!empty($params['segments'])) {
$segment_ids = array_map(function($segment_id) {
return (int)trim($segment_id);
}, explode(',', $params['segments']));
}
if(empty($segment_ids)) {
return Subscriber::filter('subscribed')->count();
} else {
return SubscriberSegment::whereIn('segment_id', $segment_ids)
->select('subscriber_id')->distinct()
->filter('subscribed')
->findResultSet()->count();
}
}
function getArchive($params) {
if(!empty($params['segments'])) {
$segment_ids = array_map(function($segment_id) {
return (int)trim($segment_id);
}, explode(',', $params['segments']));
}
$newsletters = array();
$html = '';
// TODO: needs more advanced newsletters in order to finish
$newsletters = Newsletter::limit(10)->orderByDesc('created_at')->findMany();
if(empty($newsletters)) {
return apply_filters(
'mailpoet_archive_no_newsletters',
__('Oops! There are no newsletters to display.')
);
} else {
$title = apply_filters('mailpoet_archive_title', '');
if(!empty($title)) {
$html .= '<h3 class="mailpoet_archive_title">'.$title.'</h3>';
}
$html .= '<ul class="mailpoet_archive">';
foreach($newsletters as $newsletter) {
$html .= '<li>'.
'<span class="mailpoet_archive_date">'.
apply_filters('mailpoet_archive_date', $newsletter).
'</span>
<span class="mailpoet_archive_subject">'.
apply_filters('mailpoet_archive_subject', $newsletter).
'</span>
</li>';
}
$html .= '</ul>';
}
return $html;
}
function renderArchiveDate($newsletter) {
return date_i18n(
get_option('date_format'),
strtotime($newsletter->created_at)
);
}
function renderArchiveSubject($newsletter) {
return '<a href="TODO" target="_blank" title="'
.esc_attr(__('Preview in new tab')).'">'
.esc_attr($newsletter->subject).
'</a>';
}
}

View File

@ -1,39 +1,87 @@
<?php
namespace MailPoet\Config;
use \MailPoet\Models\Subscriber;
use \MailPoet\Util\Security;
use \MailPoet\Models\Form;
if(!defined('ABSPATH')) exit;
class Widget {
function __construct() {
private $renderer = null;
function __construct($renderer = null) {
if($renderer !== null) {
$this->renderer = $renderer;
}
}
function init() {
add_action('widgets_init', array($this, 'registerWidget'));
$this->registerWidget();
if(!is_admin()) {
//$this->setupActions();
add_action('widgets_init', array($this, 'setupDependencies'));
$this->setupDependencies();
$this->setupIframe();
} else {
add_action('widgets_init', array($this, 'setupAdminDependencies'));
$this->setupAdminDependencies();
}
}
function setupIframe() {
$form_id = (isset($_GET['mailpoet_form_iframe']) ? (int)$_GET['mailpoet_form_iframe'] : 0);
if($form_id > 0) {
$form = Form::findOne($form_id);
if($form !== false) {
$form_widget = new \MailPoet\Form\Widget();
$form_html = $form_widget->widget(array(
'form' => $form_id,
'form_type' => 'iframe'
));
// capture javascripts
ob_start();
wp_print_scripts('jquery');
wp_print_scripts('mailpoet_vendor');
wp_print_scripts('mailpoet_public');
$scripts = ob_get_contents();
ob_end_clean();
// language attributes
$language_attributes = array();
$is_rtl = (bool)(function_exists('is_rtl') && is_rtl());
if($is_rtl) {
$language_attributes[] = 'dir="rtl"';
}
if($lang = get_bloginfo('language')) {
if(get_option('html_type') === 'text/html') {
$language_attributes[] = "lang=\"$lang\"";
}
}
$language_attributes = apply_filters(
'language_attributes', implode(' ', $language_attributes)
);
$data = array(
'language_attributes' => $language_attributes,
'scripts' => $scripts,
'form' => $form_html,
'mailpoet_form' => array(
'ajax_url' => admin_url('admin-ajax.php', 'absolute'),
'is_rtl' => $is_rtl,
'token' => Security::generateToken()
)
);
echo $this->renderer->render('form/iframe.html', $data);
}
exit();
}
}
function registerWidget() {
register_widget('\MailPoet\Form\Widget');
// subscribers count shortcode
add_shortcode('mailpoet_subscribers_count', array(
$this, 'getSubscribersCount'
));
add_shortcode('wysija_subscribers_count', array(
$this, 'getSubscribersCount'
));
}
function getSubscribersCount($params) {
return Subscriber::filter('subscribed')->count();
}
function setupDependencies() {
@ -82,6 +130,9 @@ class Widget {
}
}
// TODO: extract this method into an Initializer
// - the "ajax" part might probably be useless
// - the "post" (non-ajax) part needs to be redone properly
function setupActions() {
// ajax requests
add_action(

78
lib/Cron/CronHelper.php Normal file
View File

@ -0,0 +1,78 @@
<?php
namespace MailPoet\Cron;
use MailPoet\Models\Setting;
use MailPoet\Util\Security;
if(!defined('ABSPATH')) exit;
class CronHelper {
const daemon_execution_limit = 20;
const daemon_execution_timeout = 35;
const daemon_request_timeout = 2;
static function createDaemon($token) {
$daemon = array(
'status' => 'starting',
'counter' => 0,
'token' => $token
);
self::saveDaemon($daemon);
return $daemon;
}
static function getDaemon() {
return Setting::getValue('cron_daemon');
}
static function saveDaemon($daemon) {
$daemon['updated_at'] = time();
return Setting::setValue(
'cron_daemon',
$daemon
);
}
static function createToken() {
return Security::generateRandomString();
}
static function accessDaemon($token, $timeout = self::daemon_request_timeout) {
$data = serialize(array('token' => $token));
$url = '/?mailpoet&endpoint=queue&action=run&data=' .
base64_encode($data);
$args = array(
'timeout' => $timeout,
'user-agent' => 'MailPoet (www.mailpoet.com) Cron'
);
$result = wp_remote_get(
self::getSiteUrl() . $url,
$args
);
return wp_remote_retrieve_body($result);
}
private static function getSiteUrl() {
// additional check for some sites running on a virtual machine or behind
// proxy where there could be different ports (e.g., host:8080 => guest:80)
// if the site URL does not contain a port, return the URL
if(!preg_match('!^https?://.*?:\d+!', site_url())) return site_url();
preg_match('!://(?P<host>.*?):(?P<port>\d+)!', site_url(), $server);
// connect to the URL with port
$fp = @fsockopen($server['host'], $server['port'], $errno, $errstr, 1);
if($fp) return site_url();
// connect to the URL without port
$fp = @fsockopen($server['host'], $server['port'], $errno, $errstr, 1);
if($fp) return preg_replace('!(?=:\d+):\d+!', '$1', site_url());
// throw an error if all connection attempts failed
throw new \Exception(__('Site URL is unreachable.'));
}
static function checkExecutionTimer($timer) {
$elapsed_time = microtime(true) - $timer;
if($elapsed_time >= self::daemon_execution_limit) {
throw new \Exception(__('Maximum execution time reached.'));
}
}
}

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