Compare commits

...

126 Commits

Author SHA1 Message Date
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
8d61866b77 Merge pull request #278 from mailpoet/drag_and_drop
Editor: Drag&Drop
2015-12-18 14:54:30 +01:00
0831c748b1 Merge pull request #275 from mailpoet/newsletter_template
Newsletter template preview
2015-12-18 14:54:21 +01:00
b2a0bc3860 Merge pull request #273 from mailpoet/review
Global Code Review.
2015-12-18 14:44:53 +01:00
1f99345e7b Remove obsolete code 2015-12-17 18:03:16 +02:00
89782bc94b Refactor editor JS, change blocks to extend base methods 2015-12-17 16:58:02 +02:00
155ff09280 Fix incorrect insertion order for containers 2015-12-17 15:21:56 +02:00
132e6d3342 Rename Wordpress component to Communication component, fix preview JS
syntax
2015-12-17 12:40:26 +02:00
f02699158f Apply newsletter background color to template preview images 2015-12-17 12:40:26 +02:00
be3462925d Change server returned newsletter body to be a json object, not a string 2015-12-17 12:40:26 +02:00
3d82230d10 Merge pull request #274 from mailpoet/acceptance_tests_update
Acceptance test related fixes
2015-12-15 13:21:34 +01:00
c20c46fd86 Removed useless 'use' in Router/Setup 2015-12-15 13:11:07 +01:00
6e63c72aa5 Reinstall feature
- implemented reinstall in Settings > Advanced
- shorten placeholder for Form name input
2015-12-15 13:07:43 +01:00
e059eec5ea Acceptance tests have been removed from the PHP project. 2015-12-14 16:09:20 +01:00
a2d38c9076 Merge pull request #270 from mailpoet/unit_tests_fix
Unit tests + Welcome page
2015-12-14 15:48:52 +01:00
8d507b2ee0 Merge pull request #271 from mailpoet/history_fix
Fix react-router dependency issue
2015-12-14 15:31:02 +01:00
5e2979c283 Changes history version to 1.13.1 2015-12-14 14:29:51 +02:00
84ec0de3cd Unit tests + Welcome page
- fixed unit tests
- commented out failing tests that require changes in the code
- added new welcome page
2015-12-11 17:17:59 +01:00
ee07780833 Version bump: 0.0.8. 2015-12-11 14:42:59 +01:00
ed104156a9 New build script as new plugin, independent from wysija-newsletters. 2015-12-11 14:42:15 +01:00
90f2e9ff9d Merge branch 'fix_history' 2015-12-11 14:13:15 +01:00
98fb7aa65e Fix react history pushState error. 2015-12-11 14:12:35 +01:00
a51eb59cf8 Merge pull request #266 from mailpoet/newsletter_template
Editor: Template related fixes
2015-12-11 13:09:54 +01:00
270023b89c Display error and success messages for template saving and export 2015-12-10 17:13:23 +02:00
7d77e075e9 Make long template name and description fit in the box 2015-12-10 17:13:23 +02:00
9bbe36b3cb Set subject of new newsletters to "Draft newsletter" 2015-12-10 17:13:23 +02:00
7a904ed093 Add SVG icons for standard, welcome and notification email types 2015-12-10 17:13:23 +02:00
e3c065b353 Add Post Notifications Blank Template 2015-12-10 17:13:22 +02:00
4ca2872e0e Add Welcome newsletter template 2015-12-10 17:13:22 +02:00
ce338f7fe2 Add border to template thumbnails 2015-12-10 17:13:22 +02:00
fa28b0a955 Merge pull request #267 from mailpoet/settings_round_1
Settings
2015-12-10 13:04:37 +01:00
a298650187 Settings
- added default from name & address based on wp_user on install
- fixed issue with Setting::setValue (added auto-serialize of value if is_array?)
- removed daily notifications from basics settings
2015-12-10 11:44:44 +01:00
95772ef68a Merge pull request #261 from mailpoet/form_editor_round_1
Form editor round 1
2015-12-09 14:07:33 +01:00
e1c94db516 edit form name follows newsletter's design 2015-12-08 17:10:45 +01:00
7be1a11d1e Form editor
- fixed validations on radio type
- fixed date format for months
- added custom fields storing on subscribe
- fixed date widget (select today's date)
- fixed validation on form widget
2015-12-08 16:55:30 +01:00
c04b95c09a Merge pull request #262 from mailpoet/editor_transitions
Editor: fix transitions and thumbnails
2015-12-07 20:00:07 +01:00
cdfeb8d512 Fix template thumbnails by reverting to earlier html2canvas version 2015-12-07 18:50:50 +02:00
3ef8067968 Remove transition class after it ends for editor content blocks 2015-12-07 18:50:17 +02:00
9fb04bc3c0 first round of fixes #255 2015-12-07 16:54:08 +01:00
25727ea0ba Version bump: 0.0.7 2015-12-07 14:43:16 +01:00
4c8ac369b7 Merge pull request #260 from mailpoet/newsletter_sending
Bugfixes on sending
2015-12-07 14:35:38 +01:00
268dabdc9f Bugfixes on sending
- added checks to prevent adding to the queue useless items
- fixed issue with mta settings (duplicated "host" input / renamed duplicate to "domain" for MailGun)
- fixed namespace issue on cron daemon/supervisor
- fixed typo in migration preventing the newsletter_templates table to be created.
- partially fixed cron.jsx
2015-12-07 13:29:42 +01:00
179 changed files with 7893 additions and 5636 deletions

2
.gitignore vendored
View File

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

View File

@ -8,7 +8,6 @@ MailPoet done the right way.
``` ```
php php
nodejs nodejs
phantomjs
wordpress wordpress
``` ```
@ -47,16 +46,6 @@ $ ./do compile:all
$ ./do test:unit $ ./do test:unit
``` ```
- Acceptance tests:
```sh
$ ./do test:acceptance
```
- Run all tests:
```sh
$ ./do test:all
```
- Debug tests: - Debug tests:
```sh ```sh
$ ./do test:debug $ ./do test:debug

View File

@ -5,13 +5,15 @@
@require 'common' @require 'common'
@require 'modal' @require 'modal'
@require 'notice' @require 'notice'
@require 'parsley'
@require 'form_editor' @require 'form_editor'
@require 'listing' @require 'listing'
@require 'box' @require 'box'
@require 'breadcrumb' @require 'breadcrumb'
@require 'form' @require 'form'
@require 'parsley'
@require 'form_validation'
@require 'settings' @require 'settings'
@require 'progress_bar' @require 'progress_bar'

File diff suppressed because one or more lines are too long

View File

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

View File

@ -4,6 +4,9 @@
icons = '../img/form_editor_icons.png' icons = '../img/form_editor_icons.png'
handle_icon = '../img/handle.png' handle_icon = '../img/handle.png'
#mailpoet_form_name
font-size: 23px
#mailpoet_form_history #mailpoet_form_history
display: none display: none
@ -99,6 +102,7 @@ handle_icon = '../img/handle.png'
/* MailPoet Form wrapper */ /* MailPoet Form wrapper */
#mailpoet_form_wrapper #mailpoet_form_wrapper
position: relative position: relative
margin: 20px 0 0 0
/* MailPoet Form container */ /* MailPoet Form container */
#mailpoet_form_container #mailpoet_form_container
@ -121,6 +125,7 @@ handle_icon = '../img/handle.png'
float: none float: none
#mailpoet_form_toolbar #mailpoet_form_toolbar
z-index: 999
position: absolute position: absolute
width: 400px width: 400px

View File

@ -0,0 +1,6 @@
.parsley-errors-list
margin-top: 8px
.parsley-required
.parsley-custom-error-message
color: #b94a48

View File

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

View File

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

View File

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

View File

@ -48,7 +48,7 @@
.mailpoet_save_as_template_container, .mailpoet_save_as_template_container,
.mailpoet_export_template_container .mailpoet_export_template_container
border-radius(3px) border-radius(3px)
float: left display: inline-block
clear: both clear: both
margin-top: 5px margin-top: 5px

View File

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

View File

@ -30,8 +30,3 @@ $block-hover-highlight-color = $primary-active-color
.mailpoet_content .mailpoet_content
position: relative position: relative
.mailpoet_block_transition_in
animation-fade-in-and-scale-up()
.mailpoet_block_transition_out
animation-fade-out-and-scale-down()

View File

@ -129,3 +129,7 @@ body
#mailpoet_modal_close #mailpoet_modal_close
display: none display: none
.wrap > .mailpoet_notice,
.update-nag
margin-left: 2px + 15px !important

View File

@ -1,5 +1,5 @@
animation-slide-open-downwards() 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 max-height: 2000px
opacity: 1 opacity: 1
@ -9,45 +9,23 @@ animation-slide-open-downwards()
overflow-y: hidden overflow-y: hidden
animation-background-color() 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-fade-in()
animation-name: fadeInAndScaleUp animation-name: fadeIn
animation-duration: 500ms animation-duration: 300ms
animation-fill-mode: forwards
animation-fade-out-and-scale-down()
animation-name: fadeOutAndScaleDown
animation-duration: 500ms
animation-fill-mode: forwards animation-fill-mode: forwards
animation-timing-function: ease-in
animation-fade-in-and-scale-horizontally() 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% { 0% {
opacity: 0.3 opacity: 0.3
max-height: 0
overflow: hidden
} }
100% { 100% {
opacity: 1 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

@ -1,3 +1,4 @@
@import 'nib' @import 'nib'
@require 'parsley' @require 'parsley'
@require 'form_validation'

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -1,98 +1,106 @@
define( define(
[ [
'react', 'react',
'react-dom', 'react-dom',
'mailpoet' 'mailpoet'
], ],
function ( function(
React, React,
ReactDOM, ReactDOM,
MailPoet MailPoet
) { ) {
var CronControl = React.createClass({ var CronControl = React.createClass({
getInitialState: function () { getInitialState: function() {
return (cronDaemon) ? cronDaemon : null; return {
}, status: 'loading'
getDaemonData: function () { };
MailPoet.Ajax.post({ },
endpoint: 'cron', getCronData: function() {
action: 'getDaemonStatus' MailPoet.Ajax.post({
}).done(function (response) { endpoint: 'cron',
jQuery('.button-primary').removeClass('disabled'); action: 'getStatus'
if (!response) { })
this.replaceState(); .done(function(response) {
} else { jQuery('.button-primary')
this.setState(response); .removeClass('disabled');
} if(response.status !== undefined) {
}.bind(this)); this.setState(response);
}, } else {
componentDidMount: function componentDidMount() { this.replaceState();
if (this.isMounted()) { }
this.getDaemonData; }.bind(this));
setInterval(this.getDaemonData, 5000); },
} componentDidMount: function() {
}, if(this.isMounted()) {
controlDaemon: function (action) { this.getCronData();
jQuery('.button-primary').addClass('disabled'); setInterval(this.getCronData, 5000);
MailPoet.Ajax.post({ }
endpoint: 'cron', },
action: 'controlDaemon', controlCron: function(action) {
data: {'action': action} if(jQuery('.button-primary').hasClass('disabled')) {
}).done(function (response) { return;
if (!response) { }
this.replaceState(); jQuery('.button-primary')
} else { .addClass('disabled');
this.setState(response); MailPoet.Ajax.post({
} endpoint: 'cron',
}.bind(this)); action: action,
}, })
render: function () { .done(function(response) {
if (!this.state) { if(!response.result) {
return MailPoet.Notice.error(MailPoetI18n.daemonControlError);
<div> }
Woops, daemon is not running ;\ }.bind(this));
</div> },
} render: function() {
switch (this.state.status) { if(this.state.status === 'loading') {
case 'started': return(<div>Loading daemon status...</div>);
return ( }
switch(this.state.status) {
case 'started':
return(
<div> <div>
<div> Cron daemon is running.
Cron daemon is running. <br/>
<br/> <br/>
<br/> It was started
It was started <strong> {this.state.timeSinceStart} </strong> and last executed
<strong> {this.state.timeSinceStart} </strong> and last executed <strong> {this.state.timeSinceUpdate} </strong> for a total of
<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).
<strong> {this.state.counter} </strong> times (once every 30 seconds, unless it was interrupted and restarted). <br />
<br /> <br />
<br /> <a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'stop')}>Stop</a>
<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>
</div>
</div> </div>
); );
break; break;
case 'paused': case 'starting':
case 'stopped': case 'stopping':
return ( return(
<div>
Daemon is {this.state.status}
</div>
);
break;
case 'stopped':
return(
<div> <div>
Daemon is {this.state.status} Daemon is {this.state.status}
<br /> <br />
<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')}>Start</a>
</div> </div>
) );
break; break;
} }
} }
}); });
let container = document.getElementById('cron_container');
if (container) { const container = document.getElementById('cron_container');
ReactDOM.render(
if(container) {
ReactDOM.render(
<CronControl />, <CronControl />,
document.getElementById('cron_status') container
) );
} }
} });
);

View File

@ -1,31 +1,31 @@
define([ define([
'react', 'react'
'react-checkbox-group'
], ],
function( function(
React, React
CheckboxGroup
) { ) {
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() { render: function() {
var selected_values = this.props.item[this.props.field.name] || ''; const isChecked = !!(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();
});
}
var count = Object.keys(this.props.field.values).length;
var options = Object.keys(this.props.field.values).map( const options = Object.keys(this.props.field.values).map(
function(value, index) { function(value, index) {
return ( return (
<p key={ 'checkbox-' + index }> <p key={ 'checkbox-' + index }>
<label> <label>
<input type="checkbox" value={ value } /> <input
&nbsp;{ this.props.field.values[value] } ref="checkbox"
type="checkbox"
value="1"
checked={ isChecked }
onChange={ this.onValueChange }
name={ this.props.field.name }
/>
{ this.props.field.values[value] }
</label> </label>
</p> </p>
); );
@ -33,30 +33,10 @@ function(
); );
return ( return (
<CheckboxGroup <div>
name={ this.props.field.name }
value={ selected_values }
ref={ this.props.field.name }
onChange={ this.handleValueChange }>
{ options } { 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/select.jsx',
'form/fields/radio.jsx', 'form/fields/radio.jsx',
'form/fields/checkbox.jsx', 'form/fields/checkbox.jsx',
'form/fields/selection.jsx' 'form/fields/selection.jsx',
'form/fields/date.jsx',
], ],
function( function(
React, React,
@ -14,7 +15,8 @@ function(
FormFieldSelect, FormFieldSelect,
FormFieldRadio, FormFieldRadio,
FormFieldCheckbox, FormFieldCheckbox,
FormFieldSelection FormFieldSelection,
FormFieldDate
) { ) {
var FormField = React.createClass({ var FormField = React.createClass({
renderField: function(data, inline = false) { renderField: function(data, inline = false) {
@ -55,6 +57,10 @@ function(
case 'selection': case 'selection':
field = (<FormFieldSelection {...data} />); field = (<FormFieldSelection {...data} />);
break; break;
case 'date':
field = (<FormFieldDate {...data} />);
break;
} }
if(inline === true) { if(inline === true) {
@ -66,10 +72,10 @@ function(
); );
} else { } else {
return ( return (
<p key={ 'field-' + (data.index || 0) }> <div key={ 'field-' + (data.index || 0) }>
{ field } { field }
{ description } { description }
</p> </div>
); );
} }
}, },

View File

@ -7,7 +7,6 @@ function(
var FormFieldRadio = React.createClass({ var FormFieldRadio = React.createClass({
render: function() { render: function() {
var selected_value = this.props.item[this.props.field.name]; var selected_value = this.props.item[this.props.field.name];
var count = Object.keys(this.props.field.values).length;
var options = Object.keys(this.props.field.values).map( var options = Object.keys(this.props.field.values).map(
function(value, index) { function(value, index) {
@ -20,7 +19,7 @@ function(
value={ value } value={ value }
onChange={ this.props.onValueChange } onChange={ this.props.onValueChange }
name={ this.props.field.name } /> name={ this.props.field.name } />
&nbsp;{ this.props.field.values[value] } { this.props.field.values[value] }
</label> </label>
</p> </p>
); );

View File

@ -231,7 +231,7 @@ var WysijaHistory = {
/* MailPoet Form */ /* MailPoet Form */
var WysijaForm = { var WysijaForm = {
version: '0.6', version: '0.7',
options: { options: {
container: 'mailpoet_form_container', container: 'mailpoet_form_container',
editor: 'mailpoet_form_editor', editor: 'mailpoet_form_editor',
@ -317,6 +317,7 @@ var WysijaForm = {
save: function() { save: function() {
var position = 1, var position = 1,
data = { data = {
'name': $F('mailpoet_form_name'),
'settings': $('mailpoet_form_settings').serialize(true), 'settings': $('mailpoet_form_settings').serialize(true),
'body': [], 'body': [],
'styles': (MailPoet.CodeEditor !== undefined) ? MailPoet.CodeEditor.getValue() : null 'styles': (MailPoet.CodeEditor !== undefined) ? MailPoet.CodeEditor.getValue() : null
@ -616,6 +617,28 @@ var WysijaForm = {
// this is a url, so do not encode the protocol // this is a url, so do not encode the protocol
return encodeURI(str).replace(/[!'()*]/g, escape); 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;
} }
}; };
@ -824,10 +847,6 @@ WysijaForm.Block = Class.create({
Effect.Fade(this.element.identify(), { Effect.Fade(this.element.identify(), {
duration: 0.2, duration: 0.2,
afterFinish: function(effect) { 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 // remove placeholder
if(effect.element.previous('.block_placeholder') !== undefined) { if(effect.element.previous('.block_placeholder') !== undefined) {
effect.element.previous('.block_placeholder').remove(); effect.element.previous('.block_placeholder').remove();

View File

@ -47,7 +47,6 @@ define([
autoScroll: true, autoScroll: true,
onstart: function(event) { onstart: function(event) {
console.log('Drag start', event, this);
if (that.options.cloneOriginal === true) { if (that.options.cloneOriginal === true) {
// Use substitution instead of a clone // Use substitution instead of a clone

View File

@ -11,10 +11,10 @@ define([
'newsletter_editor/blocks/base', 'newsletter_editor/blocks/base',
'newsletter_editor/blocks/button', 'newsletter_editor/blocks/button',
'newsletter_editor/blocks/divider', 'newsletter_editor/blocks/divider',
'newsletter_editor/components/wordpress', 'newsletter_editor/components/communication',
'underscore', 'underscore',
'jquery' 'jquery'
], function(App, BaseBlock, ButtonBlock, DividerBlock, WordpressComponent, _, jQuery) { ], function(App, BaseBlock, ButtonBlock, DividerBlock, CommunicationComponent, _, jQuery) {
"use strict"; "use strict";
@ -69,9 +69,9 @@ define([
}, },
fetchPosts: function() { fetchPosts: function() {
var that = this; var that = this;
WordpressComponent.getTransformedPosts(this.toJSON()).done(function(content) { CommunicationComponent.getTransformedPosts(this.toJSON()).done(function(content) {
console.log('ALC fetched', arguments);
that.get('_container').get('blocks').reset(content, {parse: true}); that.get('_container').get('blocks').reset(content, {parse: true});
that.trigger('postsChanged');
}).fail(function(error) { }).fail(function(error) {
console.log('ALC fetchPosts error', arguments); console.log('ALC fetchPosts error', arguments);
}); });
@ -100,6 +100,11 @@ define([
toolsRegion: '.mailpoet_tools', toolsRegion: '.mailpoet_tools',
postsRegion: '.mailpoet_automated_latest_content_block_posts', postsRegion: '.mailpoet_automated_latest_content_block_posts',
}, },
modelEvents: _.extend(
_.omit(base.BlockView.prototype.modelEvents, 'change'),
{
'postsChanged': 'render',
}),
events: _.extend(base.BlockView.prototype.events, { events: _.extend(base.BlockView.prototype.events, {
'click .mailpoet_automated_latest_content_block_overlay': 'showSettings', 'click .mailpoet_automated_latest_content_block_overlay': 'showSettings',
}), }),
@ -161,7 +166,7 @@ define([
var that = this; var that = this;
// Dynamically update available post types // Dynamically update available post types
WordpressComponent.getPostTypes().done(_.bind(this._updateContentTypes, this)); CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
this.$('.mailpoet_automated_latest_content_categories_and_tags').select2({ this.$('.mailpoet_automated_latest_content_categories_and_tags').select2({
multiple: true, multiple: true,
@ -174,10 +179,10 @@ define([
}, },
transport: function(options, success, failure) { transport: function(options, success, failure) {
var taxonomies, var taxonomies,
promise = WordpressComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) { promise = CommunicationComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
taxonomies = tax; taxonomies = tax;
// Fetch available terms based on the list of taxonomies already fetched // Fetch available terms based on the list of taxonomies already fetched
var promise = WordpressComponent.getTerms({ var promise = CommunicationComponent.getTerms({
search: options.data.term, search: options.data.term,
taxonomies: _.keys(taxonomies) taxonomies: _.keys(taxonomies)
}).then(function(terms) { }).then(function(terms) {

View File

@ -117,12 +117,9 @@ define([
* Defines drop behavior of BlockView instance * Defines drop behavior of BlockView instance
*/ */
getDropFunc: function() { getDropFunc: function() {
var that = this;
return function() { return function() {
var newModel = that.model.clone(); return this.model.clone();
//that.model.destroy(); }.bind(this);
return newModel;
};
}, },
showBlock: function() { showBlock: function() {
if (this._isFirstRender) { if (this._isFirstRender) {
@ -131,25 +128,37 @@ define([
} }
}, },
deleteBlock: function() { deleteBlock: function() {
this.transitionOut().done(function() { this.transitionOut().then(function() {
this.model.destroy(); this.model.destroy();
}.bind(this)); }.bind(this));
}, },
transitionIn: function() { transitionIn: function() {
return this._transition('mailpoet_block_transition_in'); return this._transition('slideDown', 'fadeIn', 'easeOut');
}, },
transitionOut: function() { transitionOut: function() {
return this._transition('mailpoet_block_transition_out'); return this._transition('slideUp', 'fadeOut', 'easeIn');
}, },
_transition: function(className) { _transition: function(slideDirection, fadeDirection, easing) {
var that = this, var promise = jQuery.Deferred();
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('mailpoet_block_transition_out');
promise.resolve();
});
return promise; return promise;
}, },
}); });
@ -207,16 +216,14 @@ define([
Module.BlockSettingsView = Marionette.LayoutView.extend({ Module.BlockSettingsView = Marionette.LayoutView.extend({
className: 'mailpoet_editor_settings', className: 'mailpoet_editor_settings',
initialize: function() { initialize: function() {
var that = this;
MailPoet.Modal.panel({ MailPoet.Modal.panel({
element: this.$el, element: this.$el,
template: '', template: '',
position: 'right', position: 'right',
width: App.getConfig().get('sidepanelWidth'), width: App.getConfig().get('sidepanelWidth'),
onCancel: function() { onCancel: function() {
that.destroy(); this.destroy();
}, }.bind(this),
}); });
}, },
close: function(event) { close: function(event) {

View File

@ -232,12 +232,9 @@ define([
_.extend(this, this._buildRegions(this.regions)); _.extend(this, this._buildRegions(this.regions));
}, },
getDropFunc: function() { getDropFunc: function() {
var that = this;
return function() { return function() {
var newModel = that.model.clone(); return this.model.clone();
that.model.destroy(); }.bind(this);
return newModel;
};
}, },
showBlock: function() { showBlock: function() {
if (this._isFirstRender) { if (this._isFirstRender) {
@ -251,20 +248,32 @@ define([
}.bind(this)); }.bind(this));
}, },
transitionIn: function() { transitionIn: function() {
return this._transition('mailpoet_block_transition_in'); return this._transition('slideDown', 'fadeIn', 'easeIn');
}, },
transitionOut: function() { transitionOut: function() {
return this._transition('mailpoet_block_transition_out'); return this._transition('slideUp', 'fadeOut', 'easeOut');
}, },
_transition: function(className) { _transition: function(slideDirection, fadeDirection, easing) {
var that = this, var promise = jQuery.Deferred();
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('mailpoet_block_transition_out');
promise.resolve();
});
return promise; return promise;
}, },
}); });

View File

@ -57,11 +57,9 @@ define([
this.listenTo(this.model, 'change:styles.block.padding', this.changePadding); this.listenTo(this.model, 'change:styles.block.padding', this.changePadding);
}, },
templateHelpers: function() { templateHelpers: function() {
return { return _.extend({
model: this.model.toJSON(),
viewCid: this.cid,
totalHeight: parseInt(this.model.get('styles.block.padding'), 10)*2 + parseInt(this.model.get('styles.block.borderWidth')) + 'px', totalHeight: parseInt(this.model.get('styles.block.padding'), 10)*2 + parseInt(this.model.get('styles.block.borderWidth')) + 'px',
}; }, base.BlockView.prototype.templateHelpers.apply(this));
}, },
onRender: function() { onRender: function() {
this.toolsView = new Module.DividerBlockToolsView({ model: this.model }); this.toolsView = new Module.DividerBlockToolsView({ model: this.model });

View File

@ -39,9 +39,9 @@ define([
Module.FooterBlockView = base.BlockView.extend({ Module.FooterBlockView = base.BlockView.extend({
className: "mailpoet_block mailpoet_footer_block mailpoet_droppable_block", className: "mailpoet_block mailpoet_footer_block mailpoet_droppable_block",
getTemplate: function() { return templates.footerBlock; }, getTemplate: function() { return templates.footerBlock; },
modelEvents: { 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', '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),
onDragSubstituteBy: function() { return Module.FooterWidgetView; }, onDragSubstituteBy: function() { return Module.FooterWidgetView; },
onRender: function() { onRender: function() {
this.toolsView = new Module.FooterBlockToolsView({ model: this.model }); this.toolsView = new Module.FooterBlockToolsView({ model: this.model });

View File

@ -39,9 +39,9 @@ define([
Module.HeaderBlockView = base.BlockView.extend({ Module.HeaderBlockView = base.BlockView.extend({
className: "mailpoet_block mailpoet_header_block mailpoet_droppable_block", className: "mailpoet_block mailpoet_header_block mailpoet_droppable_block",
getTemplate: function() { return templates.headerBlock; }, getTemplate: function() { return templates.headerBlock; },
modelEvents: { 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', '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),
onDragSubstituteBy: function() { return Module.HeaderWidgetView; }, onDragSubstituteBy: function() { return Module.HeaderWidgetView; },
onRender: function() { onRender: function() {
this.toolsView = new Module.HeaderBlockToolsView({ model: this.model }); this.toolsView = new Module.HeaderBlockToolsView({ model: this.model });

View File

@ -37,11 +37,9 @@ define([
getTemplate: function() { return templates.imageBlock; }, getTemplate: function() { return templates.imageBlock; },
onDragSubstituteBy: function() { return Module.ImageWidgetView; }, onDragSubstituteBy: function() { return Module.ImageWidgetView; },
templateHelpers: function() { templateHelpers: function() {
return { return _.extend({
model: this.model.toJSON(),
viewCid: this.cid,
imageMissingSrc: App.getConfig().get('urls.imageMissing'), imageMissingSrc: App.getConfig().get('urls.imageMissing'),
}; }, base.BlockView.prototype.templateHelpers.apply(this));
}, },
onRender: function() { onRender: function() {
this.toolsView = new Module.ImageBlockToolsView({ model: this.model }); this.toolsView = new Module.ImageBlockToolsView({ model: this.model });

View File

@ -18,12 +18,12 @@ define([
'jquery', 'jquery',
'mailpoet', 'mailpoet',
'newsletter_editor/App', 'newsletter_editor/App',
'newsletter_editor/components/wordpress', 'newsletter_editor/components/communication',
'newsletter_editor/blocks/base', 'newsletter_editor/blocks/base',
'newsletter_editor/blocks/button', 'newsletter_editor/blocks/button',
'newsletter_editor/blocks/divider', 'newsletter_editor/blocks/divider',
'select2' 'select2'
], function(Backbone, Marionette, Radio, _, jQuery, MailPoet, App, WordpressComponent, BaseBlock, ButtonBlock, DividerBlock) { ], function(Backbone, Marionette, Radio, _, jQuery, MailPoet, App, CommunicationComponent, BaseBlock, ButtonBlock, DividerBlock) {
"use strict"; "use strict";
@ -96,8 +96,7 @@ define([
}, },
fetchAvailablePosts: function() { fetchAvailablePosts: function() {
var that = this; var that = this;
WordpressComponent.getPosts(this.toJSON()).done(function(posts) { CommunicationComponent.getPosts(this.toJSON()).done(function(posts) {
console.log('Posts fetched', arguments);
that.get('_availablePosts').reset(posts); that.get('_availablePosts').reset(posts);
that.get('_selectedPosts').reset(); // Empty out the collection that.get('_selectedPosts').reset(); // Empty out the collection
that.trigger('change:_availablePosts'); that.trigger('change:_availablePosts');
@ -116,8 +115,7 @@ define([
return; return;
} }
WordpressComponent.getTransformedPosts(data).done(function(posts) { CommunicationComponent.getTransformedPosts(data).done(function(posts) {
console.log('Transformed posts fetched', arguments);
that.get('_transformedPosts').get('blocks').reset(posts, {parse: true}); that.get('_transformedPosts').get('blocks').reset(posts, {parse: true});
}).fail(function() { }).fail(function() {
console.log('Posts _refreshTransformedPosts error', arguments); console.log('Posts _refreshTransformedPosts error', arguments);
@ -133,8 +131,7 @@ define([
if (data.posts.length === 0) return; if (data.posts.length === 0) return;
WordpressComponent.getTransformedPosts(data).done(function(posts) { CommunicationComponent.getTransformedPosts(data).done(function(posts) {
console.log('Available posts fetched', arguments);
collection.add(posts, { at: index }); collection.add(posts, { at: index });
}).fail(function() { }).fail(function() {
console.log('Posts fetchPosts error', arguments); console.log('Posts fetchPosts error', arguments);
@ -145,13 +142,14 @@ define([
Module.PostsBlockView = base.BlockView.extend({ Module.PostsBlockView = base.BlockView.extend({
className: "mailpoet_block mailpoet_posts_block mailpoet_droppable_block", className: "mailpoet_block mailpoet_posts_block mailpoet_droppable_block",
getTemplate: function() { return templates.postsBlock; }, getTemplate: function() { return templates.postsBlock; },
modelEvents: {}, modelEvents: {}, // Forcefully disable all events
regions: _.extend({ regions: _.extend({
postsRegion: '.mailpoet_posts_block_posts', postsRegion: '.mailpoet_posts_block_posts',
}, base.BlockView.prototype.regions), }, base.BlockView.prototype.regions),
onDragSubstituteBy: function() { return Module.PostsWidgetView; }, onDragSubstituteBy: function() { return Module.PostsWidgetView; },
initialize: function() { initialize: function() {
base.BlockView.prototype.initialize.apply(this, arguments); base.BlockView.prototype.initialize.apply(this, arguments);
this.toolsView = new Module.PostsBlockToolsView({ model: this.model }); this.toolsView = new Module.PostsBlockToolsView({ model: this.model });
this.model.reply('blockView', this.notifyAboutSelf, this); this.model.reply('blockView', this.notifyAboutSelf, this);
}, },
@ -271,7 +269,7 @@ define([
var that = this; var that = this;
// Dynamically update available post types // Dynamically update available post types
WordpressComponent.getPostTypes().done(_.bind(this._updateContentTypes, this)); CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
this.$('.mailpoet_posts_categories_and_tags').select2({ this.$('.mailpoet_posts_categories_and_tags').select2({
multiple: true, multiple: true,
@ -284,10 +282,10 @@ define([
}, },
transport: function(options, success, failure) { transport: function(options, success, failure) {
var taxonomies, var taxonomies,
promise = WordpressComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) { promise = CommunicationComponent.getTaxonomies(that.model.get('contentType')).then(function(tax) {
taxonomies = tax; taxonomies = tax;
// Fetch available terms based on the list of taxonomies already fetched // Fetch available terms based on the list of taxonomies already fetched
var promise = WordpressComponent.getTerms({ var promise = CommunicationComponent.getTerms({
search: options.data.term, search: options.data.term,
taxonomies: _.keys(taxonomies) taxonomies: _.keys(taxonomies)
}).then(function(terms) { }).then(function(terms) {

View File

@ -175,12 +175,9 @@ define([
_event.stopPropagation(); _event.stopPropagation();
}, },
getDropFunc: function() { getDropFunc: function() {
var that = this;
return function() { return function() {
var newModel = that.model.clone(); return this.model.clone();
//that.model.destroy(); }.bind(this);
return newModel;
};
}, },
_buildRegions: function(regions) { _buildRegions: function(regions) {
var that = this; var that = this;
@ -211,20 +208,32 @@ define([
}.bind(this)); }.bind(this));
}, },
transitionIn: function() { transitionIn: function() {
return this._transition('mailpoet_block_transition_in'); return this._transition('slideDown', 'fadeIn', 'easeIn');
}, },
transitionOut: function() { transitionOut: function() {
return this._transition('mailpoet_block_transition_out'); return this._transition('slideUp', 'fadeOut', 'easeOut');
}, },
_transition: function(className) { _transition: function(slideDirection, fadeDirection, easing) {
var that = this, var promise = jQuery.Deferred();
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('mailpoet_block_transition_out');
promise.resolve();
});
return promise; return promise;
}, },
}); });

View File

@ -26,6 +26,8 @@ define([
getTemplate: function() { return templates.textBlock; }, getTemplate: function() { return templates.textBlock; },
modelEvents: _.omit(base.BlockView.prototype.modelEvents, 'change'), // Prevent rerendering on model change due to text editor redrawing modelEvents: _.omit(base.BlockView.prototype.modelEvents, 'change'), // Prevent rerendering on model change due to text editor redrawing
initialize: function(options) { initialize: function(options) {
base.BlockView.prototype.initialize.apply(this, arguments);
this.renderOptions = _.defaults(options.renderOptions || {}, { this.renderOptions = _.defaults(options.renderOptions || {}, {
disableTextEditor: false, disableTextEditor: false,
}); });

View File

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

View File

@ -44,10 +44,10 @@ define([
}; };
Module.getBody = function() { Module.getBody = function() {
return JSON.stringify({ return {
content: App._contentContainer.toJSON(), content: App._contentContainer.toJSON(),
globalStyles: App.getGlobalStyles().toJSON(), globalStyles: App.getGlobalStyles().toJSON(),
}); };
}; };
Module.toJSON = function() { Module.toJSON = function() {
@ -73,8 +73,7 @@ define([
}); });
App.on('start', function(options) { App.on('start', function(options) {
// TODO: Other newsletter information will be needed as well. var body = options.newsletter.body;
var body = JSON.parse(options.newsletter.body);
App._contentContainer = new (App.getBlockTypeModel('container'))(body.content, {parse: true}); App._contentContainer = new (App.getBlockTypeModel('container'))(body.content, {parse: true});
App._contentContainerView = new (App.getBlockTypeView('container'))({ App._contentContainerView = new (App.getBlockTypeView('container'))({
model: App._contentContainer, model: App._contentContainer,

View File

@ -1,5 +1,6 @@
define([ define([
'newsletter_editor/App', 'newsletter_editor/App',
'newsletter_editor/components/communication',
'mailpoet', 'mailpoet',
'notice', 'notice',
'backbone', 'backbone',
@ -8,7 +9,18 @@ define([
'blob', 'blob',
'filesaver', 'filesaver',
'html2canvas' 'html2canvas'
], function(App, MailPoet, Notice, Backbone, Marionette, jQuery, Blob, FileSaver, html2canvas) { ], function(
App,
CommunicationComponent,
MailPoet,
Notice,
Backbone,
Marionette,
jQuery,
Blob,
FileSaver,
html2canvas
) {
"use strict"; "use strict";
@ -17,26 +29,33 @@ define([
// Save editor contents to server // Save editor contents to server
Module.save = function() { Module.save = function() {
App.getChannel().trigger('beforeEditorSave');
var json = App.toJSON(); 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 // save newsletter
MailPoet.Ajax.post({ CommunicationComponent.saveNewsletter(json).done(function(response) {
endpoint: 'newsletters',
action: 'save',
data: json,
}).done(function(response) {
if(response.success !== undefined && response.success === true) { if(response.success !== undefined && response.success === true) {
// TODO: Handle translations // TODO: Handle translations
//MailPoet.Notice.success("<?php _e('Newsletter has been saved.'); ?>"); //MailPoet.Notice.success("<?php _e('Newsletter has been saved.'); ?>");
} else if(response.error !== undefined) { } else if(response.error !== undefined) {
if(response.error.length === 0) { if(response.error.length === 0) {
// TODO: Handle translations // 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 { } else {
$(response.error).each(function(i, error) { $(response.error).each(function(i, error) {
MailPoet.Notice.error(error); MailPoet.Notice.error(error, { scroll: true });
}); });
} }
} }
@ -58,7 +77,7 @@ define([
promise.then(function(thumbnail) { promise.then(function(thumbnail) {
var data = _.extend(options || {}, { var data = _.extend(options || {}, {
thumbnail: thumbnail.toDataURL('image/jpeg'), thumbnail: thumbnail.toDataURL('image/jpeg'),
body: App.getBody(), body: JSON.stringify(App.getBody()),
}); });
return MailPoet.Ajax.post({ return MailPoet.Ajax.post({
@ -146,20 +165,52 @@ define([
}, },
saveAsTemplate: function() { saveAsTemplate: function() {
var templateName = this.$('.mailpoet_save_as_template_name').val(), var templateName = this.$('.mailpoet_save_as_template_name').val(),
templateDescription = this.$('.mailpoet_save_as_template_description').val(); templateDescription = this.$('.mailpoet_save_as_template_description').val(),
that = this;
console.log('Saving template with ', templateName, templateDescription); if (templateName === '') {
Module.saveTemplate({ MailPoet.Notice.error(
name: templateName, App.getConfig().get('translations.templateNameMissing'),
description: templateDescription, {
}).done(function() { positionAfter: that.$el,
console.log('Template saved', arguments); scroll: true,
}).fail(function() { }
// TODO: Handle error messages );
console.log('Template save failed', arguments); } else if (templateDescription === '') {
}); MailPoet.Notice.error(
App.getConfig().get('translations.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'),
{
positionAfter: that.$el,
scroll: true,
}
);
}).fail(function() {
console.log('Template save failed', arguments);
MailPoet.Notice.error(
App.getConfig().get('translations.templateSaveFailed'),
{
positionAfter: that.$el,
scroll: true,
}
);
});
this.hideOptionContents();
}
this.hideOptionContents();
}, },
toggleExportTemplate: function() { toggleExportTemplate: function() {
this.$('.mailpoet_export_template_container').toggleClass('mailpoet_hidden'); this.$('.mailpoet_export_template_container').toggleClass('mailpoet_hidden');
@ -170,12 +221,25 @@ define([
}, },
exportTemplate: function() { exportTemplate: function() {
var templateName = this.$('.mailpoet_export_template_name').val(), var templateName = this.$('.mailpoet_export_template_name').val(),
templateDescription = this.$('.mailpoet_export_template_description').val(); templateDescription = this.$('.mailpoet_export_template_description').val(),
that = this;
if (templateName === '') { if (templateName === '') {
MailPoet.Notice.error(App.getConfig().get('translations.templateNameMissing')); MailPoet.Notice.error(
App.getConfig().get('translations.templateNameMissing'),
{
positionAfter: that.$el,
scroll: true,
}
);
} else if (templateDescription === '') { } else if (templateDescription === '') {
MailPoet.Notice.error(App.getConfig().get('translations.templateDescriptionMissing')); MailPoet.Notice.error(
App.getConfig().get('translations.templateDescriptionMissing'),
{
positionAfter: that.$el,
scroll: true,
}
);
} else { } else {
console.log('Exporting template with ', templateName, templateDescription); console.log('Exporting template with ', templateName, templateDescription);
Module.exportTemplate({ Module.exportTemplate({

View File

@ -1,12 +1,13 @@
define([ define([
'newsletter_editor/App', 'newsletter_editor/App',
'newsletter_editor/components/communication',
'backbone', 'backbone',
'backbone.marionette', 'backbone.marionette',
'backbone.supermodel', 'backbone.supermodel',
'underscore', 'underscore',
'jquery', 'jquery',
'sticky-kit' 'sticky-kit'
], function(App, Backbone, Marionette, SuperModel, _, jQuery, StickyKit) { ], function(App, CommunicationComponent, Backbone, Marionette, SuperModel, _, jQuery, StickyKit) {
"use strict"; "use strict";
@ -50,8 +51,33 @@ define([
}, },
events: { events: {
'click .mailpoet_sidebar_region h3, .mailpoet_sidebar_region .handlediv': function(event) { 'click .mailpoet_sidebar_region h3, .mailpoet_sidebar_region .handlediv': function(event) {
this.$el.find('.mailpoet_sidebar_region').addClass('closed'); var $openRegion = this.$el.find('.mailpoet_sidebar_region:not(.closed)'),
this.$el.find(event.target).parent().parent().removeClass('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) { initialize: function(options) {
@ -90,7 +116,6 @@ define([
}); });
}, },
onDomRefresh: function() { onDomRefresh: function() {
var that = this;
this.$el.parent().stick_in_parent({ this.$el.parent().stick_in_parent({
offset_top: 32, offset_top: 32,
}); });
@ -169,10 +194,8 @@ define([
}, },
initialize: function(options) { initialize: function(options) {
this.availableStyles = options.availableStyles; this.availableStyles = options.availableStyles;
var that = this;
}, },
onRender: function() { onRender: function() {
var that = this;
this.$('.mailpoet_color').spectrum({ this.$('.mailpoet_color').spectrum({
clickoutFiresChange: true, clickoutFiresChange: true,
showInput: true, showInput: true,
@ -219,26 +242,29 @@ define([
console.log('trying to send a preview'); console.log('trying to send a preview');
// get form data // get form data
var data = { var data = {
from_name: this.$('#mailpoet_preview_from_name').val(), subscriber: this.$('#mailpoet_preview_to_email').val(),
from_email: this.$('#mailpoet_preview_from_email').val(), id: App.getNewsletter().get('id'),
to_email: this.$('#mailpoet_preview_to_email').val(),
newsletter: App.newsletterId,
}; };
// send test email // send test email
MailPoet.Modal.loading(true); MailPoet.Modal.loading(true);
// TODO: Migrate logic to new AJAX format CommunicationComponent.previewNewsletter(data).done(function(response) {
Wordpress.previewNewsletter(data).done(function(response) { if(response.result !== undefined && response.result === true) {
if(response.success !== undefined && response.success === true) { MailPoet.Notice.success(App.getConfig().get('translations.newsletterPreviewSent'), { scroll: true });
MailPoet.Notice.success(App.getConfig().get('translations.testEmailSent')); } else {
} else if(response.error !== undefined) { if (_.isArray(response.errors)) {
if(response.error.length === 0) { response.errors.map(function(error) {
MailPoet.Notice.error(App.getConfig().get('translations.unknownErrorOccurred')); MailPoet.Notice.error(error, { scroll: true });
} else {
$(response.error).each(function(i, error) {
MailPoet.Notice.error(error);
}); });
} else {
MailPoet.Notice.error(
App.getConfig().get('translations.newsletterPreviewFailedToSend'),
{
scroll: true,
static: true,
}
);
} }
} }
MailPoet.Modal.loading(false); MailPoet.Modal.loading(false);

View File

@ -17,7 +17,7 @@ define([
}, },
h1: { h1: {
fontColor: '#111111', fontColor: '#111111',
fontFamily: 'Arial Black', fontFamily: 'Arial',
fontSize: '40px' fontSize: '40px'
}, },
h2: { h2: {
@ -72,7 +72,7 @@ define([
App.getAvailableStyles = Module.getAvailableStyles; App.getAvailableStyles = Module.getAvailableStyles;
var body = JSON.parse(options.newsletter.body); var body = options.newsletter.body;
this.setGlobalStyles(body.globalStyles); this.setGlobalStyles(body.globalStyles);
}); });

View File

@ -16,7 +16,7 @@ define(
Breadcrumb Breadcrumb
) { ) {
var settings = window.mailpoet_settings || {}; var settings = window.mailpoet_settings || {};
var fields = [ var fields = [
{ {
@ -24,14 +24,17 @@ define(
label: 'Subject line', label: 'Subject line',
tip: "Be creative! It's the first thing your subscribers see."+ tip: "Be creative! It's the first thing your subscribers see."+
"Tempt them to open your email.", "Tempt them to open your email.",
type: 'text' type: 'text',
validation: {
'data-parsley-required': true
}
}, },
{ {
name: 'segments', name: 'segments',
label: 'Lists', label: 'Segments',
tip: "The subscriber list that will be used for this campaign.", tip: "The subscriber segment that will be used for this campaign.",
type: 'selection', type: 'selection',
placeholder: "Select a list", placeholder: "Select a segment",
id: "mailpoet_segments", id: "mailpoet_segments",
endpoint: "segments", endpoint: "segments",
multiple: true, multiple: true,
@ -111,12 +114,19 @@ define(
action: 'add', action: 'add',
data: { data: {
newsletter_id: this.props.params.id, 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) { }).done(function(response) {
if(response.result === true) { if(response.result === true) {
this.history.pushState(null, '/'); this.history.pushState(null, '/');
MailPoet.Notice.success( MailPoet.Notice.success(
'The newsletter is being sent...' 'The newsletter is being sent...'
); );

View File

@ -18,6 +18,12 @@ define(
var ImportTemplate = React.createClass({ var ImportTemplate = React.createClass({
saveTemplate: function(template) { saveTemplate: function(template) {
// Stringify to enable transmission of primitive non-string value types
if (!_.isUndefined(template.body)) {
template.body = JSON.stringify(template.body);
}
MailPoet.Ajax.post({ MailPoet.Ajax.post({
endpoint: 'newsletterTemplates', endpoint: 'newsletterTemplates',
action: 'save', action: 'save',
@ -111,12 +117,19 @@ define(
}.bind(this)); }.bind(this));
}, },
handleSelectTemplate: function(template) { 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({ MailPoet.Ajax.post({
endpoint: 'newsletters', endpoint: 'newsletters',
action: 'save', action: 'save',
data: { data: {
id: this.props.params.id, id: this.props.params.id,
body: template.body body: body
} }
}).done(function(response) { }).done(function(response) {
if(response.result === true) { if(response.result === true) {
@ -153,7 +166,7 @@ define(
handleShowTemplate: function(template) { handleShowTemplate: function(template) {
MailPoet.Modal.popup({ MailPoet.Modal.popup({
title: template.name, title: template.name,
template: '<img src="{{ thumbnail }}" />', template: '<div class="mailpoet_boxes_preview" style="background-color: {{ body.globalStyles.body.backgroundColor }}"><img src="{{ thumbnail }}" /></div>',
data: template, data: template,
}); });
}, },

View File

@ -26,6 +26,7 @@ define(
action: 'create', action: 'create',
data: { data: {
type: type, type: type,
subject: 'Draft newsletter',
} }
}).done(function(response) { }).done(function(response) {
if(response.id !== undefined) { if(response.id !== undefined) {

View File

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

View File

@ -42,11 +42,7 @@ define(
return ( return (
<div> <div>
<h2 className="title"> <h2 className="title">
Segment <a Segment
href="javascript:;"
className="add-new-h2"
onClick={ this.history.goBack }
>Back to list</a>
</h2> </h2>
<Form <Form

View File

@ -51,6 +51,28 @@ define(
} }
]; ];
var custom_fields = window.mailpoet_custom_fields || [];
custom_fields.map(custom_field => {
if(custom_field.type === 'input') {
custom_field.type = 'text';
}
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 = { var messages = {
onUpdate: function() { onUpdate: function() {
MailPoet.Notice.success('Subscriber successfully updated!'); MailPoet.Notice.success('Subscriber successfully updated!');
@ -70,11 +92,7 @@ define(
return ( return (
<div> <div>
<h2 className="title"> <h2 className="title">
Subscriber <a Subscriber
href="javascript:;"
className="add-new-h2"
onClick={ this.history.goBack }
>Back to list</a>
</h2> </h2>
<Form <Form

View File

@ -62,7 +62,7 @@ define(
if (_.contains(fieldsToExclude, selectedOptionId)) { if (_.contains(fieldsToExclude, selectedOptionId)) {
selectEvent.preventDefault(); selectEvent.preventDefault();
if (selectedOptionId === 'deselect') { if (selectedOptionId === 'deselect') {
jQuery(selectElement).select2('val', ''); jQuery(selectElement).val('').trigger('change');
} else { } else {
var allOptions = []; var allOptions = [];
_.each(container.find('option'), function (field) { _.each(container.find('option'), function (field) {
@ -70,7 +70,7 @@ define(
allOptions.push(field.value); allOptions.push(field.value);
} }
}); });
jQuery(selectElement).select2('val', allOptions); jQuery(selectElement).val(allOptions).trigger('change');
} }
jQuery(selectElement).select2('close'); jQuery(selectElement).select2('close');
} }
@ -138,11 +138,11 @@ define(
endpoint: 'ImportExport', endpoint: 'ImportExport',
action: 'processExport', action: 'processExport',
data: JSON.stringify({ data: JSON.stringify({
'exportConfirmedOption': exportData.exportConfirmedOption, 'export_confirmed_option': exportData.exportConfirmedOption,
'exportFormatOption': jQuery(':radio[name="option_format"]:checked').val(), 'export_format_option': jQuery(':radio[name="option_format"]:checked').val(),
'groupBySegmentOption': (groupBySegmentOptionElement.is(":visible")) ? groupBySegmentOptionElement.prop('checked') : false, 'group_by_segment_option': (groupBySegmentOptionElement.is(":visible")) ? groupBySegmentOptionElement.prop('checked') : false,
'segments': (exportData.segments) ? segmentsContainerElement.val() : false, 'segments': (exportData.segments) ? segmentsContainerElement.val() : false,
'subscriberFields': subscriberFieldsContainerElement.val() 'subscriber_fields': subscriberFieldsContainerElement.val()
}) })
}) })
.done(function (response) { .done(function (response) {

File diff suppressed because it is too large Load Diff

39
build
View File

@ -1,39 +0,0 @@
#!/bin/sh
# Remove previous build.
rm wysija-newsletters.zip;
# Create temp dir.
mkdir wysija-newsletters;
# Production assets.
npm install;
./do compile:all;
# Production libraries.
./composer.phar install --no-dev;
# Copy release folders.
cp -Rf lang wysija-newsletters;
cp -RfL assets wysija-newsletters;
cp -Rf lib wysija-newsletters;
cp -Rf vendor wysija-newsletters;
cp -Rf views wysija-newsletters;
rm -Rf wysija-newsletters/assets/css/src;
rm -Rf wysija-newsletters/assets/js/src;
# Copy release files.
cp LICENSE wysija-newsletters;
cp index.php wysija-newsletters;
cp mailpoet.php wysija-newsletters;
cp readme.txt wysija-newsletters;
cp uninstall.php wysija-newsletters;
# Zip final release.
zip -r wysija-newsletters.zip wysija-newsletters;
# Remove temp dir.
rm -rf wysija-newsletters;
# Reinstall dev dependencies.
./composer.phar install;

42
build.sh Executable file
View File

@ -0,0 +1,42 @@
#!/bin/sh
plugin_name='mailpoet'
# Remove previous build.
rm $plugin_name.zip
# Create temp dir.
mkdir $plugin_name
# Production assets.
rm -rf node_modules
npm install
./do compile:all
# Production libraries.
./composer.phar install --no-dev
# Copy release folders.
cp -Rf lang $plugin_name
cp -RfL assets $plugin_name
cp -Rf lib $plugin_name
cp -Rf vendor $plugin_name
cp -Rf views $plugin_name
rm -Rf $plugin_name/assets/css/src
rm -Rf $plugin_name/assets/js/src
# Copy release files.
cp LICENSE $plugin_name
cp index.php $plugin_name
cp $plugin_name.php $plugin_name
cp readme.txt $plugin_name
cp uninstall.php $plugin_name
# Zip final release.
zip -r $plugin_name.zip $plugin_name
# Remove temp dir.
rm -rf $plugin_name
# Reinstall dev dependencies.
./composer.phar install

608
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -23,4 +23,9 @@ class Activator {
$populator = new Populator(); $populator = new Populator();
$populator->up(); $populator->up();
} }
function deactivate() {
$migrator = new Migrator();
$migrator->down();
}
} }

View File

@ -10,7 +10,8 @@ class Analytics {
} }
function init() { function init() {
add_action('admin_enqueue_scripts', array($this, 'setupAdminDependencies')); // review: this creates a fatal error when mailpoet tables are dropped.
//add_action('admin_enqueue_scripts', array($this, 'setupAdminDependencies'));
} }
function setupAdminDependencies() { function setupAdminDependencies() {

View File

@ -6,15 +6,14 @@ if(!defined('ABSPATH')) exit;
class Env { class Env {
static $version; static $version;
static $plugin_name; static $plugin_name;
static $plugin_url;
static $plugin_path; static $plugin_path;
static $file; static $file;
static $path; static $path;
static $views_path; static $views_path;
static $assets_path; static $assets_path;
static $assets_url; static $assets_url;
static $temp_name;
static $temp_path; static $temp_path;
static $temp_URL;
static $languages_path; static $languages_path;
static $lib_path; static $lib_path;
static $plugin_prefix; static $plugin_prefix;
@ -34,12 +33,11 @@ class Env {
self::$file = $file; self::$file = $file;
self::$path = dirname(self::$file); self::$path = dirname(self::$file);
self::$plugin_name = 'mailpoet'; self::$plugin_name = 'mailpoet';
self::$plugin_url = plugin_dir_url(__FILE__);
self::$views_path = self::$path . '/views'; self::$views_path = self::$path . '/views';
self::$assets_path = self::$path . '/assets'; self::$assets_path = self::$path . '/assets';
self::$assets_url = plugins_url('/assets', $file); self::$assets_url = plugins_url('/assets', $file);
self::$temp_name = 'temp'; self::$temp_path = wp_upload_dir()['path'];
self::$temp_path = self::$path . '/' . self::$temp_name; self::$temp_URL = wp_upload_dir()['url'];
self::$languages_path = self::$path . '/lang'; self::$languages_path = self::$path . '/lang';
self::$lib_path = self::$path . '/lib'; self::$lib_path = self::$path . '/lib';
self::$plugin_prefix = 'mailpoet_'; self::$plugin_prefix = 'mailpoet_';

View File

@ -1,11 +1,61 @@
<?php <?php
namespace MailPoet\Config; namespace MailPoet\Config;
use \MailPoet\Models\Setting;
class Hooks { class Hooks {
function __construct() { function __construct() {
} }
function init() { function init() {
// Subscribe in comments
if((bool)Setting::getValue('subscribe.on_comment.enabled')) {
add_action(
'comment_form_after_fields',
'\MailPoet\Subscription\Comment::extendForm'
);
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((bool)Setting::getValue('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
);
}
}
// WP Users synchronization // WP Users synchronization
add_action( add_action(
'user_register', 'user_register',

View File

@ -4,6 +4,8 @@ namespace MailPoet\Config;
use MailPoet\Models; use MailPoet\Models;
use MailPoet\Cron\Supervisor; use MailPoet\Cron\Supervisor;
use MailPoet\Router; use MailPoet\Router;
use MailPoet\Models\Setting;
use MailPoet\Settings\Pages;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
@ -28,7 +30,9 @@ class Initializer {
$this->setupChangelog(); $this->setupChangelog();
$this->setupPublicAPI(); $this->setupPublicAPI();
$this->runQueueSupervisor(); $this->runQueueSupervisor();
$this->setupShortcodes();
$this->setupHooks(); $this->setupHooks();
$this->setupPages();
$this->setupImages(); $this->setupImages();
} }
@ -73,7 +77,7 @@ class Initializer {
define('MP_SUBSCRIBER_CUSTOM_FIELD_TABLE', $subscriber_custom_field); define('MP_SUBSCRIBER_CUSTOM_FIELD_TABLE', $subscriber_custom_field);
define('MP_NEWSLETTER_OPTION_FIELDS_TABLE', $newsletter_option_fields); define('MP_NEWSLETTER_OPTION_FIELDS_TABLE', $newsletter_option_fields);
define('MP_NEWSLETTER_OPTION_TABLE', $newsletter_option); 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); define('MP_NEWSLETTER_STATISTICS_TABLE', $newsletter_statistics);
} }
@ -125,6 +129,15 @@ class Initializer {
$changelog->init(); $changelog->init();
} }
function setupPages() {
$pages = new Pages();
$pages->init();
}
function setupShortcodes() {
$shortcodes = new Shortcodes();
$shortcodes->init();
}
function setupHooks() { function setupHooks() {
$hooks = new Hooks(); $hooks = new Hooks();
$hooks->init(); $hooks->init();
@ -136,13 +149,15 @@ class Initializer {
} }
function runQueueSupervisor() { function runQueueSupervisor() {
if(php_sapi_name() === 'cli') return;
try { try {
$supervisor = new Supervisor(); $supervisor = new Supervisor();
$supervisor->checkDaemon(); $supervisor->checkDaemon();
} catch (\Exception $e) {} } catch (\Exception $e) {
}
} }
function setupImages() { function setupImages() {
add_image_size('mailpoet_newsletter_max', 1320); add_image_size('mailpoet_newsletter_max', 1320);
} }
} }

View File

@ -190,6 +190,8 @@ class Menu {
} }
function welcome() { function welcome() {
if((bool)(defined('DOING_AJAX') && DOING_AJAX)) return;
global $wp; global $wp;
$current_url = home_url(add_query_arg($wp->query_string, $wp->request)); $current_url = home_url(add_query_arg($wp->query_string, $wp->request));
$redirect_url = $redirect_url =
@ -240,6 +242,7 @@ class Menu {
function settings() { function settings() {
$settings = Setting::getAll(); $settings = Setting::getAll();
$flags = $this->_getFlags();
// dkim: check if public/private keys have been generated // dkim: check if public/private keys have been generated
if( if(
@ -258,10 +261,9 @@ class Menu {
$data = array( $data = array(
'settings' => $settings, 'settings' => $settings,
'segments' => Segment::getPublished() 'segments' => Segment::getPublic()->findArray(),
->findArray(),
'pages' => Pages::getAll(), 'pages' => Pages::getAll(),
'flags' => $this->_getFlags(), 'flags' => $flags,
'charsets' => Charsets::getAll(), 'charsets' => Charsets::getAll(),
'current_user' => wp_get_current_user(), 'current_user' => wp_get_current_user(),
'permissions' => Permissions::getAll(), 'permissions' => Permissions::getAll(),
@ -294,7 +296,7 @@ class Menu {
} else { } else {
// check if users can register // check if users can register
$flags['registration_enabled'] = $flags['registration_enabled'] =
(bool) get_option('users_can_register', false); (bool)get_option('users_can_register', false);
} }
return $flags; return $flags;
@ -305,6 +307,23 @@ class Menu {
$data['segments'] = Segment::findArray(); $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); echo $this->renderer->render('subscribers/subscribers.html', $data);
} }
@ -374,15 +393,14 @@ class Menu {
->findArray(), ->findArray(),
'styles' => FormRenderer::getStyles($form), 'styles' => FormRenderer::getStyles($form),
'date_types' => Block\Date::getDateTypes(), 'date_types' => Block\Date::getDateTypes(),
'date_formats' => Block\Date::getDateFormats() 'date_formats' => Block\Date::getDateFormats(),
'month_names' => Block\Date::getMonthNames()
); );
echo $this->renderer->render('form/editor.html', $data); echo $this->renderer->render('form/editor.html', $data);
} }
function cron() { function cron() {
$daemon = new \MailPoet\Cron\BootStrapMenu(); echo $this->renderer->render('cron.html');
$data['daemon'] = json_encode($daemon->bootstrap());
echo $this->renderer->render('cron.html', $data);
} }
} }

View File

@ -41,7 +41,7 @@ class Migrator {
function down() { function down() {
global $wpdb; global $wpdb;
$drop_table = function($model) { $drop_table = function($model) use($wpdb) {
$table = $this->prefix . $model; $table = $this->prefix . $model;
$wpdb->query("DROP TABLE {$table}"); $wpdb->query("DROP TABLE {$table}");
}; };
@ -105,7 +105,7 @@ class Migrator {
'description varchar(250) NOT NULL,', 'description varchar(250) NOT NULL,',
'body LONGTEXT,', 'body LONGTEXT,',
'thumbnail LONGTEXT,', 'thumbnail LONGTEXT,',
'readonly TINYINT(1) DEFAULT 0', 'readonly TINYINT(1) DEFAULT 0,',
'created_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,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)' 'PRIMARY KEY (id)'

View File

@ -3,8 +3,12 @@ namespace MailPoet\Config;
use MailPoet\Config\PopulatorData\Templates\FranksRoastHouseTemplate; use MailPoet\Config\PopulatorData\Templates\FranksRoastHouseTemplate;
use MailPoet\Config\PopulatorData\Templates\BlankTemplate; use MailPoet\Config\PopulatorData\Templates\BlankTemplate;
use MailPoet\Config\PopulatorData\Templates\WelcomeTemplate;
use MailPoet\Config\PopulatorData\Templates\PostNotificationsBlankTemplate;
use \MailPoet\Models\Segment; use \MailPoet\Models\Segment;
use \MailPoet\Segments\WP; use \MailPoet\Segments\WP;
use \MailPoet\Models\Setting;
use \MailPoet\Settings\Pages;
if (!defined('ABSPATH')) exit; if (!defined('ABSPATH')) exit;
@ -44,6 +48,57 @@ class Populator {
array_map(array($this, 'populate'), $this->models); array_map(array($this, 'populate'), $this->models);
$this->createDefaultSegments(); $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();
Setting::setValue('subscription.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 from name & address
Setting::setValue('sender', array(
'name' => $user_name,
'address' => $current_user->user_email
));
// enable signup confirmation by default
Setting::setValue('signup_confirmation.enabled', true);
} }
private function createDefaultSegments() { private function createDefaultSegments() {
@ -127,6 +182,8 @@ class Populator {
return array( return array(
(new FranksRoastHouseTemplate(Env::$assets_url))->get(), (new FranksRoastHouseTemplate(Env::$assets_url))->get(),
(new BlankTemplate(Env::$assets_url))->get(), (new BlankTemplate(Env::$assets_url))->get(),
(new WelcomeTemplate(Env::$assets_url))->get(),
(new PostNotificationsBlankTemplate(Env::$assets_url))->get(),
); );
} }

View File

@ -7,8 +7,6 @@ class BlankTemplate {
function __construct($assets_url) { function __construct($assets_url) {
$this->assets_url = $assets_url; $this->assets_url = $assets_url;
$this->template_image_url = $this->assets_url . '/img/sample_template';
$this->social_icon_url = $this->assets_url . '/img/newsletter_editor/social-icons';
} }
function get() { function get() {
@ -181,7 +179,7 @@ class BlankTemplate {
), ),
"h1" => array( "h1" => array(
"fontColor" => "#111111", "fontColor" => "#111111",
"fontFamily" => "Arial Black", "fontFamily" => "Arial",
"fontSize" => "24px" "fontSize" => "24px"
), ),
"h2" => array( "h2" => array(

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7,15 +7,20 @@ use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class PublicAPI { class PublicAPI {
public $api;
public $section;
public $action;
public $request_payload;
function __construct() { function __construct() {
# http://example.com/?mailpoet-api&section=&action=&payload= # http://example.com/?mailpoet-api&section=&action=&request_payload=
$this->api = isset($_GET['mailpoet-api']) ? true : false; $this->api = isset($_GET['mailpoet-api']) ? true : false;
$this->section = isset($_GET['section']) ? $_GET['section'] : false; $this->section = isset($_GET['section']) ? $_GET['section'] : false;
$this->action = isset($_GET['action']) ? $this->action = isset($_GET['action']) ?
Helpers::underscoreToCamelCase($_GET['action']) : Helpers::underscoreToCamelCase($_GET['action']) :
false; false;
$this->payload = isset($_GET['payload']) ? $this->request_payload = isset($_GET['request_payload']) ?
json_decode(urldecode($_GET['payload']), true) : json_decode(urldecode($_GET['request_payload']), true) :
false; false;
} }
@ -25,8 +30,11 @@ class PublicAPI {
} }
function queue() { function queue() {
$queue = new Daemon($this->payload); try {
$this->_checkAndCallMethod($queue, $this->action); $queue = new Daemon($this->request_payload);
$this->_checkAndCallMethod($queue, $this->action);
} catch(\Exception $e) {
}
} }
private function _checkAndCallMethod($class, $method, $terminate = false) { private function _checkAndCallMethod($class, $method, $terminate = false) {

121
lib/Config/Shortcodes.php Normal file
View File

@ -0,0 +1,121 @@
<?php
namespace MailPoet\Config;
use \MailPoet\Models\Newsletter;
use \MailPoet\Models\Subscriber;
use \MailPoet\Models\SubscriberSegment;
class Shortcodes {
function __construct() {
}
function init() {
// 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()) {
// IMPORTANT: fixes conflict with MagicMember
remove_shortcode('user_list');
if(isset($params['id']) && (int)$params['id'] > 0) {
$form_widget = new \MailPoet\Form\Widget();
return $form_widget->widget(array(
'form' => (int)$params['id'],
'form_type' => 'shortcode'
));
}
}
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,6 +1,5 @@
<?php <?php
namespace MailPoet\Config; namespace MailPoet\Config;
use \MailPoet\Models\Subscriber;
use \MailPoet\Util\Security; use \MailPoet\Util\Security;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
@ -22,18 +21,6 @@ class Widget {
function registerWidget() { function registerWidget() {
register_widget('\MailPoet\Form\Widget'); 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() { function setupDependencies() {

View File

@ -10,86 +10,99 @@ require_once(ABSPATH . 'wp-includes/pluggable.php');
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class Daemon { class Daemon {
function __construct($payload = array()) { public $daemon;
public $request_payload;
public $refreshed_token;
public $timer;
function __construct($request_payload = array()) {
set_time_limit(0); set_time_limit(0);
ignore_user_abort(); ignore_user_abort();
list ($this->daemon, $this->daemonData) = $this->getDaemon(); $this->daemon = $this->getDaemon();
$this->refreshedToken = $this->refreshToken(); $this->refreshed_token = $this->refreshToken();
$this->payload = $payload; $this->request_payload = $request_payload;
$this->timer = microtime(true); $this->timer = microtime(true);
} }
function start() { function start() {
if(!isset($this->payload['session'])) { if(!isset($this->request_payload['session'])) {
$this->abortWithError('missing session ID'); $this->abortWithError(__('Missing session ID.'));
} }
$this->manageSession('start'); $this->manageSession('start');
$daemon = $this->daemon; $daemon = $this->daemon;
$daemonData = $this->daemonData;
if(!$daemon) { if(!$daemon) {
$daemon = Setting::create(); $this->saveDaemon(
$daemon->name = 'cron_daemon'; array(
$daemonData = array( 'status' => 'starting',
'status' => null, 'counter' => 0
'counter' => 0 )
); );
$daemon->value = json_encode($daemonData);
$daemon->save();
} }
if($daemonData['status'] !== 'started') { if($daemon['status'] === 'started') {
$_SESSION['cron_daemon'] = 'started';
$daemonData['status'] = 'started';
$daemonData['token'] = $this->refreshedToken;
$_SESSION['cron_daemon'] = array('result' => true);
$this->manageSession('end');
$daemon->value = json_encode($daemonData);
$daemon->save();
$this->callSelf();
} else {
$_SESSION['cron_daemon'] = array( $_SESSION['cron_daemon'] = array(
'result' => false, 'result' => false,
'error' => 'already started' 'errors' => array(__('Daemon already running.'))
); );
} }
if($daemon['status'] === 'starting') {
$_SESSION['cron_daemon'] = 'started';
$_SESSION['cron_daemon'] = array('result' => true);
$this->manageSession('end');
$daemon['status'] = 'started';
$daemon['token'] = $this->refreshed_token;
$this->saveDaemon($daemon);
$this->callSelf();
}
$this->manageSession('end'); $this->manageSession('end');
} }
function run() { function run() {
if(!$this->daemon || $this->daemonData['status'] !== 'started') { $allowed_statuses = array(
$this->abortWithError('not running'); 'stopping',
'starting',
'started'
);
if(!$this->daemon || !in_array($this->daemon['status'], $allowed_statuses)) {
$this->abortWithError(__('Invalid daemon status.'));
} }
if(!isset($this->payload['token']) || if(!isset($this->request_payload['token']) ||
$this->payload['token'] !== $this->daemonData['token'] $this->request_payload['token'] !== $this->daemon['token']
) { ) {
$this->abortWithError('invalid token'); $this->abortWithError('Invalid token.');
} }
try { try {
$sendingQueue = new SendingQueue($this->timer); $sending_queue = new SendingQueue($this->timer);
$sendingQueue->process(); $sending_queue->process();
} catch(Exception $e) { } catch(Exception $e) {
} }
$elapsed_time = microtime(true) - $this->timer;
$elapsedTime = microtime(true) - $this->timer; if($elapsed_time < 30) {
if($elapsedTime < 30) { sleep(30 - $elapsed_time);
sleep(30 - $elapsedTime);
} }
// after each execution, read daemon in case its status was modified
$daemon = $this->getDaemon();
// after each execution, read daemon in case it's status was modified if($daemon['status'] === 'stopping') $daemon['status'] = 'stopped';
list($daemon, $daemonData) = $this->getDaemon(); if($daemon['status'] === 'starting') $daemon['status'] = 'started';
$daemonData['counter']++;
$daemonData['token'] = $this->refreshedToken; $daemon['token'] = $this->refreshed_token;
$daemon->value = json_encode($daemonData); $daemon['counter']++;
$daemon->save();
if($daemonData['status'] === 'strated') $this->callSelf(); $this->saveDaemon($daemon);
if($daemon['status'] === 'started') $this->callSelf();
} }
function getDaemon() { function getDaemon() {
$daemon = Setting::where('name', 'cron_daemon') return Setting::getValue('cron_daemon');
->findOne(); }
return array(
($daemon) ? $daemon : null, function saveDaemon($daemon_data) {
($daemon) ? json_decode($daemon->value, true) : null $daemon_data['updated_at'] = time();
return Setting::setValue(
'cron_daemon',
$daemon_data
); );
} }
@ -99,24 +112,23 @@ class Daemon {
function manageSession($action) { function manageSession($action) {
switch($action) { switch($action) {
case 'start': case 'start':
if(session_id()) { if(session_id()) {
session_write_close();
}
session_id($this->payload['session']);
session_start();
break;
case 'end':
session_write_close(); session_write_close();
break; }
session_id($this->request_payload['session']);
session_start();
break;
case 'end':
session_write_close();
break;
} }
} }
function callSelf() { function callSelf() {
$payload = json_encode(array('token' => $this->refreshedToken)); $payload = json_encode(array('token' => $this->refreshed_token));
Supervisor::getRemoteUrl( Supervisor::accessRemoteUrl(
'/?mailpoet-api&section=queue&action=run&payload=' . urlencode($payload) '/?mailpoet-api&section=queue&action=run&request_payload=' . urlencode($payload)
); );
exit; exit;
} }
@ -125,7 +137,7 @@ class Daemon {
wp_send_json( wp_send_json(
array( array(
'result' => false, 'result' => false,
'error' => $error 'errors' => array($error)
)); ));
exit; exit;
} }

View File

@ -1,38 +1,48 @@
<?php <?php
namespace MailPoet\Cron; namespace MailPoet\Cron;
use Carbon\Carbon;
use MailPoet\Config\Env; use MailPoet\Config\Env;
use MailPoet\Models\Setting; use MailPoet\Models\Setting;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class Supervisor { class Supervisor {
function __construct($forceStart = false) { public $daemon;
$this->forceStart = $forceStart;
function __construct($force_start = false) {
$this->force_start = $force_start;
if(!Env::isPluginActivated()) { if(!Env::isPluginActivated()) {
throw new \Exception('Database has not been configured.'); throw new \Exception(__('MailPoet is not activated.'));
} }
list ($this->daemon, $this->daemonData) = $this->getDaemon(); $this->daemon = $this->getDaemon();
} }
function checkDaemon() { function checkDaemon() {
if(!$this->daemon) { if(!$this->daemon) {
return $this->startDaemon(); return $this->startDaemon();
} }
if(!$this->forceStart && $this->daemonData['status'] === 'stopped') { if(
return; !$this->force_start
&& isset($this->daemon['status'])
&& in_array($this->daemon['status'], array('stopped', 'stopping'))
) {
return $this->daemon['status'];
} }
$currentTime = Carbon::now('UTC');
$lastUpdateTime = Carbon::createFromFormat( $elapsed_time = time() - (int)$this->daemon['updated_at'];
'Y-m-d H:i:s',
$this->daemon->updated_at, 'UTC' if($elapsed_time < 40) {
); if(!$this->force_start) {
$timeSinceLastStart = $currentTime->diffInSeconds($lastUpdateTime); return;
if($timeSinceLastStart < 40) return; }
$this->daemonData['status'] = null; if($this->daemon['status'] === 'stopping' ||
$this->daemon->value = json_encode($this->daemonData); $this->daemon['status'] === 'starting'
$this->daemon->save(); ) {
return $this->daemon['status'];
}
}
$this->daemon['status'] = 'starting';
$this->saveDaemon($this->daemon);
return $this->startDaemon(); return $this->startDaemon();
} }
@ -41,9 +51,10 @@ class Supervisor {
$sessionId = session_id(); $sessionId = session_id();
session_write_close(); session_write_close();
$_SESSION['cron_daemon'] = null; $_SESSION['cron_daemon'] = null;
$payload = json_encode(array('session' => $sessionId)); $requestPayload = json_encode(array('session' => $sessionId));
self::getRemoteUrl( self::accessRemoteUrl(
'/?mailpoet-api&section=queue&action=start&payload=' . urlencode($payload) '/?mailpoet-api&section=queue&action=start&request_payload=' .
urlencode($requestPayload)
); );
session_start(); session_start();
$daemonStatus = $_SESSION['cron_daemon']; $daemonStatus = $_SESSION['cron_daemon'];
@ -53,19 +64,20 @@ class Supervisor {
} }
function getDaemon() { function getDaemon() {
$daemon = Setting::where('name', 'cron_daemon') return Setting::getValue('cron_daemon');
->findOne(); }
$daemonData = ($daemon) ? json_decode($daemon->value, true) : false;
return array( function saveDaemon($daemon_data) {
$daemon, return Setting::setValue(
$daemonData 'cron_daemon',
$daemon_data
); );
} }
static function getRemoteUrl($url) { static function accessRemoteUrl($url) {
$args = array( $args = array(
'timeout' => 1, 'timeout' => 1,
'user-agent' => 'MailPoet (www.mailpoet.com)' 'user-agent' => 'MailPoet (www.mailpoet.com) Cron'
); );
wp_remote_get( wp_remote_get(
self::getSiteUrl() . $url, self::getSiteUrl() . $url,
@ -74,11 +86,19 @@ class Supervisor {
} }
static function getSiteUrl() { static function getSiteUrl() {
if(preg_match('!:\d+/!', site_url())) return site_url(); // additional check for some sites running on a virtual machine or behind
preg_match('!http://(?P<host>.*?):(?P<port>\d+)!', site_url(), $server); // 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); $fp = @fsockopen($server['host'], $server['port'], $errno, $errstr, 1);
return ($fp) ? if($fp) return site_url();
site_url() : // connect to the URL without port
preg_replace('/(?=:\d+):\d+/', '$1', site_url()); $fp = @fsockopen($server['host'], $server['port'], $errno, $errstr, 1);
if($fp) return preg_replace('!(?=:\d+):\d+!', '$1', site_url());
// throw an error if all connections fail
throw new \Exception(__('Site URL is unreachable.'));
} }
} }

View File

@ -1,114 +1,134 @@
<?php <?php
namespace MailPoet\Cron\Workers; namespace MailPoet\Cron\Workers;
use MailPoet\Mailer\Mailer;
use MailPoet\Models\Newsletter; use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterStatistics; use MailPoet\Models\NewsletterStatistics;
use MailPoet\Models\Subscriber; use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\Renderer\Renderer; use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Router\Mailer;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class SendingQueue { class SendingQueue {
public $timer;
function __construct($timer = false) { function __construct($timer = false) {
$this->timer = ($timer) ? $timer : microtime(true); $this->timer = ($timer) ? $timer : microtime(true);
} }
function process() { function process() {
$queues = // TODO: implement mailer sending frequency limits
\MailPoet\Models\SendingQueue::orderByDesc('priority') foreach($this->getQueues() as $queue) {
->whereNull('deleted_at') $newsletter = Newsletter::findOne($queue->newsletter_id)
->whereNull('status') ->asArray();
->findResultSet();
foreach($queues as $queue) {
$newsletter = Newsletter::findOne($queue->newsletter_id);
if(!$newsletter) { if(!$newsletter) {
continue; continue;
}; };
$newsletter = $newsletter->asArray(); $mailer = $this->configureMailerForNewsletter($newsletter);
$mailer = new Mailer($httpRequest = false); $newsletter = $this->renderNewsletter($newsletter);
if(!empty($newsletter['sender_address']) &&
!empty($newsletter['sender_name'])
) {
$mailer->fromName = $newsletter['sender_name'];
$mailer->fromEmail = $newsletter['sender_address'];
$mailer->fromNameEmail = sprintf(
'%s <%s>',
$mailer->fromName,
$mailer->fromEmail
);
}
if(!empty($newsletter['reply_to_address']) &&
!empty($newsletter['reply_to_name'])
) {
$mailer->replyToName = $newsletter['reply_to_name'];
$mailer->replyToEmail = $newsletter['reply_to_address'];
$mailer->replyToNameEmail = sprintf(
'%s <%s>',
$mailer->replyToName,
$mailer->replyToEmail
);
}
$mailer->mailer = $mailer->buildMailer();
$renderer = new Renderer(json_decode($newsletter['body'], true));
$newsletter = array(
'subject' => $newsletter['subject'],
'id' => $newsletter['id'],
'body' => array(
'html' => $renderer->renderAll(),
'text' => ''
// TODO: add text body
)
);
$subscribers = json_decode($queue->subscribers, true); $subscribers = json_decode($queue->subscribers, true);
$subscribersToProcess = $subscribers['to_process']; $subscribers_to_process = $subscribers['to_process'];
if(!isset($subscribers['failed'])) $subscribers['failed'] = array();
if(!isset($subscribers['processed'])) $subscribers['processed'] = array(); if(!isset($subscribers['processed'])) $subscribers['processed'] = array();
foreach(array_chunk($subscribersToProcess, 200) as $subscriberIds) { if(!isset($subscribers['failed'])) $subscribers['failed'] = array();
$dbSubscribers = Subscriber::whereIn('id', $subscriberIds) foreach(array_chunk($subscribers_to_process, 200) as $subscriber_ids) {
$db_subscribers = Subscriber::whereIn('id', $subscriber_ids)
->findArray(); ->findArray();
foreach($dbSubscribers as $i => $dbSubscriber) { foreach($db_subscribers as $db_subscriber) {
$this->checkExecutionTimer(); $this->checkExecutionTimer();
// TODO: replace shortcodes in the newsletter $result = $this->sendNewsletter(
$result = $mailer->mailer->send( $mailer,
$newsletter, $this->processNewsletter($newsletter),
$mailer->transformSubscriber($dbSubscriber) $db_subscriber);
);
$newsletterStatistics = NewsletterStatistics::create();
$newsletterStatistics->subscriber_id = $dbSubscriber['id'];
$newsletterStatistics->newsletter_id = $newsletter['id'];
$newsletterStatistics->queue_id = $queue->id;
$newsletterStatistics->save();
if($result) { if($result) {
$subscribers['processed'][] = $dbSubscriber['id']; $this->updateStatistics($newsletter['id'], $db_subscriber['id'], $queue->id);
$subscribers['processed'][] = $db_subscriber['id'];
} else { } else {
$subscribers['failed'][] = $dbSubscriber['id']; $subscribers['failed'][] = $db_subscriber['id'];
} }
$subscribers['to_process'] = array_values( $this->updateQueue($queue, $subscribers);
array_diff(
$subscribers['to_process'],
array_merge($subscribers['processed'], $subscribers['failed'])
)
);
$queue->count_processed =
count($subscribers['processed']) + count($subscribers['failed']);
$queue->count_to_process = count($subscribers['to_process']);
$queue->count_failed = count($subscribers['failed']);
$queue->count_total =
$queue->count_processed + $queue->count_to_process;
if(!$queue->count_to_process) {
$queue->processed_at = date('Y-m-d H:i:s');
$queue->status = 'completed';
}
$queue->subscribers = json_encode($subscribers);
$queue->save();
} }
} }
} }
} }
function processNewsletter($newsletter) {
// TODO: replace shortcodes, etc..
return $newsletter;
}
function sendNewsletter($mailer, $newsletter, $subscriber) {
return $mailer->mailer_instance->send(
$newsletter,
$mailer->transformSubscriber($subscriber)
);
}
function updateStatistics($newsletter_id, $subscriber_id, $queue_id) {
$newsletter_statistic = NewsletterStatistics::create();
$newsletter_statistic->subscriber_id = $newsletter_id;
$newsletter_statistic->newsletter_id = $subscriber_id;
$newsletter_statistic->queue_id = $queue_id;
$newsletter_statistic->save();
}
function updateQueue($queue, $subscribers) {
$subscribers['to_process'] = array_values(
array_diff(
$subscribers['to_process'],
array_merge($subscribers['processed'], $subscribers['failed'])
)
);
$queue->count_processed =
count($subscribers['processed']) + count($subscribers['failed']);
$queue->count_to_process = count($subscribers['to_process']);
$queue->count_failed = count($subscribers['failed']);
$queue->count_total =
$queue->count_processed + $queue->count_to_process;
if(!$queue->count_to_process) {
$queue->processed_at = date('Y-m-d H:i:s');
$queue->status = 'completed';
}
$queue->subscribers = json_encode($subscribers);
$queue->save();
}
function configureMailerForNewsletter($newsletter) {
if(!empty($newsletter['sender_address']) && !empty($newsletter['sender_name'])) {
$sender = array(
'name' => $newsletter['sender_name'],
'address' => $newsletter['sender_address']
);
} else {
$sender = false;
}
if(!empty($newsletter['reply_to_address']) && !empty($newsletter['reply_to_name'])) {
$reply_to = array(
'name' => $newsletter['reply_to_name'],
'address' => $newsletter['reply_to_address']
);
} else {
$reply_to = false;
}
$mailer = new Mailer($method = false, $sender, $reply_to);
return $mailer;
}
function checkExecutionTimer() { function checkExecutionTimer() {
$elapsedTime = microtime(true) - $this->timer; $elapsed_time = microtime(true) - $this->timer;
if($elapsedTime >= 28) throw new \Exception('Maximum execution time reached.'); if($elapsed_time >= 30) throw new \Exception('Maximum execution time reached.');
}
function getQueues() {
return \MailPoet\Models\SendingQueue::orderByDesc('priority')
->whereNull('deleted_at')
->whereNull('status')
->findResultSet();
}
function renderNewsletter($newsletter) {
$renderer = new Renderer(json_decode($newsletter['body'], true));
// TODO: update once text rendering is implemented/enderer returns an array
$newsletter['body'] = array('html' => $renderer->render(), 'text' => '');
return $newsletter;
} }
} }

View File

@ -13,7 +13,9 @@ abstract class Base {
if($block['id'] === 'segments') { if($block['id'] === 'segments') {
$rules['required'] = true; $rules['required'] = true;
$rules['mincheck'] = 1; $rules['mincheck'] = 1;
$rules['error-message'] = __('You need to select a list'); $rules['group'] = $block['id'];
$rules['errors-container'] = '.mailpoet_error_'.$block['id'];
$rules['required-message'] = __('You need to select a list');
} }
if(!empty($block['params']['required'])) { if(!empty($block['params']['required'])) {
@ -29,7 +31,13 @@ abstract class Base {
} }
} }
$validation = ''; if(in_array($block['type'], array('radio', 'checkbox'))) {
$rules['group'] = 'custom_field_'.$block['id'];
$rules['errors-container'] = '.mailpoet_error_'.$block['id'];
$rules['required-message'] = __('You need to select at least one option.');
}
$validation = array();
if(!empty($rules)) { if(!empty($rules)) {
$rules = array_unique($rules); $rules = array_unique($rules);
@ -37,10 +45,10 @@ abstract class Base {
if(is_bool($value)) { if(is_bool($value)) {
$value = ($value) ? 'true' : 'false'; $value = ($value) ? 'true' : 'false';
} }
$validation .= 'data-parsley-'.$rule.'="'.$value.'"'; $validation[] = 'data-parsley-'.$rule.'="'.$value.'"';
} }
} }
return $validation; return join(' ', $validation);
} }
protected static function renderLabel($block) { protected static function renderLabel($block) {
@ -86,7 +94,11 @@ abstract class Base {
// return field name depending on block data // return field name depending on block data
protected static function getFieldName($block = array()) { protected static function getFieldName($block = array()) {
return $block['id']; if((int)$block['id'] > 0) {
return 'cf_'.$block['id'];
} else {
return $block['id'];
}
} }
protected static function getFieldLabel($block = array()) { protected static function getFieldLabel($block = array()) {
@ -98,6 +110,6 @@ abstract class Base {
protected static function getFieldValue($block = array()) { protected static function getFieldValue($block = array()) {
return (isset($block['params']['value']) return (isset($block['params']['value'])
&& strlen(trim($block['params']['value'])) > 0) && strlen(trim($block['params']['value'])) > 0)
? trim($block['params']['value']) : ''; ? esc_attr(trim($block['params']['value'])) : '';
} }
} }

View File

@ -9,15 +9,16 @@ class Checkbox extends Base {
$field_name = static::getFieldName($block); $field_name = static::getFieldName($block);
$field_validation = static::getInputValidation($block); $field_validation = static::getInputValidation($block);
// TODO: check if it still makes sense
// create hidden default value
// $html .= '<input type="hidden"name="'.$field_name.'" value="0" '.static::getInputValidation($block).'/>';
$html .= '<p class="mailpoet_paragraph">'; $html .= '<p class="mailpoet_paragraph">';
$html .= static::renderLabel($block); $html .= static::renderLabel($block);
foreach($block['params']['values'] as $option) { $options = (!empty($block['params']['values'])
? $block['params']['values']
: array()
);
foreach($options as $option) {
$html .= '<label class="mailpoet_checkbox_label">'; $html .= '<label class="mailpoet_checkbox_label">';
$html .= '<input type="checkbox" class="mailpoet_checkbox" '; $html .= '<input type="checkbox" class="mailpoet_checkbox" ';
@ -35,6 +36,8 @@ class Checkbox extends Base {
$html .= '</label>'; $html .= '</label>';
} }
$html .= '<span class="mailpoet_error_'.$block['id'].'"></span>';
$html .= '</p>'; $html .= '</p>';
return $html; return $html;

View File

@ -9,32 +9,35 @@ class Radio extends Base {
$field_name = static::getFieldName($block); $field_name = static::getFieldName($block);
$field_validation = static::getInputValidation($block); $field_validation = static::getInputValidation($block);
// TODO: check if it still makes sense
// create hidden default value
// $html .= '<input type="hidden"name="'.$field_name.'" value="0" '.static::getInputValidation($block).'/>';
$html .= '<p class="mailpoet_paragraph">'; $html .= '<p class="mailpoet_paragraph">';
$html .= static::renderLabel($block); $html .= static::renderLabel($block);
foreach($block['params']['values'] as $option) { $options = (!empty($block['params']['values'])
? $block['params']['values']
: array()
);
foreach($options as $option) {
$html .= '<label class="mailpoet_radio_label">'; $html .= '<label class="mailpoet_radio_label">';
$html .= '<input type="radio" class="mailpoet_radio" '; $html .= '<input type="radio" class="mailpoet_radio" ';
$html .= 'name="'.$field_name.'" '; $html .= 'name="'.$field_name.'" ';
$html .= 'value="'.$option['value'].'" '; $html .= 'value="'.esc_attr($option['value']).'" ';
$html .= (isset($option['is_checked']) && $option['is_checked']) $html .= (isset($option['is_checked']) && $option['is_checked'])
? 'checked="checked"' : ''; ? 'checked="checked"' : '';
$html .= $field_validation; $html .= $field_validation;
$html .= ' />&nbsp;'.$option['value']; $html .= ' />&nbsp;'.esc_attr($option['value']);
$html .= '</label>'; $html .= '</label>';
} }
$html .= '<span class="mailpoet_error_'.$block['id'].'"></span>';
$html .= '</p>'; $html .= '</p>';
return $html; return $html;

View File

@ -13,23 +13,27 @@ class Segment extends Base {
$html .= static::renderLabel($block); $html .= static::renderLabel($block);
if(!empty($block['params']['values'])) { $options = (!empty($block['params']['values'])
// display values ? $block['params']['values']
foreach($block['params']['values'] as $segment) { : array()
if(!isset($segment['id']) || !isset($segment['name'])) continue; );
$is_checked = (isset($segment['is_checked']) && $segment['is_checked']) ? 'checked="checked"' : ''; foreach($options as $option) {
if(!isset($option['id']) || !isset($option['name'])) continue;
$html .= '<label class="mailpoet_checkbox_label">'; $is_checked = (isset($option['is_checked']) && $option['is_checked']) ? 'checked="checked"' : '';
$html .= '<input type="checkbox" class="mailpoet_checkbox" ';
$html .= 'name="'.$field_name.'[]" '; $html .= '<label class="mailpoet_checkbox_label">';
$html .= 'value="'.$segment['id'].'" '.$is_checked.' '; $html .= '<input type="checkbox" class="mailpoet_checkbox" ';
$html .= $field_validation; $html .= 'name="'.$field_name.'[]" ';
$html .= ' />'.$segment['name']; $html .= 'value="'.$option['id'].'" '.$is_checked.' ';
$html .= '</label>'; $html .= $field_validation;
} $html .= ' />'.$option['name'];
$html .= '</label>';
} }
$html .= '<span class="mailpoet_error_'.$block['id'].'"></span>';
$html .= '</p>'; $html .= '</p>';
return $html; return $html;

View File

@ -6,11 +6,11 @@ class Submit extends Base {
static function render($block) { static function render($block) {
$html = ''; $html = '';
$html .= '<input class="mailpoet_submit" type="submit" '; $html .= '<p class="mailpoet_submit"><input type="submit" ';
$html .= 'value="'.static::getFieldLabel($block).'" '; $html .= 'value="'.static::getFieldLabel($block).'" ';
$html .= '/>'; $html .= '/></p>';
return $html; return $html;
} }

View File

@ -36,9 +36,9 @@ class Widget extends \WP_Widget {
return parent::__construct( return parent::__construct(
'mailpoet_form', 'mailpoet_form',
__("MailPoet Subscription Form"), __('MailPoet Form'),
array( array(
'title' => __("Newsletter subscription form"), 'description' => __('Add a newsletter subscription form.')
) )
); );
} }
@ -194,114 +194,4 @@ class Widget extends \WP_Widget {
} }
} }
} }
}
// mailpoet shortcodes
// form shortcode
add_shortcode('mailpoet_form', 'mailpoet_form_shortcode');
add_shortcode('wysija_form', 'mailpoet_form_shortcode');
function mailpoet_form_shortcode($params = array()) {
// IMPORTANT: this is to make sure MagicMember won't scan our form and find [user_list] as a code they should replace.
remove_shortcode('user_list');
if(isset($params['id']) && (int)$params['id'] > 0) {
$form_widget = new \MailPoet\Form\Widget();
return $form_widget->widget(array(
'form' => (int)$params['id'],
'form_type' => 'shortcode'
));
}
}
// set the content filter to replace the shortcode
if(isset($_GET['mailpoet_page']) && strlen(trim($_GET['mailpoet_page'])) > 0) {
switch($_GET['mailpoet_page']) {
case 'mailpoet_form_iframe':
$id = (isset($_GET['mailpoet_form']) && (int)$_GET['mailpoet_form'] > 0) ? (int)$_GET['mailpoet_form'] : null;
$form = Form::findOne($id);
if($form !== false) {
// render form
$output = Util\Export::get('html', $form->asArray());
// $output = do_shortcode($output);
print $output;
exit;
}
break;
default:
// add_filter('wp_title', 'mailpoet_meta_page_title'));
add_filter('the_title', 'mailpoet_page_title', 10, 2);
add_filter('the_content', 'mailpoet_page_content', 98, 1);
break;
}
}
function mailpoet_page_title($title = '', $id = null) {
// get signup confirmation page id
$signup_confirmation = Setting::getValue('signup_confirmation');
$page_id = $signup_confirmation['page'];
// check if we're on the signup confirmation page
if((int)$page_id === (int)$id) {
global $post;
// disable comments
$post->comment_status = 'close';
// disable password
$post->post_password = '';
$subscriber = null;
// get subscriber key from url
$subscriber_digest = (isset($_GET['mailpoet_key']) && strlen(trim($_GET['mailpoet_key'])) === 32) ? trim($_GET['mailpoet_key']) : null;
if($subscriber_digest !== null) {
// get subscriber
// TODO: change select() to selectOne() once it's implemented
$subscribers = $mailpoet->subscribers()->select(array(
'filter' => array(
'subscriber_digest' => $subscriber_digest
),
'limit' => 1
));
if(!empty($subscribers)) {
$subscriber = array_shift($subscribers);
}
}
// check if we have a subscriber record
if($subscriber === null) {
return __('Your confirmation link expired, please subscribe again.');
} else {
// we have a subscriber, let's check its state
switch($subscriber['subscriber_state']) {
case MailPoetSubscribers::STATE_UNCONFIRMED:
case MailPoetSubscribers::STATE_UNSUBSCRIBED:
// set subscriber state as confirmed
$mailpoet->subscribers()->update(array(
'subscriber' => $subscriber['subscriber'],
'subscriber_state' => MailPoetSubscribers::STATE_SUBSCRIBED,
'subscriber_confirmed_at' => time()
));
return __("You've subscribed");
break;
case MailPoetSubscribers::STATE_SUBSCRIBED:
return __("You've already subscribed");
break;
}
}
} else {
return $title;
}
}
function mailpoet_page_content($content = '') {
if(strpos($content, '[mailpoet_page]') !== FALSE) {
$content = str_replace('[mailpoet_page]', '', $content);
}
return $content;
} }

View File

@ -1,113 +0,0 @@
<?php
namespace MailPoet\Mailer\API;
if(!defined('ABSPATH')) exit;
class AmazonSES {
function __construct($region, $accessKey, $secretKey, $from) {
$this->awsAccessKey = $accessKey;
$this->awsSecret_key = $secretKey;
$this->awsRegion = $region;
$this->awsEndpoint = sprintf('email.%s.amazonaws.com', $region);
$this->awsSigningAlgorithm = 'AWS4-HMAC-SHA256';
$this->awsService = 'ses';
$this->awsTerminationString = 'aws4_request';
$this->hashAlgorithm = 'sha256';
$this->url = 'https://' . $this->awsEndpoint;
$this->from = $from;
$this->date = gmdate('Ymd\THis\Z');
$this->dateWithoutTime = gmdate('Ymd');
}
function send($newsletter, $subscriber) {
$result = wp_remote_post(
$this->url,
$this->request($newsletter, $subscriber)
);
return (
!is_wp_error($result) === true &&
wp_remote_retrieve_response_code($result) === 200
);
}
function getBody($newsletter, $subscriber) {
$body = array(
'Action' => 'SendEmail',
'Version' => '2010-12-01',
'Source' => $this->from,
'Destination.ToAddresses.member.1' => $subscriber,
'Message.Subject.Data' => $newsletter['subject'],
'ReturnPath' => $this->from
);
if(!empty($newsletter['body']['html'])) {
$body['Message.Body.Html.Data'] = $newsletter['body']['html'];
}
if(!empty($newsletter['body']['text'])) {
$body['Message.Body.Text.Data'] = $newsletter['body']['text'];
}
return $body;
}
function request($newsletter, $subscriber) {
$body = $this->getBody($newsletter, $subscriber);
return array(
'timeout' => 10,
'httpversion' => '1.1',
'method' => 'POST',
'headers' => array(
'Host' => $this->awsEndpoint,
'Authorization' => $this->signRequest($body),
'X-Amz-Date' => $this->date
),
'body' => urldecode(http_build_query($body))
);
}
function signRequest($body) {
$stringToSign = $this->createStringToSign(
$this->getCredentialScope(),
$this->getCanonicalRequest($body)
);
$signature = hash_hmac($this->hashAlgorithm, $stringToSign, $this->getSigningKey());
return sprintf(
'%s Credential=%s/%s, SignedHeaders=host;x-amz-date, Signature=%s',
$this->awsSigningAlgorithm,
$this->awsAccessKey,
$this->getCredentialScope(),
$signature);
}
function getCredentialScope() {
return sprintf('%s/%s/%s/%s', $this->dateWithoutTime, $this->awsRegion, $this->awsService, $this->awsTerminationString);
}
function getCanonicalRequest($body) {
return implode("\n", array(
'POST',
'/',
'',
'host:' . $this->awsEndpoint,
'x-amz-date:' . $this->date,
'',
'host;x-amz-date',
hash($this->hashAlgorithm, urldecode(http_build_query($body)))
));
}
function createStringToSign($credentialScope, $canonicalRequest) {
return implode("\n", array(
$this->awsSigningAlgorithm,
$this->date,
$credentialScope,
hash($this->hashAlgorithm, $canonicalRequest)
));
}
function getSigningKey() {
$dateKey = hash_hmac($this->hashAlgorithm, $this->dateWithoutTime, 'AWS4' . $this->awsSecret_key, true);
$regionKey = hash_hmac($this->hashAlgorithm, $this->awsRegion, $dateKey, true);
$serviceKey = hash_hmac($this->hashAlgorithm, $this->awsService, $regionKey, true);
return hash_hmac($this->hashAlgorithm, $this->awsTerminationString, $serviceKey, true);
}
}

146
lib/Mailer/Mailer.php Normal file
View File

@ -0,0 +1,146 @@
<?php
namespace MailPoet\Mailer;
use MailPoet\Models\Setting;
require_once(ABSPATH . 'wp-includes/pluggable.php');
if(!defined('ABSPATH')) exit;
class Mailer {
public $mailer;
public $sender;
public $reply_to;
public $mailer_instance;
function __construct($mailer = false, $sender = false, $reply_to = false) {
$this->mailer = $this->getMailer($mailer);
$this->sender = $this->getSender($sender);
$this->reply_to = $this->getReplyTo($reply_to);
$this->mailer_instance = $this->buildMailer();
}
function send($newsletter, $subscriber) {
$subscriber = $this->transformSubscriber($subscriber);
return $this->mailer_instance->send($newsletter, $subscriber);
}
function buildMailer() {
switch($this->mailer['method']) {
case 'AmazonSES':
$mailer_instance = new $this->mailer['class'](
$this->mailer['region'],
$this->mailer['access_key'],
$this->mailer['secret_key'],
$this->sender['from_name_email']
);
break;
case 'ElasticEmail':
$mailer_instance = new $this->mailer['class'](
$this->mailer['api_key'],
$this->sender['from_email'],
$this->sender['from_name']
);
break;
case 'MailGun':
$mailer_instance = new $this->mailer['class'](
$this->mailer['domain'],
$this->mailer['api_key'],
$this->sender['from_name_email']
);
break;
case 'MailPoet':
$mailer_instance = new $this->mailer['class'](
$this->mailer['mailpoet_api_key'],
$this->sender['from_email'],
$this->sender['from_name']
);
break;
case 'Mandrill':
$mailer_instance = new $this->mailer['class'](
$this->mailer['api_key'],
$this->sender['from_email'],
$this->sender['from_name']
);
break;
case 'SendGrid':
$mailer_instance = new $this->mailer['class'](
$this->mailer['api_key'],
$this->sender['from_email'],
$this->sender['from_name']
);
break;
case 'WPMail':
$mailer_instance = new $this->mailer['class'](
$this->sender['from_email'],
$this->sender['from_name']
);
break;
case 'SMTP':
$mailer_instance = new $this->mailer['class'](
$this->mailer['host'],
$this->mailer['port'],
$this->mailer['authentication'],
$this->mailer['login'],
$this->mailer['password'],
$this->mailer['encryption'],
$this->sender['from_email'],
$this->sender['from_name']
);
break;
default:
throw new \Exception(__('Mailing method does not exist.'));
break;
}
return $mailer_instance;
}
function getMailer($mailer = false) {
if(!$mailer) {
$mailer = Setting::getValue('mta', null);
if(!$mailer || !isset($mailer['method'])) throw new \Exception(__('Mailer is not configured.'));
}
$mailer['class'] = 'MailPoet\\Mailer\\Methods\\' . $mailer['method'];
return $mailer;
}
function getSender($sender = false) {
if(!$sender) {
$sender = Setting::getValue('sender', null);
if(!$sender) throw new \Exception(__('Sender name and email are not configured.'));
}
return array(
'from_name' => $sender['name'],
'from_email' => $sender['address'],
'from_name_email' => sprintf('%s <%s>', $sender['name'], $sender['address'])
);
}
function getReplyTo($reply_to = false) {
if(!$reply_to) {
$reply_to = Setting::getValue('reply_to', null);
if(!$reply_to) {
$reply_to = array(
'name' => $this->sender['from_name'],
'address' => $this->sender['from_email']
);
}
}
return array(
'reply_to_name' => $reply_to['name'],
'reply_to_email' => $reply_to['address'],
'reply_to_name_email' => sprintf('%s <%s>', $reply_to['name'], $reply_to['address'])
);
}
function transformSubscriber($subscriber) {
if(!is_array($subscriber)) return $subscriber;
if(isset($subscriber['address'])) $subscriber['email'] = $subscriber['address'];
$first_name = (isset($subscriber['first_name'])) ? $subscriber['first_name'] : '';
$last_name = (isset($subscriber['last_name'])) ? $subscriber['last_name'] : '';
if(!$first_name && !$last_name) return $subscriber['email'];
$subscriber = sprintf('%s %s <%s>', $first_name, $last_name, $subscriber['email']);
$subscriber = trim(preg_replace('!\s\s+!', ' ', $subscriber));
return $subscriber;
}
}

View File

@ -0,0 +1,155 @@
<?php
namespace MailPoet\Mailer\Methods;
if(!defined('ABSPATH')) exit;
class AmazonSES {
public $aws_access_key;
public $aws_secret_key;
public $aws_region;
public $aws_endpoint;
public $aws_signing_algorithm;
public $aws_service;
public $aws_termination_string;
public $hash_algorithm;
public $url;
public $from;
public $date;
public $date_without_time;
function __construct($region, $access_key, $secret_key, $from) {
$this->aws_access_key = $access_key;
$this->aws_secret_key = $secret_key;
$this->aws_region = $region;
$this->aws_endpoint = sprintf('email.%s.amazonaws.com', $region);
$this->aws_signing_algorithm = 'AWS4-HMAC-SHA256';
$this->aws_service = 'ses';
$this->aws_termination_string = 'aws4_request';
$this->hash_algorithm = 'sha256';
$this->url = 'https://' . $this->aws_endpoint;
$this->from = $from;
$this->date = gmdate('Ymd\THis\Z');
$this->date_without_time = gmdate('Ymd');
}
function send($newsletter, $subscriber) {
$result = wp_remote_post(
$this->url,
$this->request($newsletter, $subscriber)
);
return (
!is_wp_error($result) === true &&
wp_remote_retrieve_response_code($result) === 200
);
}
function getBody($newsletter, $subscriber) {
$body = array(
'Action' => 'SendEmail',
'Version' => '2010-12-01',
'Source' => $this->from,
'Destination.ToAddresses.member.1' => $subscriber,
'Message.Subject.Data' => $newsletter['subject'],
'ReturnPath' => $this->from
);
if(!empty($newsletter['body']['html'])) {
$body['Message.Body.Html.Data'] = $newsletter['body']['html'];
}
if(!empty($newsletter['body']['text'])) {
$body['Message.Body.Text.Data'] = $newsletter['body']['text'];
}
return $body;
}
function request($newsletter, $subscriber) {
$body = $this->getBody($newsletter, $subscriber);
return array(
'timeout' => 10,
'httpversion' => '1.1',
'method' => 'POST',
'headers' => array(
'Host' => $this->aws_endpoint,
'Authorization' => $this->signRequest($body),
'X-Amz-Date' => $this->date
),
'body' => urldecode(http_build_query($body))
);
}
function signRequest($body) {
$string_to_sign = $this->createStringToSign(
$this->getCredentialScope(),
$this->getCanonicalRequest($body)
);
$signature = hash_hmac(
$this->hash_algorithm,
$string_to_sign,
$this->getSigningKey()
);
return sprintf(
'%s Credential=%s/%s, SignedHeaders=host;x-amz-date, Signature=%s',
$this->aws_signing_algorithm,
$this->aws_access_key,
$this->getCredentialScope(),
$signature);
}
function getCredentialScope() {
return sprintf(
'%s/%s/%s/%s',
$this->date_without_time,
$this->aws_region,
$this->aws_service,
$this->aws_termination_string);
}
function getCanonicalRequest($body) {
return implode("\n", array(
'POST',
'/',
'',
'host:' . $this->aws_endpoint,
'x-amz-date:' . $this->date,
'',
'host;x-amz-date',
hash($this->hash_algorithm, urldecode(http_build_query($body)))
));
}
function createStringToSign($credential_scope, $canonical_request) {
return implode("\n", array(
$this->aws_signing_algorithm,
$this->date,
$credential_scope,
hash($this->hash_algorithm, $canonical_request)
));
}
function getSigningKey() {
$date_key = hash_hmac(
$this->hash_algorithm,
$this->date_without_time,
'AWS4' . $this->aws_secret_key,
true
);
$region_key = hash_hmac(
$this->hash_algorithm,
$this->aws_region,
$date_key,
true
);
$service_key = hash_hmac(
$this->hash_algorithm,
$this->aws_service,
$region_key,
true
);
return hash_hmac(
$this->hash_algorithm,
$this->aws_termination_string,
$service_key,
true
);
}
}

View File

@ -1,14 +1,18 @@
<?php <?php
namespace MailPoet\Mailer\API; namespace MailPoet\Mailer\Methods;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class ElasticEmail { class ElasticEmail {
function __construct($apiKey, $fromEmail, $fromName) { public $url = 'https://api.elasticemail.com/mailer/send';
$this->url = 'https://api.elasticemail.com/mailer/send'; public $api_key;
$this->apiKey = $apiKey; public $from_email;
$this->fromEmail = $fromEmail; public $from_name;
$this->fromName = $fromName;
function __construct($api_key, $from_email, $from_name) {
$this->api_key = $api_key;
$this->from_email = $from_email;
$this->from_name = $from_name;
} }
function send($newsletter, $subscriber) { function send($newsletter, $subscriber) {
@ -23,9 +27,9 @@ class ElasticEmail {
function getBody($newsletter, $subscriber) { function getBody($newsletter, $subscriber) {
$body = array( $body = array(
'api_key' => $this->apiKey, 'api_key' => $this->api_key,
'from' => $this->fromEmail, 'from' => $this->from_email,
'from_name' => $this->fromName, 'from_name' => $this->from_name,
'to' => $subscriber, 'to' => $subscriber,
'subject' => $newsletter['subject'] 'subject' => $newsletter['subject']
); );

View File

@ -1,12 +1,16 @@
<?php <?php
namespace MailPoet\Mailer\API; namespace MailPoet\Mailer\Methods;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class MailGun { class MailGun {
function __construct($domain, $apiKey, $from) { public $url;
public $api_key;
public $from;
function __construct($domain, $api_key, $from) {
$this->url = sprintf('https://api.mailgun.net/v3/%s/messages', $domain); $this->url = sprintf('https://api.mailgun.net/v3/%s/messages', $domain);
$this->apiKey = $apiKey; $this->api_key = $api_key;
$this->from = $from; $this->from = $from;
} }
@ -37,7 +41,7 @@ class MailGun {
} }
function auth() { function auth() {
return 'Basic ' . base64_encode('api:' . $this->apiKey); return 'Basic ' . base64_encode('api:' . $this->api_key);
} }
function request($newsletter, $subscriber) { function request($newsletter, $subscriber) {

View File

@ -1,16 +1,20 @@
<?php <?php
namespace MailPoet\Mailer; namespace MailPoet\Mailer\Methods;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class MailPoet { class MailPoet {
function __construct($apiKey, $fromEmail, $fromName) { public $url = 'https://bridge.mailpoet.com/api/messages';
$this->url = 'https://bridge.mailpoet.com/api/messages'; public $api_key;
$this->apiKey = $apiKey; public $from_email;
$this->fromEmail = $fromEmail; public $from_name;
$this->fromName = $fromName;
function __construct($api_key, $from_email, $from_name) {
$this->api_key = $api_key;
$this->from_email = $from_email;
$this->from_name = $from_name;
} }
function send($newsletter, $subscriber) { function send($newsletter, $subscriber) {
$result = wp_remote_post( $result = wp_remote_post(
$this->url, $this->url,
@ -21,20 +25,20 @@ class MailPoet {
wp_remote_retrieve_response_code($result) === 201 wp_remote_retrieve_response_code($result) === 201
); );
} }
function processSubscriber($subscriber) { function processSubscriber($subscriber) {
preg_match('!(?P<name>.*?)\s<(?P<email>.*?)>!', $subscriber, $subscriberData); preg_match('!(?P<name>.*?)\s<(?P<email>.*?)>!', $subscriber, $subscriber_data);
if(!isset($subscriberData['email'])) { if(!isset($subscriber_data['email'])) {
$subscriberData = array( $subscriber_data = array(
'email' => $subscriber, 'email' => $subscriber,
); );
} }
return array( return array(
'email' => $subscriberData['email'], 'email' => $subscriber_data['email'],
'name' => (isset($subscriberData['name'])) ? $subscriberData['name'] : '' 'name' => (isset($subscriber_data['name'])) ? $subscriber_data['name'] : ''
); );
} }
function getBody($newsletter, $subscriber) { function getBody($newsletter, $subscriber) {
$body = array( $body = array(
'to' => (array( 'to' => (array(
@ -42,8 +46,8 @@ class MailPoet {
'name' => $subscriber['name'] 'name' => $subscriber['name']
)), )),
'from' => (array( 'from' => (array(
'address' => $this->fromEmail, 'address' => $this->from_email,
'name' => $this->fromName 'name' => $this->from_name
)), )),
'subject' => $newsletter['subject'] 'subject' => $newsletter['subject']
); );
@ -55,11 +59,11 @@ class MailPoet {
} }
return $body; return $body;
} }
function auth() { function auth() {
return 'Basic ' . base64_encode('api:' . $this->apiKey); return 'Basic ' . base64_encode('api:' . $this->api_key);
} }
function request($newsletter, $subscriber) { function request($newsletter, $subscriber) {
$body = array($this->getBody($newsletter, $subscriber)); $body = array($this->getBody($newsletter, $subscriber));
return array( return array(
@ -70,7 +74,7 @@ class MailPoet {
'Content-Type' => 'application/json', 'Content-Type' => 'application/json',
'Authorization' => $this->auth() 'Authorization' => $this->auth()
), ),
'body' => json_encode($body) 'body' => $body
); );
} }
} }

View File

@ -1,14 +1,18 @@
<?php <?php
namespace MailPoet\Mailer\API; namespace MailPoet\Mailer\Methods;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class Mandrill { class Mandrill {
function __construct($apiKey, $fromEmail, $fromName) { public $url = 'https://mandrillapp.com/api/1.0/messages/send.json';
$this->url = 'https://mandrillapp.com/api/1.0/messages/send.json'; public $api_key;
$this->apiKey = $apiKey; public $from_email;
$this->fromName = $fromName; public $from_name;
$this->fromEmail = $fromEmail;
function __construct($api_key, $from_email, $from_name) {
$this->api_key = $api_key;
$this->from_name = $from_name;
$this->from_email = $from_email;
} }
function send($newsletter, $subscriber) { function send($newsletter, $subscriber) {
@ -24,24 +28,24 @@ class Mandrill {
} }
function processSubscriber($subscriber) { function processSubscriber($subscriber) {
preg_match('!(?P<name>.*?)\s<(?P<email>.*?)>!', $subscriber, $subscriberData); preg_match('!(?P<name>.*?)\s<(?P<email>.*?)>!', $subscriber, $subscriber_data);
if(!isset($subscriberData['email'])) { if(!isset($subscriber_data['email'])) {
$subscriberData = array( $subscriber_data = array(
'email' => $subscriber, 'email' => $subscriber,
); );
} }
return array( return array(
'email' => $subscriberData['email'], 'email' => $subscriber_data['email'],
'name' => (isset($subscriberData['name'])) ? $subscriberData['name'] : '' 'name' => (isset($subscriber_data['name'])) ? $subscriber_data['name'] : ''
); );
} }
function getBody($newsletter, $subscriber) { function getBody($newsletter, $subscriber) {
$body = array( $body = array(
'key' => $this->apiKey, 'key' => $this->api_key,
'message' => array( 'message' => array(
'from_email' => $this->fromEmail, 'from_email' => $this->from_email,
'from_name' => $this->fromName, 'from_name' => $this->from_name,
'to' => array($subscriber), 'to' => array($subscriber),
'subject' => $newsletter['subject'] 'subject' => $newsletter['subject']
), ),

View File

@ -1,19 +1,30 @@
<?php <?php
namespace MailPoet\Mailer; namespace MailPoet\Mailer\Methods;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class SMTP { class SMTP {
function __construct($host, $port, $authentication, $login = null, $password = null, $encryption, public $host;
$fromEmail, $fromName) { public $port;
public $authentication;
public $login;
public $password;
public $encryption;
public $from_name;
public $from_email;
public $mailer;
function __construct(
$host, $port, $authentication, $login = null, $password = null, $encryption,
$from_email, $from_name) {
$this->host = $host; $this->host = $host;
$this->port = $port; $this->port = $port;
$this->authentication = $authentication; $this->authentication = $authentication;
$this->login = $login; $this->login = $login;
$this->password = $password; $this->password = $password;
$this->encryption = $encryption; $this->encryption = $encryption;
$this->fromName = $fromName; $this->from_name = $from_name;
$this->fromEmail = $fromEmail; $this->from_email = $from_email;
$this->mailer = $this->buildMailer(); $this->mailer = $this->buildMailer();
} }
@ -22,7 +33,6 @@ class SMTP {
$message = $this->createMessage($newsletter, $subscriber); $message = $this->createMessage($newsletter, $subscriber);
$result = $this->mailer->send($message); $result = $this->mailer->send($message);
} catch(\Exception $e) { } catch(\Exception $e) {
!d($e->getMessage());exit;
$result = false; $result = false;
} }
return ($result === 1); return ($result === 1);
@ -43,7 +53,7 @@ class SMTP {
function createMessage($newsletter, $subscriber) { function createMessage($newsletter, $subscriber) {
$message = \Swift_Message::newInstance() $message = \Swift_Message::newInstance()
->setFrom(array($this->fromEmail => $this->fromName)) ->setFrom(array($this->from_email => $this->from_name))
->setTo($this->processSubscriber($subscriber)) ->setTo($this->processSubscriber($subscriber))
->setSubject($newsletter['subject']); ->setSubject($newsletter['subject']);
if(!empty($newsletter['body']['html'])) { if(!empty($newsletter['body']['html'])) {
@ -56,15 +66,15 @@ class SMTP {
} }
function processSubscriber($subscriber) { function processSubscriber($subscriber) {
preg_match('!(?P<name>.*?)\s<(?P<email>.*?)>!', $subscriber, $subscriberData); preg_match('!(?P<name>.*?)\s<(?P<email>.*?)>!', $subscriber, $subscriber_data);
if(!isset($subscriberData['email'])) { if(!isset($subscriber_data['email'])) {
$subscriberData = array( $subscriber_data = array(
'email' => $subscriber, 'email' => $subscriber,
); );
} }
return array( return array(
$subscriberData['email'] => $subscriber_data['email'] =>
(isset($subscriberData['name'])) ? $subscriberData['name'] : '', (isset($subscriber_data['name'])) ? $subscriber_data['name'] : ''
); );
} }
} }

View File

@ -1,14 +1,18 @@
<?php <?php
namespace MailPoet\Mailer\API; namespace MailPoet\Mailer\Methods;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class SendGrid { class SendGrid {
function __construct($apiKey, $fromEmail, $fromName) { public $url = 'https://api.sendgrid.com/api/mail.send.json';
$this->url = 'https://api.sendgrid.com/api/mail.send.json'; public $api_key;
$this->apiKey = $apiKey; public $from_email;
$this->fromEmail = $fromEmail; public $from_name;
$this->fromName = $fromName;
function __construct($api_key, $from_email, $from_name) {
$this->api_key = $api_key;
$this->from_email = $from_email;
$this->from_name = $from_name;
} }
function send($newsletter, $subscriber) { function send($newsletter, $subscriber) {
@ -27,8 +31,8 @@ class SendGrid {
function getBody($newsletter, $subscriber) { function getBody($newsletter, $subscriber) {
$body = array( $body = array(
'to' => $subscriber, 'to' => $subscriber,
'from' => $this->fromEmail, 'from' => $this->from_email,
'fromname' => $this->fromName, 'from_name' => $this->from_name,
'subject' => $newsletter['subject'] 'subject' => $newsletter['subject']
); );
if(!empty($newsletter['body']['html'])) { if(!empty($newsletter['body']['html'])) {
@ -41,7 +45,7 @@ class SendGrid {
} }
function auth() { function auth() {
return 'Bearer ' . $this->apiKey; return 'Bearer ' . $this->api_key;
} }
function request($newsletter, $subscriber) { function request($newsletter, $subscriber) {

View File

@ -1,22 +1,22 @@
<?php <?php
namespace MailPoet\Mailer; namespace MailPoet\Mailer\Methods;
require_once(ABSPATH . 'wp-includes/pluggable.php'); require_once(ABSPATH . 'wp-includes/pluggable.php');
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class WPMail { class WPMail {
function __construct($fromEmail, $fromName) { public $from_email;
$this->fromEmail = $fromEmail; public $from_name;
$this->fromName = $fromName; public $filters = array(
add_filter('wp_mail_from', array( 'wp_mail_from' => 'setFromEmail',
$this, 'wp_mail_from_name' => 'setFromName',
'setFromEmail' 'wp_mail_content_type' => 'setContentType'
)); );
$this->filters = array(
'wp_mail_from' => 'setFromEmail', function __construct($from_email, $from_name) {
'wp_mail_from_name' => 'setFromName', $this->from_email = $from_email;
'wp_mail_content_type' => 'setContentType' $this->from_name = $from_name;
);
} }
function addFilters() { function addFilters() {
@ -38,11 +38,11 @@ class WPMail {
} }
function setFromEmail() { function setFromEmail() {
return $this->fromEmail; return $this->from_email;
} }
function setFromName() { function setFromName() {
return $this->fromName; return $this->from_name;
} }
function setContentType() { function setContentType() {
@ -53,7 +53,9 @@ class WPMail {
$this->addFilters(); $this->addFilters();
$result = wp_mail( $result = wp_mail(
$subscriber, $newsletter['subject'], $subscriber, $newsletter['subject'],
(!empty($newsletter['body']['html'])) ? $newsletter['body']['html'] : $newsletter['body']['text'] (!empty($newsletter['body']['html'])) ?
$newsletter['body']['html'] :
$newsletter['body']['text']
); );
$this->removeFilters(); $this->removeFilters();
return ($result === true); return ($result === true);

View File

@ -4,7 +4,7 @@ namespace MailPoet\Models;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class SendingQueue extends Model { class SendingQueue extends Model {
public static $_table = MP_SENDING_QUEUE_TABLE; public static $_table = MP_SENDING_QUEUES_TABLE;
function __construct() { function __construct() {
parent::__construct(); parent::__construct();

View File

@ -16,23 +16,73 @@ class Setting extends Model {
} }
public static function getValue($key, $default = null) { public static function getValue($key, $default = null) {
$setting = Setting::where('name', $key)->findOne(); $keys = explode('.', $key);
if($setting === false) {
return $default; if(count($keys) === 1) {
} else { $setting = Setting::where('name', $key)->findOne();
if(is_serialized($setting->value)) { if($setting === false) {
return unserialize($setting->value); return $default;
} else { } else {
return $setting->value; if(is_serialized($setting->value)) {
return unserialize($setting->value);
} else {
return $setting->value;
}
} }
} else {
$main_key = array_shift($keys);
$setting = static::getValue($main_key, $default);
if($setting !== $default) {
for($i = 0, $count = count($keys); $i < $count; $i++) {
if(array_key_exists($keys[$i], $setting)) {
$setting = $setting[$keys[$i]];
} else {
return $default;
}
}
}
return $setting;
} }
} }
public static function setValue($key, $value) { public static function setValue($key, $value) {
return Setting::createOrUpdate(array( $keys = explode('.', $key);
'name' => $key,
'value' => $value if(count($keys) === 1) {
)); if(is_array($value)) {
$value = serialize($value);
}
return Setting::createOrUpdate(array(
'name' => $key,
'value' => $value
));
} else {
$main_key = array_shift($keys);
$setting_value = static::getValue($main_key, array());
$current_value = &$setting_value;
$last_key = array_pop($keys);
foreach($keys as $key) {
if(!is_array($current_value)) {
$current_value = array();
}
if(!array_key_exists($key, $current_value)) {
$current_value = array($key => array());
}
$current_value =& $current_value[$key];
}
if(is_scalar($current_value)) {
$current_value = array();
}
$current_value[$last_key] = $value;
return static::setValue($main_key, $setting_value);
}
} }
public static function getAll() { public static function getAll() {

View File

@ -32,6 +32,51 @@ class Subscriber extends Model {
return parent::delete(); return parent::delete();
} }
function addToSegments(array $segment_ids = array()) {
$segments = Segment::whereIn('id', $segment_ids)->findMany();
foreach($segments as $segment) {
$association = SubscriberSegment::create();
$association->subscriber_id = $this->id;
$association->segment_id = $segment->id;
$association->save();
}
}
function sendConfirmationEmail() {
$this->set('status', 'unconfirmed');
// TODO
}
static function subscribe($subscriber_data = array(), $segment_ids = array()) {
if(empty($subscriber_data) or empty($segment_ids)) {
return false;
}
$subscriber = static::createOrUpdate($subscriber_data);
if($subscriber !== false && $subscriber->id() > 0) {
// restore deleted subscriber
if($subscriber->deleted_at !== NULL) {
$subscriber->setExpr('deleted_at', 'NULL');
}
if((bool)Setting::getValue('signup_confirmation.enabled')) {
if($subscriber->status !== 'subscribed') {
$subscriber->sendConfirmationEmail();
}
} else {
$subscriber->set('status', 'subscribed');
}
if($subscriber->save()) {
$subscriber->addToSegments($segment_ids);
}
}
return $subscriber;
}
static function search($orm, $search = '') { static function search($orm, $search = '') {
if(strlen(trim($search) === 0)) { if(strlen(trim($search) === 0)) {
return $orm; return $orm;
@ -154,7 +199,7 @@ class Subscriber extends Model {
$orm = $orm->selectExpr( $orm = $orm->selectExpr(
'CASE WHEN ' . 'CASE WHEN ' .
MP_CUSTOM_FIELDS_TABLE . '.id=' . $customField['id'] . ' THEN ' . MP_CUSTOM_FIELDS_TABLE . '.id=' . $customField['id'] . ' THEN ' .
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE . '.value END as "' . $customField['name'].'"'); MP_SUBSCRIBER_CUSTOM_FIELD_TABLE . '.value END as "' . $customField['id'].'"');
} }
$orm = $orm $orm = $orm
->leftOuterJoin( ->leftOuterJoin(
@ -181,21 +226,82 @@ class Subscriber extends Model {
$subscriber = false; $subscriber = false;
if(isset($data['id']) && (int)$data['id'] > 0) { if(isset($data['id']) && (int)$data['id'] > 0) {
$subscriber = self::findOne((int)$data['id']); $subscriber = static::findOne((int)$data['id']);
unset($data['id']); unset($data['id']);
} }
if($subscriber === false && !empty($data['email'])) {
$subscriber = static::where('email', $data['email'])->findOne();
if($subscriber !== false) {
unset($data['email']);
}
}
// custom fields
$custom_fields = array();
foreach($data as $key => $value) {
if(strpos($key, 'cf_') === 0) {
$custom_fields[(int)substr($key, 3)] = $value;
unset($data[$key]);
}
}
if($subscriber === false) { if($subscriber === false) {
$subscriber = self::create(); $subscriber = static::create();
$subscriber->hydrate($data); $subscriber->hydrate($data);
} else { } else {
$subscriber->set($data); $subscriber->set($data);
} }
$subscriber->save(); if($subscriber->save()) {
if(!empty($custom_fields)) {
foreach($custom_fields as $custom_field_id => $value) {
$subscriber->setCustomField($custom_field_id, $value);
}
}
}
return $subscriber; return $subscriber;
} }
function withCustomFields() {
$custom_fields = CustomField::select('id')->findArray();
if(empty($custom_fields)) return $this;
$custom_field_ids = Helpers::arrayColumn($custom_fields, 'id');
$relations = SubscriberCustomField::select('custom_field_id')
->select('value')
->whereIn('custom_field_id', $custom_field_ids)
->where('subscriber_id', $this->id())
->findMany();
foreach($relations as $relation) {
$this->{'cf_'.$relation->custom_field_id} = $relation->value;
}
return $this;
}
function getCustomField($custom_field_id, $default = null) {
$custom_field = SubscriberCustomField::select('value')
->where('custom_field_id', $custom_field_id)
->where('subscriber_id', $this->id())
->findOne();
if($custom_field === false) {
return $default;
} else {
return $custom_field->value;
}
}
function setCustomField($custom_field_id, $value) {
return SubscriberCustomField::createOrUpdate(array(
'subscriber_id' => $this->id(),
'custom_field_id' => $custom_field_id,
'value' => $value
));
}
static function bulkMoveToList($orm, $data = array()) { static function bulkMoveToList($orm, $data = array()) {
$segment_id = (isset($data['segment_id']) ? (int)$data['segment_id'] : 0); $segment_id = (isset($data['segment_id']) ? (int)$data['segment_id'] : 0);
$segment = Segment::findOne($segment_id); $segment = Segment::findOne($segment_id);
@ -273,8 +379,7 @@ class Subscriber extends Model {
if(!empty($subscribers)) { if(!empty($subscribers)) {
foreach($subscribers as $subscriber) { foreach($subscribers as $subscriber) {
// TODO: send confirmation email $subscriber->sendConfirmationEmail();
// $subscriber->sendConfirmationEmail()
} }
return $subscribers->count(); return $subscribers->count();

View File

@ -12,6 +12,49 @@ class SubscriberCustomField extends Model {
parent::__construct(); parent::__construct();
} }
static function createOrUpdate($data = array()) {
$custom_field = CustomField::findOne($data['custom_field_id']);
if($custom_field === false) {
return false;
} else {
$custom_field = $custom_field->asArray();
}
if($custom_field['type'] === 'date') {
if(is_array($data['value'])) {
$day = (
isset($data['value']['day'])
? (int)$data['value']['day']
: 1
);
$month = (
isset($data['value']['month'])
? (int)$data['value']['month']
: 1
);
$year = (
isset($data['value']['year'])
? (int)$data['value']['year']
: 1970
);
$data['value'] = mktime(0, 0, 0, $month, $day, $year);
}
}
$relation = self::where('custom_field_id', $data['custom_field_id'])
->where('subscriber_id', $data['subscriber_id'])
->findOne();
if($relation === false) {
$relation = self::create();
$relation->hydrate($data);
} else {
$relation->set($data);
}
return $relation->save();
}
static function createMultiple($values) { static function createMultiple($values) {
$values = array_map('array_values', $values); $values = array_map('array_values', $values);
return self::rawExecute( return self::rawExecute(

View File

@ -33,6 +33,10 @@ class SubscriberSegment extends Model {
return $orm; return $orm;
} }
static function subscribed($orm) {
return $orm->where('status', 'subscribed');
}
static function createMultiple($segmnets, $subscribers) { static function createMultiple($segmnets, $subscribers) {
$values = Helpers::flattenArray( $values = Helpers::flattenArray(
array_map(function ($segment) use ($subscribers) { array_map(function ($segment) use ($subscribers) {

View File

@ -1,47 +1,40 @@
<?php namespace MailPoet\Newsletter\Renderer\Blocks; <?php
namespace MailPoet\Newsletter\Renderer\Blocks;
use MailPoet\Newsletter\Renderer\StylesHelper; use MailPoet\Newsletter\Renderer\StylesHelper;
class Button { class Button {
static function render($element) { static function render($element) {
$stylesHelper = new StylesHelper();
$template = ' $template = '
<tr> <tr>
<td class="mailpoet_col mailpoet_button mailpoet_padded" valign = "top" > <td class="mailpoet_padded" valign="top">
<div> <div>
<table width="100%" cellpadding="0" cellspacing="0" border="0"> <table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;">
<tr> <tr>
<td align="' . $element['styles']['block']['textAlign'] . '"> <td class="mailpoet_button-container" style="padding:8px 0;text-align:' . $element['styles']['block']['textAlign'] . ';"><!--[if mso]>
<!--[if mso]> <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word"
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" href="' . $element['url'] . '"
xmlns:w="urn:schemas-microsoft-com:office:word" style="height:' . $element['styles']['block']['lineHeight'] . ';
href="' . $element['url'] . '" width:' . $element['styles']['block']['width'] . ';
style="height:' . $element['styles']['block']['lineHeight'] . '; v-text-anchor:middle;"
width:' . $element['styles']['block']['width'] . '; arcsize="' . round($element['styles']['block']['borderRadius'] / $element['styles']['block']['lineHeight'] * 100) . '%"
v-text-anchor:middle;" strokeweight="1px"
arcsize="' . round($element['styles']['block']['borderRadius'] / $element['styles']['block']['lineHeight'] * 100) . '%" strokecolor="' . $element['styles']['block']['borderColor'] . '"
strokecolor="' . $element['styles']['block']['borderColor'] . '" fillcolor="' . $element['styles']['block']['backgroundColor'] . '">
fillcolor="' . $element['styles']['block']['backgroundColor'] . '">
<w:anchorlock/> <w:anchorlock/>
<center style="color:' . $element['styles']['block']['fontColor'] . '; <center style="color:' . $element['styles']['block']['fontColor'] . ';
font-family:' . $element['styles']['block']['fontFamily'] . '; font-family:' . $element['styles']['block']['fontFamily'] . ';
font-size:' . $element['styles']['block']['fontSize'] . '; font-size:' . $element['styles']['block']['fontSize'] . ';
font-weight:bold;">' . $element['text'] . ' font-weight:bold;">' . $element['text'] . '
</center> </center>
</v:roundrect> </v:roundrect>
<![endif]--> <![endif]--><a class="mailpoet_button" href="' . $element['url'] . '" style="display:inline-block;-webkit-text-size-adjust:none;mso-hide:all;text-decoration:none!important;text-align:center;' . StylesHelper::getBlockStyles($element, $exclude = array('textAlign')) . '"> ' . $element['text'] . '
<a class="mailpoet_button" </td>
href="' . $element['url'] . '" </tr>
style="display:inline-block;text-align:center;text-decoration:none;-webkit-text-size-adjust:none;mso-hide:all;' . $stylesHelper->getBlockStyles($element, array('textAlign')) . '"> ' . $element['text'] . ' </table>
</a> </div>
</td> </td>
</tr> </tr>';
</table>
</div>
</td>
</tr>';
return $template; return $template;
} }
} }

View File

@ -1,23 +1,34 @@
<?php namespace MailPoet\Newsletter\Renderer\Blocks; <?php
namespace MailPoet\Newsletter\Renderer\Blocks;
use MailPoet\Newsletter\Renderer\StylesHelper;
class Divider { class Divider {
static function render($element) { static function render($element) {
$template = ' $template = '
<tr> <tr>
<td class="mailpoet_col mailpoet_divider mailpoet_padded" <td class="mailpoet_divider" valign="top" ' .
style="background-color: ' . $element['styles']['block']['backgroundColor'] . '; padding: ' . $element['styles']['block']['padding'] . ' 0;" (($element['styles']['block']['backgroundColor'] !== 'transparent') ?
valign="top"> 'bgColor="' . $element['styles']['block']['backgroundColor'] . '" style="background-color:' . $element['styles']['block']['backgroundColor'] . ';' :
<table width="100%"> 'style="'
<tr> ) .
<td style="border-top-width: ' . $element['styles']['block']['borderWidth'] . '; sprintf('padding: %s %spx %s %spx;',
border-top-style: ' . $element['styles']['block']['borderStyle'] . '; $element['styles']['block']['padding'],
border-top-color: ' . $element['styles']['block']['borderColor'] . ';"> StylesHelper::$padding_width,
</td> $element['styles']['block']['padding'],
</tr> StylesHelper::$padding_width) . '">
</table> <table width="100%" border="0" cellpadding="0" cellspacing="0"
</td> style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0;">
</tr>'; <tr>
<td class="mailpoet_divider-cell"
style="border-top-width: ' . $element['styles']['block']['borderWidth'] . ';
border-top-style: ' . $element['styles']['block']['borderStyle'] . ';
border-top-color: ' . $element['styles']['block']['borderColor'] . ';">
</td>
</tr>
</table>
</td>
</tr>';
return $template; return $template;
} }
} }

View File

@ -1,30 +1,27 @@
<?php namespace MailPoet\Newsletter\Renderer\Blocks; <?php
namespace MailPoet\Newsletter\Renderer\Blocks;
use MailPoet\Newsletter\Renderer\StylesHelper; use MailPoet\Newsletter\Renderer\StylesHelper;
class Footer { class Footer {
static function render($element) { static function render($element) {
$stylesHelper = new StylesHelper();
// apply link styles
if(isset($element['styles']['link'])) { if(isset($element['styles']['link'])) {
$element['text'] = str_replace('<a', '<a style="' . $stylesHelper->getStyles($element['styles'], 'link') . '"', $element['text']); $element['text'] = str_replace(
'<a',
'<a style="'
. StylesHelper::getStyles($element['styles'], 'link')
. '"', $element['text']
);
} }
$element['text'] = preg_replace('/\n/', '<br /><br />', $element['text']);
// apply text styles $element['text'] = preg_replace('/(<\/?p>)/', '', $element['text']);
if(isset($element['styles']['link'])) {
$element['text'] = str_replace('<p', '<p style="' . $stylesHelper->getStyles($element['styles'], 'text') . '"', $element['text']);
}
$template = ' $template = '
<tr> <tr>
<td class="mailpoet_col mailpoet_footer" <td class="mailpoet_padded_header_footer mailpoet_footer" bgcolor="' . $element['styles']['block']['backgroundColor'] . '"
style="' . $stylesHelper->getStyles($element['styles'], 'block') . '" style="' . StylesHelper::getBlockStyles($element) . StylesHelper::getStyles($element['styles'], 'text') . '">
valign="top"> ' . $element['text'] . '
<div>' . $element['text'] . '</div> </td>
</td> </tr>';
</tr>';
return $template; return $template;
} }
} }

View File

@ -1,30 +1,27 @@
<?php namespace MailPoet\Newsletter\Renderer\Blocks; <?php
namespace MailPoet\Newsletter\Renderer\Blocks;
use MailPoet\Newsletter\Renderer\StylesHelper; use MailPoet\Newsletter\Renderer\StylesHelper;
class Header { class Header {
static function render($element) { static function render($element) {
$stylesHelper = new StylesHelper();
// apply link styles
if(isset($element['styles']['link'])) { if(isset($element['styles']['link'])) {
$element['text'] = str_replace('<a', '<a style="' . $stylesHelper->getStyles($element['styles'], 'link') . '"', $element['text']); $element['text'] = str_replace(
'<a',
'<a style="'
. StylesHelper::getStyles($element['styles'], 'link')
. '"', $element['text']
);
} }
$element['text'] = preg_replace('/\n/', '<br /><br />', $element['text']);
// apply text styles $element['text'] = preg_replace('/(<\/?p>)/', '', $element['text']);
if(isset($element['styles']['link'])) {
$element['text'] = str_replace('<p', '<p style="' . $stylesHelper->getStyles($element['styles'], 'text') . '"', $element['text']);
}
$template = ' $template = '
<tr> <tr>
<td class="mailpoet_col mailpoet_header" <td class="mailpoet_padded_header_footer mailpoet_header" bgcolor="' . $element['styles']['block']['backgroundColor'] . '"
style="' . $stylesHelper->getBlockStyles($element) . '" style="' . StylesHelper::getBlockStyles($element) . StylesHelper::getStyles($element['styles'], 'text') . '">
valign="top"> ' . $element['text'] . '
<div>' . $element['text'] . '</div> </td>
</td> </tr>';
</tr>';
return $template; return $template;
} }
} }

View File

@ -1,24 +1,42 @@
<?php namespace MailPoet\Newsletter\Renderer\Blocks; <?php
namespace MailPoet\Newsletter\Renderer\Blocks;
use MailPoet\Newsletter\Renderer\Columns\ColumnsHelper;
use MailPoet\Newsletter\Renderer\StylesHelper; use MailPoet\Newsletter\Renderer\StylesHelper;
class Image { class Image {
static function render($element) { static function render($element, $columnCount) {
$stylesHelper = new StylesHelper(); $element = self::getImageDimensions($element, $columnCount);
$element['width'] = (int) $element['width'];
$template = ' $template = '
<tr> <tr>
<td class="mailpoet_col mailpoet_image ' . (($element['padded'] === true) ? "mailpoet_padded" : "") . '" <td class="mailpoet_image ' . $element['paddedClass'] . '" align="center" valign="top">
style="' . $stylesHelper->getBlockStyles($element) . '" <img style="max-width:' . $element['width'] . 'px;" src="' . $element['src'] . '"
valign="top"> width="' . $element['width'] . '" height="' . $element['height'] . '" alt="' . $element['alt'] . '"/>
<img style="top:0; left:0; height: auto; width:100%;" </td>
src="' . $element['src'] . '" </tr>';
width="' . (($element['padded'] === true) ? $element['width'] - (20 * 2) : $element['width']) . '">
</td>
</tr>';
return $template; return $template;
} }
static function getImageDimensions($element, $column_count) {
$column_width = ColumnsHelper::columnWidth($column_count);
$padded_width = StylesHelper::$padding_width * 2;
// resize image if it's wider than the column width
if((int) $element['width'] >= $column_width) {
$ratio = (int) $element['width'] / $column_width;
$element['width'] = $column_width;
$element['height'] = ceil((int) $element['height'] / $ratio);
}
if($element['padded'] == "true" && $element['width'] >= $column_width) {
// resize image if the padded option is on
$ratio = (int) $element['width'] / ((int) $element['width'] - $padded_width);
$element['width'] = (int) $element['width'] - $padded_width;
$element['height'] = ceil((int) $element['height'] / $ratio);
$element['paddedClass'] = 'mailpoet_padded';
} else {
$element['width'] = (int) $element['width'];
$element['height'] = (int) $element['height'];
$element['paddedClass'] = '';
}
return $element;
}
} }

View File

@ -1,24 +1,24 @@
<?php namespace MailPoet\Newsletter\Renderer\Blocks; <?php
namespace MailPoet\Newsletter\Renderer\Blocks;
class Renderer { class Renderer {
function render($data) { function render($data, $column_count) {
array_map(function ($block) use (&$blockContent, &$columns) { $block_content = '';
$blockContent .= $this->createElementFromBlockType($block); array_map(function ($block) use (&$block_content, &$columns, $column_count) {
$block_content .= $this->createElementFromBlockType($block, $column_count);
if(isset($block['blocks'])) { if(isset($block['blocks'])) {
$blockContent = $this->render($block); $block_content = $this->render($block, $column_count);
} }
// vertical orientation denotes column container // vertical orientation denotes column container
if($block['type'] === 'container' && $block['orientation'] === 'vertical') { if($block['type'] === 'container' && $block['orientation'] === 'vertical') {
$columns[] = $blockContent; $columns[] = $block_content;
} }
}, $data['blocks']); }, $data['blocks']);
return (isset($columns)) ? $columns : $block_content;
return (isset($columns)) ? $columns : $blockContent;
} }
function createElementFromBlockType($block) { function createElementFromBlockType($block, $column_count) {
$blockClass = __NAMESPACE__ . '\\' . ucfirst($block['type']); $block_class = __NAMESPACE__ . '\\' . ucfirst($block['type']);
return (class_exists($blockClass)) ? $blockClass::render($block) : ''; return (class_exists($block_class)) ? $block_class::render($block, $column_count) : '';
} }
}
}

View File

@ -1,26 +1,23 @@
<?php namespace MailPoet\Newsletter\Renderer\Blocks; <?php
namespace MailPoet\Newsletter\Renderer\Blocks;
class Social { class Social {
static function render($element) { static function render($element) {
$iconsBlock = ''; $icons_block = '';
if(is_array($element['icons'])) { if(is_array($element['icons'])) {
foreach ($element['icons'] as $icon) { foreach($element['icons'] as $index => $icon) {
$iconsBlock .= ' $icons_block .= '
<a href="' . $icon['link'] . '"> <a href="' . $icon['link'] . '" style="text-decoration:none!important;">
<img src="' . $icon['image'] . '" width = "32" height = "32" style="width: 32px; height: 32px;" alt="' . $icon['iconType'] . '"> <img src="' . $icon['image'] . '" width="' . (int) $icon['width'] . '" height="' . (int) $icon['height'] . '" style="width:' . $icon['width'] . ';height:' . $icon['width'] . ';-ms-interpolation-mode:bicubic;border:0;display:inline;outline:none;" alt="' . $icon['iconType'] . '">
</a> </a>';
<img src="http://mp3.mailpoet.net/spacer.gif" width = "10" height = "1" style=" width: 10px; height: 1px;">';
} }
$template = '
<tr>
<td class="mailpoet_padded" valign="top" align="center">
' . $icons_block . '
</td>
</tr>';
return $template;
} }
$template = '
<tr>
<td class="mailpoet_col mailpoet_social" valign="top">
<div class="mailpoet_social-icon mailpoet_padded">' . $iconsBlock . ' </div>
</td>
</tr>';
return $template;
} }
} }

View File

@ -1,23 +1,12 @@
<?php namespace MailPoet\Newsletter\Renderer\Blocks; <?php
namespace MailPoet\Newsletter\Renderer\Blocks;
use MailPoet\Newsletter\Renderer\StylesHelper;
class Spacer { class Spacer {
static function render($element) { static function render($element) {
$stylesHelper = new StylesHelper();
// if the parent container (column) has background set and the divider element has a transparent background,
// it will assume the newsletter background, not that of the parent container
if($element['styles']['block']['backgroundColor'] === 'transparent') {
unset($element['styles']['block']['backgroundColor']);
}
$template = ' $template = '
<tr> <tr>
<td class="mailpoet_col mailpoet_spacer" style="' . $stylesHelper->getBlockStyles($element) . '" valign="top"> </td> <td class="mailpoet_spacer" height="' . (int) $element['styles']['block']['height'] . '" valign="top"></td>
</tr>'; </tr>';
return $template; return $template;
} }
} }

View File

@ -1,114 +1,110 @@
<?php namespace MailPoet\Newsletter\Renderer\Blocks; <?php
namespace MailPoet\Newsletter\Renderer\Blocks;
class Text { class Text {
static $typeFace = array(
'Arial' => "Arial, 'Helvetica Neue', Helvetica, sans-serif",
'Comic Sans MS' => "'Comic Sans MS', 'Marker Felt-Thin', Arial, sans-serif",
'Courier New' => "'Courier New', Courier, 'Lucida Sans Typewriter', 'Lucida Typewriter', monospace",
'Georgia' => "Georgia, Times, 'Times New Roman', serif",
'Lucida' => "'Lucida Sans Unicode', 'Lucida Grande', sans-serif",
'Tahoma' => "Tahoma, Verdana, Segoe, sans-serif",
'Times New Roman' => "'Times New Roman', Times, Baskerville, Georgia, serif",
'Trebuchet MS' => "'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif",
'Verdana' => "Verdana, Geneva, sans-serif"
);
static function render($element) { static function render($element) {
$html = $element['text']; $html = $element['text'];
$html = self::convertBlockquotesToTables($html); $html = self::convertBlockquotesToTables($html);
$html = self::addLineBreakAfterTags($html);
$html = self::removeEmptyTags($html);
$html = self::convertEmptyParagraphsToLineBreaks($html);
$html = self::convertParagraphsToTables($html); $html = self::convertParagraphsToTables($html);
$html = self::removeLastBreakLine($html); $html = self::addLineBreakAfterTags($html);
$html = self::styleLists($html);
$html = self::styleHeadings($html);
$html = self::removeLastElementBreakLine($html);
$template = ' $template = '
<tr> <tr>
<td class="mailpoet_col mailpoet_text mailpoet_padded" valign="top">' . $html . ' </td> <td class="mailpoet_text mailpoet_padded" valign="top" style="word-break:break-word;word-wrap:break-word;">
</tr>'; ' . $html . '
</td>
</tr>';
return $template; return $template;
} }
static function removeLastBreakLine($html) {
return preg_replace('/<br>([^<br>]*)$/s', '', $html);
}
static function convertParagraphsToTables($html) { static function convertParagraphsToTables($html) {
$html = preg_replace('/<p>(.*?)<\/p>/', ' $html = preg_replace('/<p>(.*?)<\/p>/', '
<table cellpadding="0" cellspacing="0" border="0"> <table width="100%" cellpadding="0" cellspacing="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0">
<tr> <tr>
<td> <td class="mailpoet_paragraph" style="word-break:break-word;word-wrap:break-word;">
<span class="paragraph">
$1 $1
</span> <br /><br />
</td> </td>
</tr> </tr>
</table>', $html); </table>'
, $html);
return preg_replace('/<p(.+style=\".*?\")?>(.*?)<\/p>/', ' $html = preg_replace('/<p style=\"(.*)\">(.*?)<\/p>/', '
<table cellpadding="0" cellspacing="0" border="0"> <table width="100%" cellpadding="0" cellspacing="0" style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0">
<tr> <tr>
<td $1> <td class="mailpoet_paragraph" style="word-break:break-word;word-wrap:break-word;$1">
<span class="paragraph">
$2 $2
</span> <br /><br />
</td> </td>
</tr> </tr>
</table>', $html); </table>'
, $html);
return $html;
} }
static function convertEmptyParagraphsToLineBreaks($html) {
return preg_replace('/<p(?:.+style=\".*?\")?><\/p>/', '<br>', $html); static function removeLastElementBreakLine($html) {
return preg_replace('/<br\/>([^<br\/>]*)$/s', '', $html);
} }
static function addLineBreakAfterTags($html) { static function addLineBreakAfterTags($html) {
return preg_replace('/(<\/(ul|ol|h\d)>)/', '$1<br>', $html); return preg_replace('/(<\/(ul|ol|h\d)>)/', '$1<br />', $html);
} }
static function convertBlockquotesToTables($html) { static function convertBlockquotesToTables($html) {
$template = ' $template = '
<table cellpadding="0" cellspacing="0" border="0" class="mailpoet_blockquote"> <table width="100%" cellpadding="0" cellspacing="0" border="0">
<tbody> <tbody>
<tr> <tr>
<td valign="top">$1</td> <td width="2" bgcolor="#565656"></td>
</tr> <td width="10"></td>
</tbody> <td valign="top">
</table> <table style="border-spacing:0;mso-table-lspace:0;mso-table-rspace:0">
<br>'; <tr>
<td class="mailpoet_blockquote">
return preg_replace('/<blockquote>(.*?)<\/blockquote>/s', $template, $html); $1
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
<br/>';
preg_match('/<blockquote>.*?<\/blockquote>/s', $html, $blockquotes);
foreach($blockquotes as $index => $blockquote) {
$blockquote = preg_replace('/<\/p>\n<p>/', '<br/><br/>', $blockquote);
$blockquote = preg_replace('/<\/?p>/', '', $blockquote);
$blockquote = preg_replace(
'/<blockquote>(.*?)<\/blockquote>/s',
$template,
$blockquote
);
$html = preg_replace(
'/' . preg_quote($blockquotes[$index], '/') . '/',
$blockquote,
$html
);
}
return $html;
} }
static function removeEmptyTags($html) { static function styleHeadings($html) {
$pattern = <<<'EOD' return preg_replace(
~ '/<(h[1-6])(?:.+style=\"(.*)?\")?>/',
< '<$1 style="margin:0;font-style:normal;font-weight:normal;$2">',
(?: $html
!--[^-]*(?:-(?!->)[^-]*)*-->[^<]*(*SKIP)(*F) # skip comments );
| }
( # group 1
(span|em|strong) # tag name in group 2 static function styleLists($html) {
[^"'>]* #'"# all that is not a quote or a closing angle bracket $html = preg_replace(
(?: # quoted attributes '/<(ul|ol)>/',
"[^\\"]*(?:\\.[^\\"]*)*+" [^"'>]* #'"# double quote '<$1 class="mailpoet_paragraph" style="padding-top:0;padding-bottom:0;margin-top:0;margin-bottom:0;">',
| $html
'[^\\']*(?:\\.[^\\']*)*+' [^"'>]* #'"# single quote );
)*+ $html = preg_replace('/<li>/', '<li class="mailpoet_paragraph">', $html);
> return $html;
\s*
(?:
<!--[^-]*(?:-(?!->)[^-]*)*+--> \s* # html comments
|
<(?1) \s* # recursion with the group 1
)*+
</\2> # closing tag
) # end of the group 1
)
~sxi
EOD;
return preg_replace($pattern, '', $html);
} }
} }

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