Compare commits

...

115 Commits
0.0.4 ... 0.0.7

Author SHA1 Message Date
c0ba218949 Merge branch 'master' of github.com:mailpoet/mailpoet 2015-12-04 21:41:29 +01:00
ee85139089 Merge pull request #254 from mailpoet/queue
Sending Queue: start & track
2015-12-04 21:36:50 +01:00
b4f83fe1bd Merge pull request #257 from mailpoet/image_size
Custom newsletter image size
2015-12-04 21:22:15 +01:00
bd83001ef5 Merge pull request #258 from mailpoet/helpscout
HelpScout integration fixes
2015-12-04 21:20:28 +01:00
fb3a9f485f Merge pull request #259 from mailpoet/custom_fields
Add custom fields to newsletter editor
2015-12-04 21:20:09 +01:00
fd44776ae9 - Fixes SMTP issue 2015-12-04 14:33:43 -05:00
6dbe338b01 - Updates mailers to use HTML and/or TEXT body 2015-12-04 14:06:43 -05:00
1f06a7dd0b fixed naming of sending_queue & added validation of segments on step 3 2015-12-04 19:54:31 +01:00
37c218f782 fixed sending test email text version only 2015-12-04 19:46:44 +01:00
25016f2a8d test sending method works 2015-12-04 19:28:00 +01:00
792744a270 - Implements check for name/email in mailer router 2015-12-04 12:47:14 -05:00
c5fbfca132 Merge branch 'queue' of mailpoet:mailpoet/mailpoet into queue 2015-12-04 12:32:38 -05:00
c2fde308cb - Renames and updates sending queue worker
- Updates mailer router's send() method
2015-12-04 12:31:54 -05:00
533d9b0d38 Change custom field shortcode to follow MP2 format 2015-12-04 17:48:35 +02:00
2035b802e3 Add shortcodes for custom fields to newsletter editor 2015-12-04 17:47:18 +02:00
a5e66ec6a0 implemented sending test email 2015-12-04 16:20:07 +01:00
beb939df9e updated send with tab 2015-12-04 15:31:04 +01:00
44e342c692 Fix z-index, change icon, add instructional message 2015-12-04 15:30:59 +02:00
3a417d460f Add custom MailPoet image size for newsletters 2015-12-04 14:37:05 +02:00
1950d6661f Step 3 sender and reply to per newsletter
- added sender_address/sender_name/reply_to_address/reply_to_name
- added validation on all form fields (except checkbox and radio)
2015-12-04 12:29:11 +01:00
da6e154642 fixed conflicts 2015-12-04 11:25:06 +01:00
acebf669a7 - Updates queue worker to use mailer router for sending
- Updates mailer router to detect method type
- Rebases master
2015-12-03 22:01:33 -05:00
4a2bbe3f88 Sending Progress
- improved progress bar styles (with completed status)
- add pause/resume buttons
- fixed method case in settings.mta for MailPoet & SMTP Providers
- fixed parsley dependency
- added validation on from name & address on step 3
2015-12-03 22:01:19 -05:00
9b011c0281 - Places supervisor/daemon/worker under the new Cron class
- Updates endpoints
- Integrates queue worker with MailPoet mailer
- Fixes script activation check logic
2015-12-03 22:01:18 -05:00
bf58d8a22d Queue
- updated menu icon for our plugin
- added watchCss command to watch only CSS files
- added Status column in Newsletters listing
- added progress bar styles
- fixed issue with JS assets being loaded twice on non MP pages
- changed subscriber_ids to segment_ids in addQueue
2015-12-03 22:01:17 -05:00
72d1eb79a6 fixed too much recursion issue on selection JSX 2015-12-03 22:01:17 -05:00
bb4893c0a0 added missing messages on newsletter form 2015-12-03 22:01:16 -05:00
9929cf0aee - Adds router methods to add/delete/et queue status 2015-12-03 22:01:15 -05:00
83967e84ba - Implements starting/stopping/pausing daemon 2015-12-03 22:01:15 -05:00
9621cb3ca9 Merge pull request #253 from mailpoet/animations
Editor: Animations
2015-12-03 22:48:43 +01:00
a413f666fe Sending Progress
- improved progress bar styles (with completed status)
- add pause/resume buttons
- fixed method case in settings.mta for MailPoet & SMTP Providers
- fixed parsley dependency
- added validation on from name & address on step 3
2015-12-03 18:26:36 +01:00
d1e2c6c074 Add transition for "Delete block" icon 2015-12-03 15:53:56 +02:00
3b6a9f7a6e Add block, tool and widget transition animations 2015-12-03 14:44:32 +02:00
d2e5fb89c2 - Places supervisor/daemon/worker under the new Cron class
- Updates endpoints
- Integrates queue worker with MailPoet mailer
- Fixes script activation check logic
2015-12-02 22:48:15 -05:00
97d1e95037 Add transitions for content block addition and removal 2015-12-02 17:54:06 +02:00
48fbce22e7 Queue
- updated menu icon for our plugin
- added watchCss command to watch only CSS files
- added Status column in Newsletters listing
- added progress bar styles
- fixed issue with JS assets being loaded twice on non MP pages
- changed subscriber_ids to segment_ids in addQueue
2015-12-02 12:25:28 +01:00
916fe76795 Add transitions: sidebar contents, template preview, sidebar 2015-12-01 16:49:50 +02:00
e10310fb5c fixed too much recursion issue on selection JSX 2015-12-01 13:50:35 +01:00
367afcf814 added missing messages on newsletter form 2015-12-01 13:01:06 +01:00
67fa9e0993 - Adds router methods to add/delete/et queue status 2015-11-30 19:09:06 -05:00
d7553a5f27 Merge pull request #251 from mailpoet/newsletter_templates
Editor: Add two sample templates
2015-11-30 21:13:49 +01:00
8c847825fa - Implements starting/stopping/pausing daemon 2015-11-30 13:01:07 -05:00
d21d9b99b0 Add an option for certain templates to be read only 2015-11-30 18:23:43 +02:00
8461c13532 Include thumbnail on saved templates, add another sample template 2015-11-30 18:05:10 +02:00
3b7ffe9ba7 Update Marionette to 2.4.4 from 2.4.3 2015-11-30 13:21:28 +02:00
1724fa22c1 Merge pull request #250 from mailpoet/default_data_on_install
Default segments on install
2015-11-30 12:17:32 +01:00
01089d7a72 Bump version to 0.0.6 2015-11-27 22:00:54 +01:00
717ebfd20c Bump composer lockfile. 2015-11-27 22:00:17 +01:00
daff3d5016 Merge pull request #249 from mailpoet/sticky_kit_fix
Add back sticky-kit dependency
2015-11-27 19:14:03 +01:00
98fb838169 updated default lists' descriptions 2015-11-27 15:56:18 +01:00
f13a4dd4f3 Add back sticky-kit dependency 2015-11-27 16:44:04 +02:00
62a164e4c6 added creation of WP Users & default list on install 2015-11-27 15:27:50 +01:00
04238f3339 Phoenix Vagrant VM has been replaced with a new VM with test data. 2015-11-27 15:16:06 +01:00
bc62474f3c Merge pull request #240 from mailpoet/queue
Queue
2015-11-27 13:49:18 +01:00
98005a2a6f - Rebases master 2015-11-27 07:46:26 -05:00
b7fe8dc6d6 - Adds Daemon status (under Mailpoet->Queue) 2015-11-27 07:40:23 -05:00
436faea591 - Refactors and renames code
- Adds Queue menu option and displays status
2015-11-27 07:40:22 -05:00
4208b148b4 - Implements queue worker class 2015-11-27 07:35:16 -05:00
5ce5f0bf8a - Refactors code 2015-11-27 07:35:16 -05:00
18518de397 - Refactors some code 2015-11-27 07:35:15 -05:00
68f747211a - Adds a helper method to convert cameCase to under_score
- Removes unused method from Queue daemon
2015-11-27 07:35:15 -05:00
ebd26ddd98 - Silences fsockopen errors 2015-11-27 07:35:14 -05:00
924aa0439f - Minor regex changes
- Adds method to Env class that returns plugin activation status
- Prevents Supervisor from running if the plugin isn't activated
2015-11-27 07:35:13 -05:00
3124d6a61b - Fixes issue with ucwords() function on older PHP versions
- Updates Supervisor/Daemon to strip port from site_url() when it's
  running on localhost inside a virtual machine :)
2015-11-27 07:35:13 -05:00
149d031b52 - Adds session support
- Renames Queue to Daemon
- Updates router methods
2015-11-27 07:35:12 -05:00
fa96c4697d - Adds Queue router
- Updates logic for Queue and Supervisor
- #227
2015-11-27 07:35:12 -05:00
d46c9d5412 - Fixes issue with Supervisor when database tables do not exist 2015-11-27 07:35:11 -05:00
9ab8b1f0c5 added loading on wp users sync 2015-11-27 12:57:52 +01:00
9425ac1593 Merge pull request #244 from mailpoet/wp_users_segment
WP Users segment
2015-11-27 11:39:08 +01:00
4e32479609 Merge pull request #246 from mailpoet/import-fixes
Fixes import issues and updates code
2015-11-27 09:30:19 +01:00
7d95b38dc4 - Renames/refactors Import and Export classes/views/JS
- Updates Import and Export to ignore trashed subscribers
- Updates tests
Closes #245
2015-11-26 20:48:19 -05:00
17c83c5bd4 Merge pull request #242 from mailpoet/newsletter_templates
Newsletter template thumbnails
2015-11-26 21:26:23 +01:00
23bb2f111f Add a sample translatable template 2015-11-26 17:21:10 +02:00
4655b2c64c Switch template thumbnails to be generated in jpeg, not png 2015-11-26 17:20:34 +02:00
84fede11b8 removed dynamic lists from import 2015-11-26 16:02:53 +01:00
f37488fc0b Adds a Preview icon for previewing newsletter templates 2015-11-26 13:09:08 +02:00
20e2e03982 Hide non default segments on some pages
- added getPublic() method on Segment model
- filter out dynamic lists from add/move/remove segment in subscribers
2015-11-26 11:32:10 +01:00
a5d96f1534 WP Users list
- refactored and fixed listing issues (related to Segments)
- removed bulk actions from segments
- added synchronize methods for WP users
- Update action in segments only for WP Users list
- added "type" column to segments (default, wp_users, dynamic...)
- added "status" column to subscriber_segment table (useful soon)
2015-11-25 18:31:57 +01:00
a516eb1a95 Add overlay and newsletter thumbnail preview 2015-11-25 16:44:18 +02:00
6dd8270bec WP Users list
- migration for filters & segment_filter tables
- models for new tables
- update of Listing JSX to allow for conditional display of item actions
2015-11-24 17:12:14 +01:00
981cbd579f Fix clickable labels for editor settings views 2015-11-24 16:51:27 +02:00
52a0aae10f Add template thumbnail generation and display 2015-11-24 16:50:57 +02:00
ebca4257a6 Version bump: 0.0.5 2015-11-21 00:39:11 +01:00
c3944095bc Install npm deps in build command. 2015-11-21 00:36:12 +01:00
a1445d1b6a Vagrant Container and new build process. 2015-11-21 00:28:33 +01:00
e62c24879b Merge pull request #239 from mailpoet/revert-238-queue
Revert "Queue"
2015-11-20 23:51:19 +01:00
00f06ea202 Revert "Queue" 2015-11-20 23:51:02 +01:00
32ca24ce38 Merge pull request #238 from mailpoet/queue
Queue
2015-11-20 23:02:30 +01:00
44e3adb422 Merge pull request #235 from mailpoet/alc
Editor: ALC fixes
2015-11-20 23:00:47 +01:00
a1346ebecc Merge pull request #237 from mailpoet/changelog
welcome/update pages display logic
2015-11-20 23:00:30 +01:00
25b51d0446 - Adds queue management and supervisor. Issue #227 2015-11-20 16:20:54 -05:00
556a170903 - Bootstraps queue 2015-11-20 16:20:35 -05:00
7ac386a8e2 fixed welcome/update pages display logic 2015-11-20 13:37:52 +01:00
d91b55ec52 Include static sticky-kit library, patch it, fix sticky editor sidebar 2015-11-20 14:16:51 +02:00
786fbc36a2 Change post types to plural labels, move Posts/ALC UI elements 2015-11-19 19:08:48 +02:00
160e8b7a12 Merge pull request #232 from mailpoet/export-rename
Rename BootstrapMenu.php to BootStrapMenu.php
2015-11-19 15:01:24 +01:00
0b1f85da09 Rename BootstrapMenu.php to BootStrapMenu.php
- Renames BootStrapMenu class to use proper camelCase
2015-11-19 08:57:37 -05:00
fbc6f54ddc Merge pull request #230 from mailpoet/export
Export
2015-11-19 10:35:27 +01:00
a603e97b8c Merge pull request #229 from mailpoet/editor_posts
Editor: Posts widget
2015-11-19 10:31:31 +01:00
0875b627b6 Merge pull request #231 from mailpoet/newsletters_select_all_fix
fixed missing messages in newsletters listing
2015-11-19 10:27:45 +01:00
035784ece0 fixed missing messages in newsletters listing 2015-11-19 10:24:27 +01:00
aa93c7349f - Rebases master
- Adds tests for all Export class methods
Closes #221
2015-11-18 22:14:48 -05:00
82cf4a28fd - Updates export tests 2015-11-18 14:42:28 -05:00
832e5ef342 - Fixed minor import issue wrt to status not being set 2015-11-18 14:42:27 -05:00
e3de3a123a - Corrects exported subscriber count
- Properly exports subscribers not in any list
- Adds test for export class constructor method
Resolves #221
2015-11-18 14:42:27 -05:00
db91590159 Merge pull request #228 from mailpoet/welcome_redirect
Welcome page redirection
2015-11-18 14:33:48 +01:00
28c61fca0b Newsletters listing
- fixed listing actions on newsletters
2015-11-18 14:04:34 +01:00
e62a8c2ec5 Implement Becs' changes to Posts widget and post rendering 2015-11-18 14:44:57 +02:00
fdbd1245e3 Redirect to welcome or update page 2015-11-17 20:11:03 +01:00
0eef46db57 Fix post transformation to take titleIsLink option into account 2015-11-17 16:55:55 +02:00
080ae88a04 Fix Posts block tests 2015-11-17 16:31:57 +02:00
225be9f3cd Starting welcome page redirection 2015-11-17 13:43:25 +01:00
c9a42ebb76 Add auto-refresh of Posts block contents on option change 2015-11-17 14:40:39 +02:00
ae9b3df92d Merge pull request #223 from mailpoet/build
Fix JS lib inclusion in build step
2015-11-16 13:38:33 +01:00
63a08ebb55 Fix inclusion of js/lib/tinymce by disabling removal of node_modules/ 2015-11-16 14:27:29 +02:00
145 changed files with 10338 additions and 6234 deletions

3
.gitignore vendored
View File

@ -15,4 +15,5 @@ temp
wysija-newsletters.zip
tests/javascript/testBundles
assets/css/*.css
assets/js/*.js
assets/js/*.js
.vagrant

View File

@ -47,6 +47,15 @@ class RoboFile extends \Robo\Tasks {
->run();
}
function watchCss() {
$css_files = $this->rsearch('assets/css/src/', array('styl'));
$this->taskWatch()
->monitor($css_files, function() {
$this->compileCss();
})
->run();
}
function watchJs() {
$this->_exec('./node_modules/webpack/bin/webpack.js --watch');
}

View File

@ -13,4 +13,5 @@
@require 'breadcrumb'
@require 'form'
@require 'settings'
@require 'settings'
@require 'progress_bar'

View File

@ -18,6 +18,41 @@
height: 150px
margin-right: 15px
float: left
overflow: hidden
position: relative
img
min-width: 150px
min-height: 150px
height: auto
width: 110%
position: relative
top: 0
left: 50%
transform: translate(-50%, 0%)
.mailpoet_overlay
position: absolute
top: 0
left: 0
right: 0
bottom: 0
background-color: rgba(255, 255, 255, 0.0)
opacity: 0
transition: all 200ms cubic-bezier(0.420, 0.000, 0.580, 1.000) /* ease-in-out */
&:hover
background-color: rgba(255, 255, 255, 0.7)
opacity: 1
&::after
content: " "
position: absolute
top: 0
left: 0
bottom: 0
right: 0
background: url(../img/preview_magnifying_glass.svg) no-repeat center center
.mailpoet_boxes .mailpoet_description
float:left

View File

@ -26,3 +26,25 @@ textarea.regular-text
@media screen and (max-width: 782px)
.select2-container
width: 100% !important
// progress bars
progress-border-radius = 5px
progress-background = #efefef
progress-foreground = #69b1e9
progress
background-color: progress-background;
height: 2em
border: 0
width: 100%
progress::-webkit-progress-bar
background-color: progress-background;
progress::-webkit-progress-value
background-color: progress-foreground
border-radius: progress-border-radius
progress::-moz-progress-bar
background-color: progress-foreground
border-radius: progress-border-radius

View File

@ -159,23 +159,6 @@ body.mailpoet_modal_opened
margin: 0
text-align: right
.mailpoet_button
padding: 3px 15px
border: 1px solid #444
font-weight: normal
cursor: pointer
background-color: #222
color: #cfcfcf
font-size: 1em
.mailpoet_button:hover
background-color: #00aacc
color: #fff
.mailpoet_button:active
background-color: #00ccff
color: #fff
@media screen and (max-width: 782px)
#mailpoet_modal_overlay.mailpoet_panel_overlay
top: 46px

View File

@ -76,13 +76,24 @@ $layer-selector-width = 30px
display: inline-block
padding: 2px
vertical-align: top
animation-background-color()
.mailpoet_tool
padding: 0
.mailpoet_delete_block_activate
max-width: 100%
display: inline-block
opacity: 1
animation-fade-in-and-scale-horizontally()
.mailpoet_delete_block_confirm,
.mailpoet_delete_block_cancel
display: none
max-width: 0
opacity: 0
overflow: hidden
display: inline-block
animation-fade-in-and-scale-horizontally()
.mailpoet_delete_block_activated
width: auto
@ -93,11 +104,14 @@ $layer-selector-width = 30px
height: auto
.mailpoet_delete_block_activate
display: none
overflow: hidden
max-width: 0
opacity: 0
.mailpoet_delete_block_confirm,
.mailpoet_delete_block_cancel
display: inline-block
max-width: 100%
opacity: 1
.mailpoet_delete_block_confirm
color: $warning-text-color

View File

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

View File

@ -32,7 +32,7 @@ $widget-icon-width = 30px
.mailpoet_region_content
max-height: 2000px
transition: max-height 0.2s ease
transition: max-height 300ms ease
padding: 0 20px
margin-top: 12px

View File

@ -18,3 +18,6 @@
& > .mailpoet_block
width: 100%
.mailpoet_automated_latest_content_display_options
animation-slide-open-downwards()

View File

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

View File

@ -79,3 +79,4 @@ $three-column-width = ($newsletter-width / 3) - (2 * $column-margin)
box-shadow(inset 1px 2px 1px $primary-inactive-color)
color: #656565
border-radius(3px)
animation-background-color()

View File

@ -19,6 +19,10 @@ $divider-hover-border-color = $primary-active-color
width: 100%
border: 1px solid transparent
.mailpoet_active_divider_style
border: 1px solid $active-divider-border-color
background: $active-divider-background-color
.mailpoet_field_divider_style:hover
border: 1px solid $divider-hover-border-color

View File

@ -1,15 +1,12 @@
.mailpoet_posts_block
box-shadow(none)
padding-left: 0
padding-right: 0
& > .mailpoet_content
font-size: 1em
text-align: center
background-color: $primary-active-color
margin: 20px 0
padding: 15px
box-shadow(inset 1px 2px 1px $primary-inset-shadow-color)
color: $white-color
border-radius(3px)
.mailpoet_posts_block_posts
overflow: auto
& > .mailpoet_block
width: 100%
.mailpoet_post_selection_filter_row
margin-top: 5px
@ -18,7 +15,11 @@
.mailpoet_posts_categories_and_tags
width: 100%
.mailpoet_settings_posts_show_display_options
.mailpoet_settings_posts_display_options
.mailpoet_settings_posts_selection
animation-slide-open-downwards()
.mailpoet_settings_posts_show_display_options,
.mailpoet_settings_posts_show_post_selection
display: block
margin-top: 10px

View File

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

View File

@ -6,6 +6,7 @@
@require 'mixins/border-radius'
@require 'mixins/box-shadow'
@require 'mixins/filter-shadow'
@require 'mixins/transitions'
@require 'variables'
@require 'common'

View File

@ -0,0 +1,29 @@
.mailpoet_progress
background-color: #efefef
height: 25px
padding: 0
width: 100%
margin: 0
border-radius: 5px
position: relative
.mailpoet_progress_label
position: absolute
width: 100%
text-align: center
display: inline-block
margin: 2px 0 0 0
.mailpoet_progress_bar
position: absolute
display: inline-block
height: 100%
border-radius: 3px
box-shadow: 0 1px 0 rgba(255, 255, 255, .5) inset
background-color: #34c2e3
background-image: linear-gradient(top, #34c2e3, darken(#34c2e3, 20%))
.mailpoet_progress_complete
.mailpoet_progress_bar
background-color: #fecf23
background-image: linear-gradient(top, #fecf23, #fd9215)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="47.002px" height="38.003px" viewBox="0 0 47.002 38.003" enable-background="new 0 0 47.002 38.003" xml:space="preserve">
<path fill-rule="evenodd" clip-rule="evenodd" fill="#565656" d="M46.328,36.365c-1.188,1.725-3.553,2.158-5.273,0.962L25.479,26.52
c-1.104-0.763-1.674-2.007-1.631-3.257c-2.006,2.157-4.642,3.606-7.594,4.145c-3.626,0.663-7.288-0.13-10.311-2.227
c-3.024-2.098-5.054-5.253-5.714-8.887c-0.661-3.636,0.127-7.31,2.221-10.344c4.325-6.264,12.927-7.834,19.177-3.5
c5.672,3.938,7.486,11.412,4.537,17.443c1.152-0.486,2.519-0.392,3.627,0.377l15.58,10.808C47.09,32.274,47.52,34.641,46.328,36.365
z M23.235,12.09c-0.459-2.534-1.874-4.734-3.982-6.196C14.897,2.87,8.896,3.963,5.878,8.331c-3.014,4.373-1.922,10.388,2.435,13.408
c4.356,3.025,10.356,1.932,13.374-2.438C23.146,17.187,23.696,14.625,23.235,12.09z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

98
assets/js/src/cron.jsx Normal file
View File

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

View File

@ -23,7 +23,9 @@ function(
name={ this.props.field.name }
id={ 'field_'+this.props.field.name }
value={ this.props.item[this.props.field.name] }
onChange={ this.props.onValueChange }>
onChange={ this.props.onValueChange }
{...this.props.field.validation}
>
{options}
</select>
);

View File

@ -25,13 +25,10 @@ function(
(this.props.item !== undefined && prevProps.item !== undefined)
&& (this.props.item.id !== prevProps.item.id)
) {
jQuery('#'+this.refs.select.id).select2(
'val',
this.props.item[this.props.field.name]
);
jQuery('#'+this.refs.select.id)
.val(this.props.item[this.props.field.name])
.trigger('change');
}
this.setupSelect2();
},
setupSelect2: function() {
if(
@ -53,7 +50,19 @@ function(
}
});
var hasRemoved = false;
select2.on('select2:unselecting', function(e) {
hasRemoved = true;
});
select2.on('select2:opening', function(e) {
if(hasRemoved === true) {
hasRemoved = false;
e.preventDefault();
}
});
select2.on('change', this.handleChange);
select2.select2(
'val',
this.props.item[this.props.field.name]
@ -77,7 +86,7 @@ function(
handleChange: function(e) {
if(this.props.onValueChange !== undefined) {
if(this.props.field.multiple) {
value = jQuery('#'+this.refs.select.id).select2('val');
value = jQuery('#'+this.refs.select.id).val();
} else {
value = e.target.value;
}
@ -88,7 +97,6 @@ function(
}
});
}
return true;
},
render: function() {
var options = this.state.items.map(function(item, index) {
@ -114,8 +122,8 @@ function(
ref="select"
placeholder={ this.props.field.placeholder }
multiple={ this.props.field.multiple }
onChange={ this.handleChange }
defaultValue={ default_value }
{...this.props.field.validation}
>{ options }</select>
);
}

View File

@ -6,6 +6,8 @@ function(
) {
var FormFieldText = React.createClass({
render: function() {
var value = this.props.item[this.props.field.name];
if(!value) { value = null; }
return (
<input
type="text"
@ -17,10 +19,12 @@ function(
}
name={ this.props.field.name }
id={ 'field_'+this.props.field.name }
value={ this.props.item[this.props.field.name] }
value={ value }
placeholder={ this.props.field.placeholder }
defaultValue={ this.props.field.defaultValue }
onChange={ this.props.onValueChange } />
onChange={ this.props.onValueChange }
{...this.props.field.validation}
/>
);
}
});

View File

@ -15,7 +15,9 @@ function(
value={ this.props.item[this.props.field.name] }
placeholder={ this.props.field.placeholder }
defaultValue={ this.props.field.defaultValue }
onChange={ this.props.onValueChange } />
onChange={ this.props.onValueChange }
{...this.props.field.validation}
/>
);
}
});

View File

@ -68,12 +68,25 @@ define(
handleSubmit: function(e) {
e.preventDefault();
// handle validation
if(this.props.isValid !== undefined) {
if(this.props.isValid() === false) {
return;
}
}
this.setState({ loading: true });
// only get values from displayed fields
item = {};
var item = {};
this.props.fields.map(function(field) {
item[field.name] = this.state.item[field.name];
if(field['fields'] !== undefined) {
field.fields.map(function(subfield) {
item[subfield.name] = this.state.item[subfield.name];
}.bind(this));
} else {
item[field.name] = this.state.item[field.name];
}
}.bind(this));
// set id if specified

View File

@ -105,6 +105,9 @@ const item_actions = [
refresh();
});
}
},
{
name: 'trash'
}
];

View File

@ -79,27 +79,48 @@ define(
if(custom_actions.length > 0) {
item_actions = custom_actions.map(function(action, index) {
if(action.refresh) {
if(action.display !== undefined) {
if(action.display(this.props.item) === false) {
return;
}
}
if(action.name === 'trash') {
return (
<span key={ 'action-'+index } className="trash">
{(index > 0) ? ' | ' : ''}
<a
href="javascript:;"
onClick={ this.handleTrashItem.bind(
null,
this.props.item.id
) }>
Trash
</a>
</span>
);
} else if(action.refresh) {
return (
<span
onClick={ this.props.onRefreshItems }
key={ 'action-'+index } className={ action.name }>
{(index > 0) ? ' | ' : ''}
{ action.link(this.props.item) }
{(index < (custom_actions.length - 1)) ? ' | ' : ''}
</span>
);
} else if(action.link) {
return (
<span
key={ 'action-'+index } className={ action.name }>
{(index > 0) ? ' | ' : ''}
{ action.link(this.props.item) }
{(index < (custom_actions.length - 1)) ? ' | ' : ''}
</span>
);
} else {
return (
<span
key={ 'action-'+index } className={ action.name }>
{(index > 0) ? ' | ' : ''}
<a href="javascript:;" onClick={
(action.onClick !== undefined)
? action.onClick.bind(null,
@ -108,7 +129,6 @@ define(
)
: false
}>{ action.label }</a>
{(index < (custom_actions.length - 1)) ? ' | ' : ''}
</span>
);
}
@ -158,17 +178,6 @@ define(
<div>
<div className="row-actions">
{ item_actions }
{ ' | ' }
<span className="trash">
<a
href="javascript:;"
onClick={ this.handleTrashItem.bind(
null,
this.props.item.id
) }>
Trash
</a>
</span>
</div>
<button
onClick={ this.handleToggleItem.bind(null, this.props.item.id) }
@ -388,6 +397,12 @@ define(
if(this.isMounted()) {
const params = this.props.params || {}
this.initWithParams(params)
if(this.props.auto_refresh) {
jQuery(document).on('heartbeat-tick.mailpoet', function(e, data) {
this.getItems();
}.bind(this));
}
}
},
componentWillReceiveProps: function(nextProps) {
@ -637,7 +652,7 @@ define(
// bulk actions
var bulk_actions = this.props.bulk_actions || [];
if(this.state.group === 'trash') {
if(this.state.group === 'trash' && bulk_actions.length > 0) {
bulk_actions = [
{
name: 'restore',

View File

@ -226,11 +226,11 @@ define([
toggleDisplayOptions: function(event) {
var el = this.$('.mailpoet_automated_latest_content_display_options'),
showControl = this.$('.mailpoet_automated_latest_content_show_display_options');
if (el.hasClass('mailpoet_hidden')) {
el.removeClass('mailpoet_hidden');
if (el.hasClass('mailpoet_closed')) {
el.removeClass('mailpoet_closed');
showControl.addClass('mailpoet_hidden');
} else {
el.addClass('mailpoet_hidden');
el.addClass('mailpoet_closed');
showControl.removeClass('mailpoet_hidden');
}
},
@ -306,7 +306,7 @@ define([
_.each(postTypes, function(type) {
select.append(jQuery('<option>', {
value: type.name,
text: type.labels.singular_name,
text: type.label,
}));
});
select.val(selectedValue);

View File

@ -48,6 +48,7 @@ define([
},
modelEvents: {
'change': 'render',
'delete': 'deleteBlock',
},
events: {
"mouseenter": "showTools",
@ -88,7 +89,9 @@ define([
this.$el.addClass('mailpoet_editor_view_' + this.cid);
},
initialize: function() {
this.on('showSettings', this.showSettings);
this.on('showSettings', this.showSettings, this);
this.on('dom:refresh', this.showBlock, this);
this._isFirstRender = true;
},
showTools: function(_event) {
if (!this.showingToolsDisabled) {
@ -121,6 +124,34 @@ define([
return newModel;
};
},
showBlock: function() {
if (this._isFirstRender) {
this.transitionIn();
this._isFirstRender = false;
}
},
deleteBlock: function() {
this.transitionOut().done(function() {
this.model.destroy();
}.bind(this));
},
transitionIn: function() {
return this._transition('mailpoet_block_transition_in');
},
transitionOut: function() {
return this._transition('mailpoet_block_transition_out');
},
_transition: function(className) {
var that = this,
promise = jQuery.Deferred();
this.$el.addClass(className);
this.$el.one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd animationend', function() {
that.$el.removeClass('mailpoet_block_transition_out');
promise.resolve();
});
return promise;
},
});
Module.BlockToolsView = AugmentedView.extend({
@ -168,9 +199,9 @@ define([
},
deleteBlock: function(event) {
event.preventDefault();
this.model.destroy();
this.model.trigger('delete');
return false;
}
},
});
Module.BlockSettingsView = Marionette.LayoutView.extend({

View File

@ -42,16 +42,12 @@ define([
Module.ButtonBlockView = base.BlockView.extend({
className: "mailpoet_block mailpoet_button_block mailpoet_droppable_block",
getTemplate: function() { return templates.buttonBlock; },
modelEvents: {
'change': 'render',
},
onDragSubstituteBy: function() { return Module.ButtonWidgetView; },
initialize: function() {
base.BlockView.prototype.initialize.apply(this, arguments);
var that = this;
// Listen for attempts to change all dividers in one go
this._replaceButtonStylesHandler = function(data) { that.model.set(data); };
this._replaceButtonStylesHandler = function(data) { this.model.set(data); }.bind(this);
App.getChannel().on('replaceAllButtonStyles', this._replaceButtonStylesHandler);
},
onRender: function() {

View File

@ -75,7 +75,8 @@ define([
getEmptyView: function() { return Module.ContainerBlockEmptyView; },
emptyViewOptions: function() { return { renderOptions: this.renderOptions }; },
modelEvents: {
'change': 'render'
'change': 'render',
'delete': 'deleteBlock',
},
events: {
"mouseenter": "showTools",
@ -136,6 +137,8 @@ define([
},
initialize: function(options) {
this.renderOptions = _.defaults(options.renderOptions || {}, {});
this.on('dom:refresh', this.showBlock, this);
this._isFirstRender = true;
},
// Determines which view type should be used for a child
getChildView: function(model) {
@ -236,6 +239,34 @@ define([
return newModel;
};
},
showBlock: function() {
if (this._isFirstRender) {
this.transitionIn();
this._isFirstRender = false;
}
},
deleteBlock: function() {
this.transitionOut().done(function() {
this.model.destroy();
}.bind(this));
},
transitionIn: function() {
return this._transition('mailpoet_block_transition_in');
},
transitionOut: function() {
return this._transition('mailpoet_block_transition_out');
},
_transition: function(className) {
var that = this,
promise = jQuery.Deferred();
this.$el.addClass(className);
this.$el.one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd animationend', function() {
that.$el.removeClass('mailpoet_block_transition_out');
promise.resolve();
});
return promise;
},
});
Module.ContainerBlockEmptyView = Marionette.ItemView.extend({

View File

@ -31,7 +31,7 @@ define([
base = BaseBlock;
Module.PostsBlockModel = base.BlockModel.extend({
stale: ['_selectedPosts', '_availablePosts'],
stale: ['_selectedPosts', '_availablePosts', '_transformedPosts'],
defaults: function() {
return this._getDefaults({
type: 'posts',
@ -63,6 +63,7 @@ define([
divider: {},
_selectedPosts: [],
_availablePosts: [],
_transformedPosts: new (App.getBlockTypeModel('container'))(),
}, App.getConfig().get('blockDefaults.posts'));
},
relations: function() {
@ -71,15 +72,26 @@ define([
divider: App.getBlockTypeModel('divider'),
_selectedPosts: Backbone.Collection,
_availablePosts: Backbone.Collection,
_transformedPosts: App.getBlockTypeModel('container'),
};
},
initialize: function() {
var that = this;
var that = this,
POST_REFRESH_DELAY_MS = 500,
refreshAvailablePosts = _.debounce(this.fetchAvailablePosts.bind(this), POST_REFRESH_DELAY_MS),
refreshTransformedPosts = _.debounce(this._refreshTransformedPosts.bind(this), POST_REFRESH_DELAY_MS);
// Attach Radio.Requests API primarily for highlighting
_.extend(this, Radio.Requests);
this.fetchAvailablePosts();
this.on('change:amount change:contentType change:terms change:inclusionType change:postStatus change:search change:sortBy', this._scheduleFetchAvailablePosts, this);
this.on('change:amount change:contentType change:terms change:inclusionType change:postStatus change:search change:sortBy', refreshAvailablePosts);
this.listenTo(this.get('_selectedPosts'), 'add remove reset', refreshTransformedPosts);
this.on('change:displayType change:titleFormat change:titlePosition change:titleAlignment change:titleIsLink change:imagePadded change:showAuthor change:authorPrecededBy change:showCategories change:categoriesPrecededBy change:readMoreType change:readMoreText change:showDivider', refreshTransformedPosts);
this.listenTo(this.get('readMoreButton'), 'change', refreshTransformedPosts);
this.listenTo(this.get('divider'), 'change', refreshTransformedPosts);
this.on('insertSelectedPosts', this._insertSelectedPosts, this);
},
fetchAvailablePosts: function() {
@ -93,20 +105,23 @@ define([
console.log('Posts fetchPosts error', arguments);
});
},
/**
* Batch more changes during a specific time, instead of fetching
* ALC posts on each model change
*/
_scheduleFetchAvailablePosts: function() {
var timeout = 500,
that = this;
if (this._fetchPostsTimer !== undefined) {
clearTimeout(this._fetchPostsTimer);
_refreshTransformedPosts: function() {
var that = this,
data = this.toJSON();
data.posts = this.get('_selectedPosts').pluck('ID');
if (data.posts.length === 0) {
this.get('_transformedPosts.blocks').reset();
return;
}
this._fetchPostsTimer = setTimeout(function() {
that.fetchAvailablePosts();
that._fetchPostsTimer = undefined;
}, timeout);
WordpressComponent.getTransformedPosts(data).done(function(posts) {
console.log('Transformed posts fetched', arguments);
that.get('_transformedPosts').get('blocks').reset(posts, {parse: true});
}).fail(function() {
console.log('Posts _refreshTransformedPosts error', arguments);
});
},
_insertSelectedPosts: function() {
var that = this,
@ -131,6 +146,9 @@ define([
className: "mailpoet_block mailpoet_posts_block mailpoet_droppable_block",
getTemplate: function() { return templates.postsBlock; },
modelEvents: {},
regions: _.extend({
postsRegion: '.mailpoet_posts_block_posts',
}, base.BlockView.prototype.regions),
onDragSubstituteBy: function() { return Module.PostsWidgetView; },
initialize: function() {
base.BlockView.prototype.initialize.apply(this, arguments);
@ -142,6 +160,13 @@ define([
this.toolsRegion.show(this.toolsView);
}
this.trigger('showSettings');
var ContainerView = App.getBlockTypeView('container'),
renderOptions = {
disableTextEditor: true,
disableDragAndDrop: true,
};
this.postsRegion.show(new ContainerView({ model: this.model.get('_transformedPosts'), renderOptions: renderOptions }));
},
notifyAboutSelf: function() {
return this;
@ -197,8 +222,8 @@ define([
},
switchToDisplayOptions: function() {
// Switch content view
this.$('.mailpoet_settings_posts_selection').addClass('mailpoet_hidden');
this.$('.mailpoet_settings_posts_display_options').removeClass('mailpoet_hidden');
this.$('.mailpoet_settings_posts_selection').addClass('mailpoet_closed');
this.$('.mailpoet_settings_posts_display_options').removeClass('mailpoet_closed');
// Switch controls
this.$('.mailpoet_settings_posts_show_display_options').addClass('mailpoet_hidden');
@ -206,8 +231,8 @@ define([
},
switchToPostSelection: function() {
// Switch content view
this.$('.mailpoet_settings_posts_display_options').addClass('mailpoet_hidden');
this.$('.mailpoet_settings_posts_selection').removeClass('mailpoet_hidden');
this.$('.mailpoet_settings_posts_display_options').addClass('mailpoet_closed');
this.$('.mailpoet_settings_posts_selection').removeClass('mailpoet_closed');
// Switch controls
this.$('.mailpoet_settings_posts_show_post_selection').addClass('mailpoet_hidden');
@ -216,6 +241,7 @@ define([
insertPosts: function() {
this.model.trigger('insertSelectedPosts');
this.model.destroy();
this.close();
},
});
@ -307,11 +333,6 @@ define([
},
}).trigger( 'change' );
},
onBeforeDestroy: function() {
base.BlockSettingsView.prototype.onBeforeDestroy.apply(this, arguments);
// Force close select2 if it hasn't closed yet
this.$('.mailpoet_posts_categories_and_tags').select2('close');
},
changeField: function(field, event) {
this.model.set(field, jQuery(event.target).val());
},
@ -323,7 +344,7 @@ define([
_.each(postTypes, function(type) {
select.append(jQuery('<option>', {
value: type.name,
text: type.labels.singular_name,
text: type.label,
}));
});
select.val(selectedValue);

View File

@ -103,7 +103,8 @@ define([
getTemplate: function() { return templates.socialBlock; },
childViewContainer: '.mailpoet_social',
modelEvents: {
'change': 'render'
'change': 'render',
'delete': 'deleteBlock',
},
events: {
"mouseover": "showTools",
@ -145,6 +146,10 @@ define([
arguments[0].collection = arguments[0].model.get('icons');
Marionette.CompositeView.apply(this, arguments);
},
initialize: function() {
this.on('dom:refresh', this.showBlock, this);
this._isFirstRender = true;
},
// Determines which view type should be used for a child
childView: SocialIconView,
templateHelpers: function() {
@ -194,6 +199,34 @@ define([
this.regionManager.destroy();
_.extend(this, this._buildRegions(this.regions));
},
showBlock: function() {
if (this._isFirstRender) {
this.transitionIn();
this._isFirstRender = false;
}
},
deleteBlock: function() {
this.transitionOut().done(function() {
this.model.destroy();
}.bind(this));
},
transitionIn: function() {
return this._transition('mailpoet_block_transition_in');
},
transitionOut: function() {
return this._transition('mailpoet_block_transition_out');
},
_transition: function(className) {
var that = this,
promise = jQuery.Deferred();
this.$el.addClass(className);
this.$el.one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd animationend', function() {
that.$el.removeClass('mailpoet_block_transition_out');
promise.resolve();
});
return promise;
},
});
Module.SocialBlockToolsView = base.BlockToolsView.extend({

View File

@ -6,8 +6,9 @@ define([
'backbone.marionette',
'jquery',
'blob',
'filesaver'
], function(App, MailPoet, Notice, Backbone, Marionette, jQuery, Blob, FileSaver) {
'filesaver',
'html2canvas'
], function(App, MailPoet, Notice, Backbone, Marionette, jQuery, Blob, FileSaver, html2canvas) {
"use strict";
@ -46,26 +47,52 @@ define([
});
};
Module.getThumbnail = function(element, options) {
return html2canvas(element, options || {});
};
Module.saveTemplate = function(options) {
return MailPoet.Ajax.post({
endpoint: 'newsletterTemplates',
action: 'save',
data: _.extend(options || {}, {
var that = this,
promise = jQuery.Deferred();
promise.then(function(thumbnail) {
var data = _.extend(options || {}, {
thumbnail: thumbnail.toDataURL('image/jpeg'),
body: App.getBody(),
}),
});
return MailPoet.Ajax.post({
endpoint: 'newsletterTemplates',
action: 'save',
data: data,
});
});
Module.getThumbnail(
jQuery('#mailpoet_editor_content > .mailpoet_block').get(0)
).then(function(thumbnail) {
promise.resolve(thumbnail);
});
return promise;
};
Module.exportTemplate = function(options) {
var data = _.extend(options || {}, {
body: App.getBody(),
});
var blob = new Blob(
[JSON.stringify(data)],
{ type: 'application/json;charset=utf-8' }
);
var that = this;
return Module.getThumbnail(
jQuery('#mailpoet_editor_content > .mailpoet_block').get(0)
).then(function(thumbnail) {
var data = _.extend(options || {}, {
thumbnail: thumbnail.toDataURL('image/jpeg'),
body: App.getBody(),
});
var blob = new Blob(
[JSON.stringify(data)],
{ type: 'application/json;charset=utf-8' }
);
FileSaver.saveAs(blob, 'template.json');
FileSaver.saveAs(blob, 'template.json');
});
};
Module.SaveView = Marionette.LayoutView.extend({

View File

@ -21,6 +21,10 @@ define(
label: 'Subject',
sortable: true
},
{
name: 'status',
label: 'Status'
},
{
name: 'segments',
label: 'Lists'
@ -112,12 +116,101 @@ define(
</a>
);
}
},
{
name: 'trash'
}
];
var NewsletterList = React.createClass({
renderItem: function(newsletter, actions) {
pauseSending: function(item) {
MailPoet.Ajax.post({
endpoint: 'sendingQueue',
action: 'pause',
data: item.id
}).done(function() {
jQuery('#resume_'+item.id).show();
jQuery('#pause_'+item.id).hide();
});
},
resumeSending: function(item) {
MailPoet.Ajax.post({
endpoint: 'sendingQueue',
action: 'resume',
data: item.id
}).done(function() {
jQuery('#pause_'+item.id).show();
jQuery('#resume_'+item.id).hide();
});
},
renderStatus: function(item) {
if(item.queue === null) {
return (
<span>Not sent yet.</span>
);
} else {
var progressClasses = classNames(
'mailpoet_progress',
{ 'mailpoet_progress_complete': item.queue.status === 'completed'}
);
// calculate percentage done
var percentage = Math.round(
(item.queue.count_processed * 100) / (item.queue.count_total)
);
var label = false;
if(item.queue.status === 'completed') {
label = (
<span>
Sent to {
item.queue.count_processed - item.queue.count_failed
} out of { item.queue.count_total }.
</span>
);
} else {
label = (
<span>
{ item.queue.count_processed } / { item.queue.count_total }
&nbsp;&nbsp;
<a
id={ 'resume_'+item.id }
className="button"
style={{ display: (item.queue.status === 'paused') ? 'inline-block': 'none' }}
href="javascript:;"
onClick={ this.resumeSending.bind(null, item) }
>Resume</a>
<a
id={ 'pause_'+item.id }
className="button mailpoet_pause"
style={{ display: (item.queue.status === null) ? 'inline-block': 'none' }}
href="javascript:;"
onClick={ this.pauseSending.bind(null, item) }
>Pause</a>
</span>
);
}
return (
<div>
<div className={ progressClasses }>
<span
className="mailpoet_progress_bar"
style={ { width: percentage + "%"} }
></span>
<span className="mailpoet_progress_label">
{ percentage + "%" }
</span>
</div>
<p style={{ textAlign:'center' }}>
{ label }
</p>
</div>
);
}
},
renderItem: function(newsletter, actions) {
var rowClasses = classNames(
'manage-column',
'column-primary',
@ -138,6 +231,9 @@ define(
</strong>
{ actions }
</td>
<td className="column" data-colname="Lists">
{ this.renderStatus(newsletter) }
</td>
<td className="column" data-colname="Lists">
{ segments }
</td>
@ -164,7 +260,8 @@ define(
columns={columns}
bulk_actions={ bulk_actions }
item_actions={ item_actions }
messages={ messages } />
messages={ messages }
auto_refresh={ true } />
</div>
);
}

View File

@ -37,6 +37,9 @@ define(
multiple: true,
filter: function(segment) {
return !!(!segment.deleted_at);
},
validation: {
'data-parsley-required': true
}
},
{
@ -45,17 +48,24 @@ define(
tip: "Name & email of yourself or your company.",
fields: [
{
name: 'from_name',
name: 'sender_name',
type: 'text',
placeholder: 'John Doe',
defaultValue: settings.from_name
defaultValue: (settings.sender !== undefined) ? settings.sender.name : '',
validation: {
'data-parsley-required': true
}
},
{
name: 'from_email',
name: 'sender_address',
type: 'text',
placeholder: 'john.doe@email.com',
defaultValue: settings.from_address
},
defaultValue: (settings.sender !== undefined) ? settings.sender.address : '',
validation: {
'data-parsley-required': true,
'data-parsley-type': 'email'
}
}
]
},
{
@ -68,20 +78,25 @@ define(
{
name: 'reply_to_name',
type: 'text',
placeholder: 'John Doe'
placeholder: 'John Doe',
defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.name : '',
},
{
name: 'reply_to_email',
name: 'reply_to_address',
type: 'text',
placeholder: 'john.doe@email.com'
placeholder: 'john.doe@email.com',
defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.address : ''
},
]
}
];
var messages = {
updated: function() {
MailPoet.Notice.success('The newsletter has been updated!');
onUpdate: function() {
MailPoet.Notice.success('Newsletter successfully updated!');
},
onCreate: function() {
MailPoet.Notice.success('Newsletter successfully added!');
}
};
@ -90,34 +105,43 @@ define(
Router.History
],
handleSend: function() {
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'send',
data: {
id: this.props.params.id,
newsletter: jQuery('#mailpoet_newsletter').serializeObject(),
segments: jQuery('#mailpoet_segments').val()
}
}).done(function(response) {
if(response === true) {
this.history.pushState(null, '/');
if(jQuery('#mailpoet_newsletter').parsley().validate() === true) {
MailPoet.Ajax.post({
endpoint: 'sendingQueue',
action: 'add',
data: {
newsletter_id: this.props.params.id,
segments: jQuery('#mailpoet_segments').val()
}
}).done(function(response) {
if(response.result === true) {
this.history.pushState(null, '/');
MailPoet.Notice.success(
'The newsletter has been sent!'
);
} else {
if(response.errors) {
MailPoet.Notice.error(
response.errors.join("<br />")
MailPoet.Notice.success(
'The newsletter is being sent...'
);
} else {
MailPoet.Notice.error(
'An error occurred while trying to send. '+
'<a href="?page=mailpoet-settings">Check your settings.</a>'
);
if(response.errors) {
MailPoet.Notice.error(
response.errors.join("<br />")
);
} else {
MailPoet.Notice.error(
'An error occurred while trying to send. '+
'<a href="?page=mailpoet-settings">Check your settings.</a>'
);
}
}
}
}.bind(this));
}.bind(this));
}
},
componentDidMount: function() {
if(this.isMounted()) {
jQuery('#mailpoet_newsletter').parsley();
}
},
isValid: function() {
return (jQuery('#mailpoet_newsletter').parsley().validate());
},
render: function() {
return (
@ -131,7 +155,8 @@ define(
endpoint="newsletters"
fields={ fields }
params={ this.props.params }
messages={ messages }>
messages={ messages }
isValid={ this.isValid }>
<p className="submit">
<input

View File

@ -99,7 +99,7 @@ define(
"MailPoet's Guide",
description:
"This is the standard template that comes with MailPoet.",
readonly: true
readonly: "1"
}
]
}
@ -150,6 +150,13 @@ define(
this.setState({ loading: false });
}
},
handleShowTemplate: function(template) {
MailPoet.Modal.popup({
title: template.name,
template: '<img src="{{ thumbnail }}" />',
data: template,
});
},
handleTemplateImport: function() {
this.getTemplates();
},
@ -164,11 +171,22 @@ define(
Delete
</a>
</div>
);
), thumbnail = '';
if (typeof template.thumbnail === 'string'
&& template.thumbnail.length > 0) {
thumbnail = (
<a href="javascript:;" onClick={this.handleShowTemplate.bind(null, template)}>
<img src={ template.thumbnail } />
<div className="mailpoet_overlay"></div>
</a>
);
}
return (
<li key={ 'template-'+index }>
<div className="mailpoet_thumbnail">
{ thumbnail }
</div>
<div className="mailpoet_description">
@ -192,7 +210,7 @@ define(
Preview
</a>
</div>
{ (template.readonly) ? false : deleteLink }
{ (template.readonly === "1") ? false : deleteLink }
</li>
);
}.bind(this));

View File

@ -121,6 +121,32 @@ const item_actions = [
);
refresh();
});
},
display: function(segment) {
return (segment.type !== 'wp_users');
}
},
{
name: 'synchronize_segment',
label: 'Update',
className: 'update',
onClick: function(item, refresh) {
MailPoet.Modal.loading(true);
MailPoet.Ajax.post({
endpoint: 'segments',
action: 'synchronize'
}).done(function(response) {
MailPoet.Modal.loading(false);
if(response === true) {
MailPoet.Notice.success(
('List "%$1s" has been synchronized.').replace('%$1s', item.name)
);
refresh();
}
});
},
display: function(segment) {
return (segment.type === 'wp_users');
}
},
{
@ -130,15 +156,16 @@ const item_actions = [
<a href={ item.subscribers_url }>View subscribers</a>
);
}
},
{
name: 'trash',
display: function(segment) {
return (segment.type !== 'wp_users');
}
}
];
const bulk_actions = [
{
name: 'trash',
label: 'Trash',
onSuccess: messages.onTrash
}
];
const SegmentList = React.createClass({
@ -148,7 +175,6 @@ const SegmentList = React.createClass({
'column-primary',
'has-row-actions'
);
return (
<div>
<td className={ rowClasses }>

View File

@ -15,10 +15,10 @@ define(
MailPoet.Router = new (Backbone.Router.extend({
routes: {
'mta(/:method)': 'sendingMethod',
'mta(/:group)': 'sendingMethodGroup',
'(:tab)': 'tabs',
},
sendingMethod: function(method) {
sendingMethodGroup: function(group) {
// display mta tab
this.tabs('mta');
@ -30,13 +30,13 @@ define(
// hide "save settings" button
jQuery('.mailpoet_settings_submit').hide();
if(method === null) {
if(group === null) {
// show sending methods
jQuery('.mailpoet_sending_methods').fadeIn();
} else {
// hide DKIM option when using MailPoet's API
jQuery('#mailpoet_mta_dkim')[
(method === 'mailpoet')
(group === 'mailpoet')
? 'hide'
: 'show'
]();
@ -45,7 +45,7 @@ define(
jQuery('.mailpoet_sending_methods').hide();
// display selected sending method's settings
jQuery('.mailpoet_sending_method[data-method="'+ method +'"]').show();
jQuery('.mailpoet_sending_method[data-group="'+ group +'"]').show();
jQuery('#mailpoet_sending_method_setup').fadeIn();
}
},

View File

@ -20,6 +20,7 @@ define(
return;
}
jQuery(document).ready(function () {
jQuery('input[name="select_method"]').attr('checked', false);
// configure router
router = new (Backbone.Router.extend({
routes: {
@ -299,11 +300,11 @@ define(
var test,
cleanEmail =
email
// left/right trim spaces, punctuation (e.g., " 'email@email.com'; ")
// right trim non-printable characters (e.g., "email@email.com<6F>")
// left/right trim spaces, punctuation (e.g., " 'email@email.com'; ")
// right trim non-printable characters (e.g., "email@email.com<6F>")
.replace(/^["';.,\s]+|[^\x20-\x7E]+$|["';.,_\s]+$/g, '')
// remove spaces (e.g., "email @ email . com")
// remove urlencoded characters
// remove spaces (e.g., "email @ email . com")
// remove urlencoded characters
.replace(/\s+|%\d+|,+/g, '')
.toLowerCase();
@ -1000,7 +1001,6 @@ define(
}
function toggleNextStepButton(condition) {
console.log(condition);
var disabled = 'button-disabled';
if (condition === 'on') {
nextStepButton.removeClass(disabled);

View File

@ -103,7 +103,9 @@ const bulk_actions = [
id: 'move_to_segment',
endpoint: 'segments',
filter: function(segment) {
return !!(!segment.deleted_at);
return !!(
!segment.deleted_at && segment.type === 'default'
);
}
};
@ -132,7 +134,9 @@ const bulk_actions = [
id: 'add_to_segment',
endpoint: 'segments',
filter: function(segment) {
return !!(!segment.deleted_at);
return !!(
!segment.deleted_at && segment.type === 'default'
);
}
};
@ -159,7 +163,12 @@ const bulk_actions = [
onSelect: function() {
let field = {
id: 'remove_from_segment',
endpoint: 'segments'
endpoint: 'segments',
filter: function(segment) {
return !!(
segment.type === 'default'
);
}
};
return (
@ -206,6 +215,21 @@ const bulk_actions = [
}
];
const item_actions = [
{
name: 'edit',
label: 'Edit',
link: function(item) {
return (
<Link to={ `/edit/${item.id}` }>Edit</Link>
);
}
},
{
name: 'trash'
}
];
const SubscriberList = React.createClass({
renderItem: function(subscriber, actions) {
let row_classes = classNames(
@ -295,6 +319,7 @@ const SubscriberList = React.createClass({
onRenderItem={ this.renderItem }
columns={ columns }
bulk_actions={ bulk_actions }
item_actions={ item_actions }
messages={ messages }
onGetItems={ this.onGetItems }
/>

View File

@ -0,0 +1,264 @@
// Generated by CoffeeScript 1.9.2
/**
@license Sticky-kit v1.1.2 | WTFPL | Leaf Corcoran 2015 | http://leafo.net
*/
(function() {
var $, win;
$ = this.jQuery || window.jQuery;
win = $(window);
$.fn.stick_in_parent = function(opts) {
var doc, elm, enable_bottoming, fn, i, inner_scrolling, len, manual_spacer, offset_top, outer_width, parent_selector, recalc_every, sticky_class;
if (opts == null) {
opts = {};
}
sticky_class = opts.sticky_class, inner_scrolling = opts.inner_scrolling, recalc_every = opts.recalc_every, parent_selector = opts.parent, offset_top = opts.offset_top, manual_spacer = opts.spacer, enable_bottoming = opts.bottoming;
if (offset_top == null) {
offset_top = 0;
}
if (parent_selector == null) {
parent_selector = void 0;
}
if (inner_scrolling == null) {
inner_scrolling = true;
}
if (sticky_class == null) {
sticky_class = "is_stuck";
}
doc = $(document);
if (enable_bottoming == null) {
enable_bottoming = true;
}
outer_width = function(el) {
var _el, computed, w;
if (window.getComputedStyle) {
_el = el[0];
computed = window.getComputedStyle(el[0]);
w = parseFloat(computed.getPropertyValue("width")) + parseFloat(computed.getPropertyValue("margin-left")) + parseFloat(computed.getPropertyValue("margin-right"));
if (computed.getPropertyValue("box-sizing") !== "border-box") {
w += parseFloat(computed.getPropertyValue("border-left-width")) + parseFloat(computed.getPropertyValue("border-right-width")) + parseFloat(computed.getPropertyValue("padding-left")) + parseFloat(computed.getPropertyValue("padding-right"));
}
return w;
} else {
return el.outerWidth(true);
}
};
fn = function(elm, padding_bottom, parent_top, parent_height, top, height, el_float, detached) {
var bottomed, detach, fixed, last_pos, last_scroll_height, offset, parent, recalc, recalc_and_tick, recalc_counter, spacer, tick;
if (elm.data("sticky_kit")) {
return;
}
elm.data("sticky_kit", true);
last_scroll_height = doc.height();
parent = elm.parent();
if (parent_selector != null) {
parent = parent.closest(parent_selector);
}
if (!parent.length) {
throw "failed to find stick parent";
}
fixed = false;
bottomed = false;
spacer = manual_spacer != null ? manual_spacer && elm.closest(manual_spacer) : $("<div />");
if (spacer) {
spacer.css('position', elm.css('position'));
}
recalc = function() {
var border_top, padding_top, restore;
if (detached) {
return;
}
last_scroll_height = doc.height();
border_top = parseInt(parent.css("border-top-width"), 10);
padding_top = parseInt(parent.css("padding-top"), 10);
padding_bottom = parseInt(parent.css("padding-bottom"), 10);
parent_top = parent.offset().top + border_top + padding_top;
parent_height = parent.height();
if (fixed) {
fixed = false;
bottomed = false;
if (manual_spacer == null) {
elm.insertAfter(spacer);
spacer.detach();
}
elm.css({
position: "",
top: "",
width: "",
bottom: ""
}).removeClass(sticky_class);
restore = true;
}
top = elm.offset().top - (parseInt(elm.css("margin-top"), 10) || 0) - offset_top;
height = elm.outerHeight(true);
el_float = elm.css("float");
if (spacer) {
spacer.css({
width: outer_width(elm),
height: height,
display: elm.css("display"),
"vertical-align": elm.css("vertical-align"),
"float": el_float
});
}
if (restore) {
return tick();
}
};
recalc();
last_pos = void 0;
offset = offset_top;
recalc_counter = recalc_every;
tick = function() {
var css, delta, recalced, scroll, will_bottom, win_height;
if (detached) {
return;
}
recalced = false;
if (recalc_counter != null) {
recalc_counter -= 1;
if (recalc_counter <= 0) {
recalc_counter = recalc_every;
recalc();
recalced = true;
}
}
if (!recalced && doc.height() !== last_scroll_height) {
recalc();
recalced = true;
}
scroll = win.scrollTop();
if (last_pos != null) {
delta = scroll - last_pos;
}
last_pos = scroll;
if (fixed) {
if (enable_bottoming) {
will_bottom = scroll + height + offset > parent_height + parent_top;
if (bottomed && !will_bottom) {
bottomed = false;
elm.css({
position: "fixed",
bottom: "",
top: offset
}).trigger("sticky_kit:unbottom");
}
}
if (scroll < top) {
fixed = false;
offset = offset_top;
if (manual_spacer == null) {
if (el_float === "left" || el_float === "right") {
elm.insertAfter(spacer);
}
spacer.detach();
}
css = {
position: "",
width: "",
top: ""
};
elm.css(css).removeClass(sticky_class).trigger("sticky_kit:unstick");
}
if (inner_scrolling) {
win_height = win.height();
if (height + offset_top > win_height) {
if (!bottomed) {
offset -= delta;
offset = Math.max(win_height - height, offset);
offset = Math.min(offset_top, offset);
if (fixed) {
elm.css({
top: offset + "px"
});
}
}
}
}
} else {
if (scroll > top) {
fixed = true;
css = {
position: "fixed",
top: offset
};
css.width = elm.css("box-sizing") === "border-box" ? elm.outerWidth() + "px" : elm.width() + "px";
elm.css(css).addClass(sticky_class);
if (manual_spacer == null) {
elm.after(spacer);
if (el_float === "left" || el_float === "right") {
spacer.append(elm);
}
}
elm.trigger("sticky_kit:stick");
}
}
if (fixed && enable_bottoming) {
if (will_bottom == null) {
will_bottom = scroll + height + offset > parent_height + parent_top;
}
if (!bottomed && will_bottom) {
bottomed = true;
if (parent.css("position") === "static") {
parent.css({
position: "relative"
});
}
return elm.css({
position: "absolute",
bottom: padding_bottom,
top: "auto"
}).trigger("sticky_kit:bottom");
}
}
};
recalc_and_tick = function() {
recalc();
return tick();
};
detach = function() {
detached = true;
win.off("touchmove", tick);
win.off("scroll", tick);
win.off("resize", recalc_and_tick);
$(document.body).off("sticky_kit:recalc", recalc_and_tick);
elm.off("sticky_kit:detach", detach);
elm.removeData("sticky_kit");
elm.css({
position: "",
bottom: "",
top: "",
width: ""
});
parent.position("position", "");
if (fixed) {
if (manual_spacer == null) {
if (el_float === "left" || el_float === "right") {
elm.insertAfter(spacer);
}
spacer.remove();
}
return elm.removeClass(sticky_class);
}
};
win.on("touchmove", tick);
win.on("scroll", tick);
win.on("resize", recalc_and_tick);
$(document.body).on("sticky_kit:recalc", recalc_and_tick);
elm.on("sticky_kit:detach", detach);
return setTimeout(tick, 0);
};
for (i = 0, len = this.length; i < len; i++) {
elm = this[i];
fn($(elm));
}
return this;
};
}).call(this);

6
build
View File

@ -7,12 +7,10 @@ rm wysija-newsletters.zip;
mkdir wysija-newsletters;
# Production assets.
npm install;
./do compile:all;
# Production libraries.
rm -rf vendor;
rm -rf node_modules;
rm composer.lock;
./composer.phar install --no-dev;
# Copy release folders.
@ -38,6 +36,4 @@ zip -r wysija-newsletters.zip wysija-newsletters;
rm -rf wysija-newsletters;
# Reinstall dev dependencies.
rm composer.lock;
./composer.phar install;
./do install;

View File

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

468
composer.lock generated
View File

@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "7d7ef94b6e40ac2b2d594e5832d7e16d",
"content-hash": "2e70c335edf7429df0794ebf49e2f210",
"hash": "4720dce62e4a6a7bf4d3ba3944b9c2b9",
"content-hash": "748470f0803c52a798a4ecd1bb8a93b9",
"packages": [
{
"name": "cerdic/css-tidy",
@ -158,6 +158,97 @@
],
"time": "2014-09-23 10:49:36"
},
{
"name": "mtdowling/cron-expression",
"version": "v1.0.4",
"source": {
"type": "git",
"url": "https://github.com/mtdowling/cron-expression.git",
"reference": "fd92e883195e5dfa77720b1868cf084b08be4412"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mtdowling/cron-expression/zipball/fd92e883195e5dfa77720b1868cf084b08be4412",
"reference": "fd92e883195e5dfa77720b1868cf084b08be4412",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
},
"require-dev": {
"phpunit/phpunit": "4.*"
},
"type": "library",
"autoload": {
"psr-0": {
"Cron": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due",
"keywords": [
"cron",
"schedule"
],
"time": "2015-01-11 23:07:46"
},
{
"name": "nesbot/carbon",
"version": "1.21.0",
"source": {
"type": "git",
"url": "https://github.com/briannesbitt/Carbon.git",
"reference": "7b08ec6f75791e130012f206e3f7b0e76e18e3d7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/7b08ec6f75791e130012f206e3f7b0e76e18e3d7",
"reference": "7b08ec6f75791e130012f206e3f7b0e76e18e3d7",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"symfony/translation": "~2.6|~3.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0|~5.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Carbon\\": "src/Carbon/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Brian Nesbitt",
"email": "brian@nesbot.com",
"homepage": "http://nesbot.com"
}
],
"description": "A simple API extension for DateTime.",
"homepage": "http://carbon.nesbot.com",
"keywords": [
"date",
"datetime",
"time"
],
"time": "2015-11-04 20:07:17"
},
{
"name": "phpmailer/phpmailer",
"version": "v5.2.14",
@ -403,6 +494,69 @@
],
"time": "2015-06-06 14:19:39"
},
{
"name": "symfony/translation",
"version": "v2.7.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
"reference": "e4ecb9c3ba1304eaf24de15c2d7a428101c1982f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/e4ecb9c3ba1304eaf24de15c2d7a428101c1982f",
"reference": "e4ecb9c3ba1304eaf24de15c2d7a428101c1982f",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"conflict": {
"symfony/config": "<2.7"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "~2.7",
"symfony/intl": "~2.4",
"symfony/yaml": "~2.2"
},
"suggest": {
"psr/log": "To use logging capability in translator",
"symfony/config": "",
"symfony/yaml": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Translation\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
"time": "2015-11-18 13:41:01"
},
{
"name": "tburry/pquery",
"version": "v1.1.0",
@ -783,16 +937,16 @@
},
{
"name": "guzzlehttp/guzzle",
"version": "6.1.0",
"version": "6.1.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "66fd14b4d0b8f2389eaf37c5458608c7cb793a81"
"reference": "c6851d6e48f63b69357cbfa55bca116448140e0c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/66fd14b4d0b8f2389eaf37c5458608c7cb793a81",
"reference": "66fd14b4d0b8f2389eaf37c5458608c7cb793a81",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/c6851d6e48f63b69357cbfa55bca116448140e0c",
"reference": "c6851d6e48f63b69357cbfa55bca116448140e0c",
"shasum": ""
},
"require": {
@ -841,7 +995,7 @@
"rest",
"web service"
],
"time": "2015-09-08 17:36:26"
"time": "2015-11-23 00:47:50"
},
{
"name": "guzzlehttp/promises",
@ -1961,16 +2115,16 @@
},
{
"name": "symfony/browser-kit",
"version": "v2.7.6",
"version": "v2.7.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
"reference": "07d664a052572ccc28eb2ab7dbbe82155b1ad367"
"reference": "bd28847ea2193916074c7b11d4fdd78570049694"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/07d664a052572ccc28eb2ab7dbbe82155b1ad367",
"reference": "07d664a052572ccc28eb2ab7dbbe82155b1ad367",
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/bd28847ea2193916074c7b11d4fdd78570049694",
"reference": "bd28847ea2193916074c7b11d4fdd78570049694",
"shasum": ""
},
"require": {
@ -1993,7 +2147,10 @@
"autoload": {
"psr-4": {
"Symfony\\Component\\BrowserKit\\": ""
}
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -2011,20 +2168,20 @@
],
"description": "Symfony BrowserKit Component",
"homepage": "https://symfony.com",
"time": "2015-10-23 14:47:27"
"time": "2015-11-02 20:20:53"
},
{
"name": "symfony/config",
"version": "v2.7.6",
"version": "v2.7.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
"reference": "831f88908b51b9ce945f5e6f402931d1ac544423"
"reference": "61973327bfb054f6f470de7be033a28b76c1dc20"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/config/zipball/831f88908b51b9ce945f5e6f402931d1ac544423",
"reference": "831f88908b51b9ce945f5e6f402931d1ac544423",
"url": "https://api.github.com/repos/symfony/config/zipball/61973327bfb054f6f470de7be033a28b76c1dc20",
"reference": "61973327bfb054f6f470de7be033a28b76c1dc20",
"shasum": ""
},
"require": {
@ -2040,7 +2197,10 @@
"autoload": {
"psr-4": {
"Symfony\\Component\\Config\\": ""
}
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -2058,20 +2218,20 @@
],
"description": "Symfony Config Component",
"homepage": "https://symfony.com",
"time": "2015-10-11 09:39:48"
"time": "2015-11-02 20:20:53"
},
{
"name": "symfony/console",
"version": "v2.7.6",
"version": "v2.7.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "5efd632294c8320ea52492db22292ff853a43766"
"reference": "16bb1cb86df43c90931df65f529e7ebd79636750"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/5efd632294c8320ea52492db22292ff853a43766",
"reference": "5efd632294c8320ea52492db22292ff853a43766",
"url": "https://api.github.com/repos/symfony/console/zipball/16bb1cb86df43c90931df65f529e7ebd79636750",
"reference": "16bb1cb86df43c90931df65f529e7ebd79636750",
"shasum": ""
},
"require": {
@ -2096,7 +2256,10 @@
"autoload": {
"psr-4": {
"Symfony\\Component\\Console\\": ""
}
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -2114,20 +2277,20 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2015-10-20 14:38:46"
"time": "2015-11-18 09:54:26"
},
{
"name": "symfony/css-selector",
"version": "v2.7.6",
"version": "v2.7.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
"reference": "e1b865b26be4a56d22a8dee398375044a80c865b"
"reference": "abb47717fb88aebd9437da2fc8bb01a50a36679f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/e1b865b26be4a56d22a8dee398375044a80c865b",
"reference": "e1b865b26be4a56d22a8dee398375044a80c865b",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/abb47717fb88aebd9437da2fc8bb01a50a36679f",
"reference": "abb47717fb88aebd9437da2fc8bb01a50a36679f",
"shasum": ""
},
"require": {
@ -2142,7 +2305,10 @@
"autoload": {
"psr-4": {
"Symfony\\Component\\CssSelector\\": ""
}
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -2164,20 +2330,20 @@
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
"time": "2015-10-11 09:39:48"
"time": "2015-10-30 20:10:21"
},
{
"name": "symfony/dom-crawler",
"version": "v2.7.6",
"version": "v2.7.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
"reference": "5fef7d8b80d8f9992df99d8ee283f420484c9612"
"reference": "b33593cbfe1d81b50d48353f338aca76a08658d8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/5fef7d8b80d8f9992df99d8ee283f420484c9612",
"reference": "5fef7d8b80d8f9992df99d8ee283f420484c9612",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b33593cbfe1d81b50d48353f338aca76a08658d8",
"reference": "b33593cbfe1d81b50d48353f338aca76a08658d8",
"shasum": ""
},
"require": {
@ -2198,7 +2364,10 @@
"autoload": {
"psr-4": {
"Symfony\\Component\\DomCrawler\\": ""
}
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -2216,20 +2385,20 @@
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
"time": "2015-10-11 09:39:48"
"time": "2015-11-02 20:20:53"
},
{
"name": "symfony/event-dispatcher",
"version": "v2.7.6",
"version": "v2.7.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "87a5db5ea887763fa3a31a5471b512ff1596d9b8"
"reference": "7e2f9c31645680026c2372edf66f863fc7757af5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/87a5db5ea887763fa3a31a5471b512ff1596d9b8",
"reference": "87a5db5ea887763fa3a31a5471b512ff1596d9b8",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7e2f9c31645680026c2372edf66f863fc7757af5",
"reference": "7e2f9c31645680026c2372edf66f863fc7757af5",
"shasum": ""
},
"require": {
@ -2255,7 +2424,10 @@
"autoload": {
"psr-4": {
"Symfony\\Component\\EventDispatcher\\": ""
}
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -2273,20 +2445,20 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"time": "2015-10-11 09:39:48"
"time": "2015-10-30 20:10:21"
},
{
"name": "symfony/filesystem",
"version": "v2.7.6",
"version": "v2.7.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "56fd6df73be859323ff97418d97edc1d756df6df"
"reference": "8e173509d7fdbbba3cf34d6d072f2073c0210c1d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/56fd6df73be859323ff97418d97edc1d756df6df",
"reference": "56fd6df73be859323ff97418d97edc1d756df6df",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/8e173509d7fdbbba3cf34d6d072f2073c0210c1d",
"reference": "8e173509d7fdbbba3cf34d6d072f2073c0210c1d",
"shasum": ""
},
"require": {
@ -2301,7 +2473,10 @@
"autoload": {
"psr-4": {
"Symfony\\Component\\Filesystem\\": ""
}
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -2319,20 +2494,20 @@
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
"time": "2015-10-18 20:23:18"
"time": "2015-11-18 13:41:01"
},
{
"name": "symfony/finder",
"version": "v2.7.6",
"version": "v2.7.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "2ffb4e9598db3c48eb6d0ae73b04bbf09280c59d"
"reference": "a06a0c0ff7db3736a50d530c908cca547bf13da9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/2ffb4e9598db3c48eb6d0ae73b04bbf09280c59d",
"reference": "2ffb4e9598db3c48eb6d0ae73b04bbf09280c59d",
"url": "https://api.github.com/repos/symfony/finder/zipball/a06a0c0ff7db3736a50d530c908cca547bf13da9",
"reference": "a06a0c0ff7db3736a50d530c908cca547bf13da9",
"shasum": ""
},
"require": {
@ -2347,7 +2522,10 @@
"autoload": {
"psr-4": {
"Symfony\\Component\\Finder\\": ""
}
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -2365,20 +2543,20 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
"time": "2015-10-11 09:39:48"
"time": "2015-10-30 20:10:21"
},
{
"name": "symfony/form",
"version": "v2.7.6",
"version": "v2.7.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/form.git",
"reference": "b93fcb816bec2b8470ea9d54e4b6658b2461b83c"
"reference": "0a2c2ce0d4bd3c50bb0ae4e75ac27e5274c25e81"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/form/zipball/b93fcb816bec2b8470ea9d54e4b6658b2461b83c",
"reference": "b93fcb816bec2b8470ea9d54e4b6658b2461b83c",
"url": "https://api.github.com/repos/symfony/form/zipball/0a2c2ce0d4bd3c50bb0ae4e75ac27e5274c25e81",
"reference": "0a2c2ce0d4bd3c50bb0ae4e75ac27e5274c25e81",
"shasum": ""
},
"require": {
@ -2416,7 +2594,10 @@
"autoload": {
"psr-4": {
"Symfony\\Component\\Form\\": ""
}
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -2434,20 +2615,20 @@
],
"description": "Symfony Form Component",
"homepage": "https://symfony.com",
"time": "2015-10-27 15:38:06"
"time": "2015-11-23 10:34:14"
},
{
"name": "symfony/intl",
"version": "v2.7.6",
"version": "v2.7.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/intl.git",
"reference": "330f52a996749eb6a2fdc1506c7a4868e070d678"
"reference": "6c6c3aa69f68aff72e48a9bfc11f24680e23eb2d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/intl/zipball/330f52a996749eb6a2fdc1506c7a4868e070d678",
"reference": "330f52a996749eb6a2fdc1506c7a4868e070d678",
"url": "https://api.github.com/repos/symfony/intl/zipball/6c6c3aa69f68aff72e48a9bfc11f24680e23eb2d",
"reference": "6c6c3aa69f68aff72e48a9bfc11f24680e23eb2d",
"shasum": ""
},
"require": {
@ -2474,6 +2655,9 @@
],
"files": [
"Resources/stubs/functions.php"
],
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
@ -2508,20 +2692,20 @@
"l10n",
"localization"
],
"time": "2015-10-11 09:39:48"
"time": "2015-11-18 13:41:01"
},
{
"name": "symfony/options-resolver",
"version": "v2.7.6",
"version": "v2.7.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
"reference": "85fd10e551677d3c9a4632def78b8ec4670b247d"
"reference": "d6b7d3452b4cfff89b642993e02fea7cc254530e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/85fd10e551677d3c9a4632def78b8ec4670b247d",
"reference": "85fd10e551677d3c9a4632def78b8ec4670b247d",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/d6b7d3452b4cfff89b642993e02fea7cc254530e",
"reference": "d6b7d3452b4cfff89b642993e02fea7cc254530e",
"shasum": ""
},
"require": {
@ -2536,7 +2720,10 @@
"autoload": {
"psr-4": {
"Symfony\\Component\\OptionsResolver\\": ""
}
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -2559,20 +2746,20 @@
"configuration",
"options"
],
"time": "2015-10-11 09:39:48"
"time": "2015-11-18 13:41:01"
},
{
"name": "symfony/process",
"version": "v2.7.6",
"version": "v2.7.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "4a959dd4e19c2c5d7512689413921e0a74386ec7"
"reference": "f6290983c8725d0afa29bdc3e5295879de3e58f5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/4a959dd4e19c2c5d7512689413921e0a74386ec7",
"reference": "4a959dd4e19c2c5d7512689413921e0a74386ec7",
"url": "https://api.github.com/repos/symfony/process/zipball/f6290983c8725d0afa29bdc3e5295879de3e58f5",
"reference": "f6290983c8725d0afa29bdc3e5295879de3e58f5",
"shasum": ""
},
"require": {
@ -2587,7 +2774,10 @@
"autoload": {
"psr-4": {
"Symfony\\Component\\Process\\": ""
}
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -2605,20 +2795,20 @@
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
"time": "2015-10-23 14:47:27"
"time": "2015-11-19 16:11:24"
},
{
"name": "symfony/property-access",
"version": "v2.7.6",
"version": "v2.7.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/property-access.git",
"reference": "368b784738fa932e6d86866038312b03e073a824"
"reference": "49d76463a54d8b3005fa58f3b8df41d0ae206eaa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/property-access/zipball/368b784738fa932e6d86866038312b03e073a824",
"reference": "368b784738fa932e6d86866038312b03e073a824",
"url": "https://api.github.com/repos/symfony/property-access/zipball/49d76463a54d8b3005fa58f3b8df41d0ae206eaa",
"reference": "49d76463a54d8b3005fa58f3b8df41d0ae206eaa",
"shasum": ""
},
"require": {
@ -2633,7 +2823,10 @@
"autoload": {
"psr-4": {
"Symfony\\Component\\PropertyAccess\\": ""
}
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -2662,20 +2855,20 @@
"property path",
"reflection"
],
"time": "2015-10-23 14:47:27"
"time": "2015-11-18 13:41:01"
},
{
"name": "symfony/routing",
"version": "v2.7.6",
"version": "v2.7.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
"reference": "f353e1f588679c3ec987624e6c617646bd01ba38"
"reference": "7450f6196711b124fb8b04a12286d01a0401ddfe"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/routing/zipball/f353e1f588679c3ec987624e6c617646bd01ba38",
"reference": "f353e1f588679c3ec987624e6c617646bd01ba38",
"url": "https://api.github.com/repos/symfony/routing/zipball/7450f6196711b124fb8b04a12286d01a0401ddfe",
"reference": "7450f6196711b124fb8b04a12286d01a0401ddfe",
"shasum": ""
},
"require": {
@ -2708,7 +2901,10 @@
"autoload": {
"psr-4": {
"Symfony\\Component\\Routing\\": ""
}
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -2732,85 +2928,25 @@
"uri",
"url"
],
"time": "2015-10-27 15:38:06"
},
{
"name": "symfony/translation",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
"reference": "6ccd9289ec1c71d01a49d83480de3b5293ce30c8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/6ccd9289ec1c71d01a49d83480de3b5293ce30c8",
"reference": "6ccd9289ec1c71d01a49d83480de3b5293ce30c8",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"conflict": {
"symfony/config": "<2.7"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "~2.7",
"symfony/intl": "~2.4",
"symfony/yaml": "~2.2"
},
"suggest": {
"psr/log": "To use logging capability in translator",
"symfony/config": "",
"symfony/yaml": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Translation\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
"time": "2015-10-27 15:38:06"
"time": "2015-11-18 13:41:01"
},
{
"name": "symfony/twig-bridge",
"version": "v2.7.6",
"version": "v2.7.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/twig-bridge.git",
"reference": "3dd44937b1e08af8c8f6b14850f4b9c4d1039c6f"
"reference": "7c491aa71af4320747f81ab0140eac4f0ad5509b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/twig-bridge/zipball/3dd44937b1e08af8c8f6b14850f4b9c4d1039c6f",
"reference": "3dd44937b1e08af8c8f6b14850f4b9c4d1039c6f",
"url": "https://api.github.com/repos/symfony/twig-bridge/zipball/7c491aa71af4320747f81ab0140eac4f0ad5509b",
"reference": "7c491aa71af4320747f81ab0140eac4f0ad5509b",
"shasum": ""
},
"require": {
"php": ">=5.3.9",
"twig/twig": "~1.20|~2.0"
"twig/twig": "~1.23|~2.0"
},
"require-dev": {
"symfony/asset": "~2.7",
@ -2852,7 +2988,10 @@
"autoload": {
"psr-4": {
"Symfony\\Bridge\\Twig\\": ""
}
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -2870,20 +3009,20 @@
],
"description": "Symfony Twig Bridge",
"homepage": "https://symfony.com",
"time": "2015-10-11 09:39:48"
"time": "2015-11-02 20:25:31"
},
{
"name": "symfony/yaml",
"version": "v2.7.6",
"version": "v2.7.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "eca9019c88fbe250164affd107bc8057771f3f4d"
"reference": "4cfcd7a9fceba662b3c036b7d9a91f6197af046c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/eca9019c88fbe250164affd107bc8057771f3f4d",
"reference": "eca9019c88fbe250164affd107bc8057771f3f4d",
"url": "https://api.github.com/repos/symfony/yaml/zipball/4cfcd7a9fceba662b3c036b7d9a91f6197af046c",
"reference": "4cfcd7a9fceba662b3c036b7d9a91f6197af046c",
"shasum": ""
},
"require": {
@ -2898,7 +3037,10 @@
"autoload": {
"psr-4": {
"Symfony\\Component\\Yaml\\": ""
}
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -2916,7 +3058,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2015-10-11 09:39:48"
"time": "2015-11-18 13:41:01"
},
{
"name": "twig/extensions",

61
lib/Config/Changelog.php Normal file
View File

@ -0,0 +1,61 @@
<?php
namespace MailPoet\Config;
use \MailPoet\Models\Setting;
class Changelog {
function init() {
$doing_ajax = (bool)(defined('DOING_AJAX') && DOING_AJAX);
// don't run any check when it's an ajax request
if($doing_ajax) {
return;
}
// don't run any check when we're not on our pages
if(
!(isset($_GET['page']))
or
(isset($_GET['page']) && strpos($_GET['page'], 'mailpoet') !== 0)
) {
return;
}
add_action(
'admin_init',
array($this, 'check')
);
}
function check() {
$version = Setting::getValue('version', null);
$redirect_url = null;
if($version === null) {
// new install
$redirect_url = admin_url('admin.php?page=mailpoet-welcome');
} else if($version !== Env::$version) {
// update
$redirect_url = admin_url('admin.php?page=mailpoet-update');
}
if($redirect_url !== null) {
// save version number
Setting::setValue('version', Env::$version);
global $wp;
$current_url = home_url(add_query_arg($wp->query_string, $wp->request));
if($redirect_url !== $current_url) {
wp_safe_redirect(
add_query_arg(
array(
'mailpoet_redirect' => urlencode($current_url)
),
$redirect_url
)
);
exit;
}
}
}
}

View File

@ -4,34 +4,37 @@ namespace MailPoet\Config;
if(!defined('ABSPATH')) exit;
class Env {
public static $version;
public static $plugin_name;
public static $file;
public static $path;
public static $views_path;
public static $assets_path;
public static $assets_url;
public static $temp_name;
public static $temp_path;
public static $languages_path;
public static $lib_path;
public static $plugin_prefix;
public static $db_prefix;
public static $db_source_name;
public static $db_host;
public static $db_socket;
public static $db_port;
public static $db_name;
public static $db_username;
public static $db_password;
public static $db_charset;
static $version;
static $plugin_name;
static $plugin_url;
static $plugin_path;
static $file;
static $path;
static $views_path;
static $assets_path;
static $assets_url;
static $temp_name;
static $temp_path;
static $languages_path;
static $lib_path;
static $plugin_prefix;
static $db_prefix;
static $db_source_name;
static $db_host;
static $db_socket;
static $db_port;
static $db_name;
static $db_username;
static $db_password;
static $db_charset;
public static function init($file, $version) {
static function init($file, $version) {
global $wpdb;
self::$version = $version;
self::$plugin_name = 'mailpoet';
self::$file = $file;
self::$path = dirname(self::$file);
self::$plugin_name = 'mailpoet';
self::$plugin_url = plugin_dir_url(__FILE__);
self::$views_path = self::$path . '/views';
self::$assets_path = self::$path . '/assets';
self::$assets_url = plugins_url('/assets', $file);
@ -44,11 +47,12 @@ class Env {
self::$db_host = DB_HOST;
self::$db_port = 3306;
self::$db_socket = false;
if (preg_match('/(?=:\d+$)/', DB_HOST)) {
if(preg_match('/(?=:\d+$)/', DB_HOST)) {
list(self::$db_host, self::$db_port) = explode(':', DB_HOST);
}
else if (preg_match('/:/', DB_HOST)) {
self::$db_socket = true;
} else {
if(preg_match('/:/', DB_HOST)) {
self::$db_socket = true;
}
}
self::$db_name = DB_NAME;
self::$db_username = DB_USER;
@ -57,7 +61,7 @@ class Env {
self::$db_source_name = self::dbSourceName(self::$db_host, self::$db_socket, self::$db_port);
}
private static function dbSourceName($host, $socket,$port) {
private static function dbSourceName($host, $socket, $port) {
$source_name = array(
(!$socket) ? 'mysql:host=' : 'mysql:unix_socket=',
$host,
@ -70,4 +74,19 @@ class Env {
);
return implode('', $source_name);
}
static function isPluginActivated() {
$activatesPlugins = get_option('active_plugins');
$isActivated = (
in_array(
sprintf('%s/%s.php', basename(self::$path), self::$plugin_name),
$activatesPlugins
) ||
in_array(
sprintf('%s/%s.php', explode('/', plugin_basename(__FILE__))[0], self::$plugin_name),
$activatesPlugins
)
);
return ($isActivated) ? true : false;
}
}

56
lib/Config/Hooks.php Normal file
View File

@ -0,0 +1,56 @@
<?php
namespace MailPoet\Config;
class Hooks {
function __construct() {
}
function init() {
// WP Users synchronization
add_action(
'user_register',
'\MailPoet\Segments\WP::synchronizeUser',
1
);
add_action(
'added_existing_user',
'\MailPoet\Segments\WP::synchronizeUser',
1
);
add_action(
'profile_update',
'\MailPoet\Segments\WP::synchronizeUser',
1
);
add_action(
'delete_user',
'\MailPoet\Segments\WP::synchronizeUser',
1
);
// multisite
add_action(
'deleted_user',
'\MailPoet\Segments\WP::synchronizeUser',
1
);
add_action(
'remove_user_from_blog',
'\MailPoet\Segments\WP::synchronizeUser',
1
);
add_filter(
'image_size_names_choose',
array(
$this,
'appendImageSizes'
)
);
}
function appendImageSizes($sizes) {
return array_merge($sizes, array(
'mailpoet_newsletter_max' => __('MailPoet Newsletter'),
));
}
}

View File

@ -2,6 +2,7 @@
namespace MailPoet\Config;
use MailPoet\Models;
use MailPoet\Cron\Supervisor;
use MailPoet\Router;
if(!defined('ABSPATH')) exit;
@ -24,6 +25,11 @@ class Initializer {
$this->setupWidget();
$this->setupAnalytics();
$this->setupPermissions();
$this->setupChangelog();
$this->setupPublicAPI();
$this->runQueueSupervisor();
$this->setupHooks();
$this->setupImages();
}
function setupDB() {
@ -32,7 +38,8 @@ class Initializer {
\ORM::configure('password', Env::$db_password);
\ORM::configure('logging', WP_DEBUG);
\ORM::configure('driver_options', array(
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET TIME_ZONE = "+00:00"'
));
$subscribers = Env::$db_prefix . 'subscribers';
@ -40,6 +47,8 @@ class Initializer {
$newsletters = Env::$db_prefix . 'newsletters';
$newsletter_templates = Env::$db_prefix . 'newsletter_templates';
$segments = Env::$db_prefix . 'segments';
$filters = Env::$db_prefix . 'filters';
$segment_filter = Env::$db_prefix . 'segment_filter';
$forms = Env::$db_prefix . 'forms';
$subscriber_segment = Env::$db_prefix . 'subscriber_segment';
$newsletter_segment = Env::$db_prefix . 'newsletter_segment';
@ -47,11 +56,15 @@ class Initializer {
$subscriber_custom_field = Env::$db_prefix . 'subscriber_custom_field';
$newsletter_option_fields = Env::$db_prefix . 'newsletter_option_fields';
$newsletter_option = Env::$db_prefix . 'newsletter_option';
$sending_queues = Env::$db_prefix . 'sending_queues';
$newsletter_statistics = Env::$db_prefix . 'newsletter_statistics';
define('MP_SUBSCRIBERS_TABLE', $subscribers);
define('MP_SETTINGS_TABLE', $settings);
define('MP_NEWSLETTERS_TABLE', $newsletters);
define('MP_SEGMENTS_TABLE', $segments);
define('MP_FILTERS_TABLE', $filters);
define('MP_SEGMENT_FILTER_TABLE', $segment_filter);
define('MP_FORMS_TABLE', $forms);
define('MP_SUBSCRIBER_SEGMENT_TABLE', $subscriber_segment);
define('MP_NEWSLETTER_TEMPLATES_TABLE', $newsletter_templates);
@ -60,6 +73,8 @@ class Initializer {
define('MP_SUBSCRIBER_CUSTOM_FIELD_TABLE', $subscriber_custom_field);
define('MP_NEWSLETTER_OPTION_FIELDS_TABLE', $newsletter_option_fields);
define('MP_NEWSLETTER_OPTION_TABLE', $newsletter_option);
define('MP_SENDING_QUEUE_TABLE', $sending_queues);
define('MP_NEWSLETTER_STATISTICS_TABLE', $newsletter_statistics);
}
function setupActivator() {
@ -104,4 +119,30 @@ class Initializer {
$permissions = new Permissions();
$permissions->init();
}
function setupChangelog() {
$changelog = new Changelog();
$changelog->init();
}
function setupHooks() {
$hooks = new Hooks();
$hooks->init();
}
function setupPublicAPI() {
$publicAPI = new PublicAPI();
$publicAPI->init();
}
function runQueueSupervisor() {
try {
$supervisor = new Supervisor();
$supervisor->checkDaemon();
} catch (\Exception $e) {}
}
function setupImages() {
add_image_size('mailpoet_newsletter_max', 1320);
}
}

View File

@ -1,17 +1,18 @@
<?php
namespace MailPoet\Config;
use MailPoet\Form\Block;
use MailPoet\Form\Renderer as FormRenderer;
use MailPoet\Models\CustomField;
use MailPoet\Models\Form;
use MailPoet\Models\Segment;
use MailPoet\Models\Setting;
use MailPoet\Settings\Charsets;
use MailPoet\Settings\Hosts;
use MailPoet\Settings\Pages;
use MailPoet\Subscribers\ImportExport\BootStrapMenu;
use \MailPoet\Models\Segment;
use \MailPoet\Models\Setting;
use \MailPoet\Models\Form;
use \MailPoet\Form\Block;
use \MailPoet\Form\Renderer as FormRenderer;
use \MailPoet\Settings\Hosts;
use \MailPoet\Settings\Pages;
use \MailPoet\Settings\Charsets;
use \MailPoet\Util\Permissions;
use \MailPoet\Util\DKIM;
use MailPoet\Util\DKIM;
use MailPoet\Util\Permissions;
if(!defined('ABSPATH')) exit;
@ -24,7 +25,10 @@ class Menu {
function init() {
add_action(
'admin_menu',
array($this, 'setup')
array(
$this,
'setup'
)
);
}
@ -34,7 +38,10 @@ class Menu {
'MailPoet',
'manage_options',
'mailpoet',
array($this, 'welcome'),
array(
$this,
'home'
),
$this->assets_url . '/img/menu_icon.png',
30
);
@ -44,7 +51,10 @@ class Menu {
__('Newsletters'),
'manage_options',
'mailpoet-newsletters',
array($this, 'newsletters')
array(
$this,
'newsletters'
)
);
add_submenu_page(
'mailpoet',
@ -52,7 +62,10 @@ class Menu {
__('Forms'),
'manage_options',
'mailpoet-forms',
array($this, 'forms')
array(
$this,
'forms'
)
);
add_submenu_page(
'mailpoet',
@ -60,7 +73,10 @@ class Menu {
__('Subscribers'),
'manage_options',
'mailpoet-subscribers',
array($this, 'subscribers')
array(
$this,
'subscribers'
)
);
add_submenu_page(
'mailpoet',
@ -68,7 +84,10 @@ class Menu {
__('Segments'),
'manage_options',
'mailpoet-segments',
array($this, 'segments')
array(
$this,
'segments'
)
);
add_submenu_page(
'mailpoet',
@ -76,7 +95,10 @@ class Menu {
__('Settings'),
'manage_options',
'mailpoet-settings',
array($this, 'settings')
array(
$this,
'settings'
)
);
add_submenu_page(
null,
@ -84,7 +106,10 @@ class Menu {
__('Import'),
'manage_options',
'mailpoet-import',
array($this, 'import')
array(
$this,
'import'
)
);
add_submenu_page(
null,
@ -92,33 +117,71 @@ class Menu {
__('Export'),
'manage_options',
'mailpoet-export',
array($this, 'export')
array(
$this,
'export'
)
);
// add_submenu_page(
// 'mailpoet',
// __('Newsletter editor'),
// __('Newsletter editor'),
// 'manage_options',
// 'mailpoet-newsletter-editor',
// array($this, 'newletterEditor')
// );
$this->registered_pages();
}
function registered_pages() {
global $_registered_pages;
$pages = array(
'mailpoet-welcome' => array($this, 'welcome'),
'mailpoet-form-editor' => array($this, 'formEditor'),
'mailpoet-newsletter-editor' => array($this, 'newletterEditor')
add_submenu_page(
null,
__('Welcome'),
__('Welcome'),
'manage_options',
'mailpoet-welcome',
array(
$this,
'welcome'
)
);
add_submenu_page(
null,
__('Update'),
__('Update'),
'manage_options',
'mailpoet-update',
array(
$this,
'update'
)
);
add_submenu_page(
null,
__('Form editor'),
__('Form editor'),
'manage_options',
'mailpoet-form-editor',
array(
$this,
'formEditor'
)
);
add_submenu_page(
null,
__('Newsletter editor'),
__('Newsletter editor'),
'manage_options',
'mailpoet-newsletter-editor',
array(
$this,
'newletterEditor'
)
);
add_submenu_page(
'mailpoet',
__('Cron'),
__('Cron'),
'manage_options',
'mailpoet-cron',
array(
$this,
'cron'
)
);
foreach($pages as $menu_slug => $callback) {
$hookname = get_plugin_page_hookname($menu_slug, null);
if(!empty($hookname)) {
add_action($hookname, $callback);
}
$_registered_pages[$hookname] = true;
}
}
function home() {
@ -127,12 +190,52 @@ class Menu {
}
function welcome() {
global $wp;
$current_url = home_url(add_query_arg($wp->query_string, $wp->request));
$redirect_url =
(!empty($_GET['mailpoet_redirect']))
? urldecode($_GET['mailpoet_redirect'])
: wp_get_referer();
if(
$redirect_url === $current_url
or
strpos($redirect_url, 'mailpoet') === false
) {
$redirect_url = admin_url('admin.php?page=mailpoet');
}
$data = array(
'settings' => Setting::getAll(),
'current_user' => wp_get_current_user()
'current_user' => wp_get_current_user(),
'redirect_url' => $redirect_url
);
echo $this->renderer->render('welcome.html', $data);
}
function update() {
global $wp;
$current_url = home_url(add_query_arg($wp->query_string, $wp->request));
$redirect_url =
(!empty($_GET['mailpoet_redirect']))
? urldecode($_GET['mailpoet_redirect'])
: wp_get_referer();
if(
$redirect_url === $current_url
or
strpos($redirect_url, 'mailpoet') === false
) {
$redirect_url = admin_url('admin.php?page=mailpoet');
}
$data = array(
'settings' => Setting::getAll(),
'current_user' => wp_get_current_user(),
'redirect_url' => $redirect_url
);
echo $this->renderer->render('welcome.html', $data);
echo $this->renderer->render('update.html', $data);
}
function settings() {
@ -155,7 +258,8 @@ class Menu {
$data = array(
'settings' => $settings,
'segments' => Segment::getPublished()->findArray(),
'segments' => Segment::getPublished()
->findArray(),
'pages' => Pages::getAll(),
'flags' => $this->_getFlags(),
'charsets' => Charsets::getAll(),
@ -183,11 +287,14 @@ class Menu {
// check if users can register
$flags['registration_enabled'] =
!(in_array($registration, array('none', 'blog')));
!(in_array($registration, array(
'none',
'blog'
)));
} else {
// check if users can register
$flags['registration_enabled'] =
(bool)get_option('users_can_register', false);
(bool) get_option('users_can_register', false);
}
return $flags;
@ -198,7 +305,7 @@ class Menu {
$data['segments'] = Segment::findArray();
echo $this->renderer->render('subscribers.html', $data);
echo $this->renderer->render('subscribers/subscribers.html', $data);
}
function segments() {
@ -219,17 +326,22 @@ class Menu {
$data = array();
$data['segments'] = Segment::findArray();
$settings = Setting::findArray();
$data['settings'] = array();
foreach($settings as $setting) {
$data['settings'][$setting['name']] = $setting['value'];
}
$data['settings'] = Setting::getAll();
$data['roles'] = $wp_roles->get_names();
echo $this->renderer->render('newsletters.html', $data);
}
function newletterEditor() {
$data = array();
$custom_fields = array_map(function($field) {
return array(
'text' => $field['name'],
'shortcode' => 'field:' . $field['id'],
);
}, CustomField::findArray());
$data = array(
'customFields' => $custom_fields,
);
wp_enqueue_media();
wp_enqueue_script('tinymce-wplink', includes_url('js/tinymce/plugins/wplink/plugin.js'));
wp_enqueue_style('editor', includes_url('css/editor.css'));
@ -239,17 +351,17 @@ class Menu {
function import() {
$import = new BootStrapMenu('import');
$data = $import->bootstrap();
echo $this->renderer->render('import.html', $data);
echo $this->renderer->render('subscribers/importExport/import.html', $data);
}
function export() {
$export = new BootStrapMenu('export');
$data = $export->bootstrap();
echo $this->renderer->render('export.html', $data);
echo $this->renderer->render('subscribers/importExport/export.html', $data);
}
function formEditor() {
$id = (isset($_GET['id']) ? (int)$_GET['id'] : 0);
$id = (isset($_GET['id']) ? (int) $_GET['id'] : 0);
$form = Form::findOne($id);
if($form !== false) {
$form = $form->asArray();
@ -258,7 +370,8 @@ class Menu {
$data = array(
'form' => $form,
'pages' => Pages::getAll(),
'segments' => Segment::getPublished()->findArray(),
'segments' => Segment::getPublic()
->findArray(),
'styles' => FormRenderer::getStyles($form),
'date_types' => Block\Date::getDateTypes(),
'date_formats' => Block\Date::getDateFormats()
@ -266,4 +379,10 @@ class Menu {
echo $this->renderer->render('form/editor.html', $data);
}
}
function cron() {
$daemon = new \MailPoet\Cron\BootStrapMenu();
$data['daemon'] = json_encode($daemon->bootstrap());
echo $this->renderer->render('cron.html', $data);
}
}

View File

@ -21,6 +21,8 @@ class Migrator {
'subscriber_custom_field',
'newsletter_option_fields',
'newsletter_option',
'sending_queues',
'newsletter_statistics',
'forms'
);
}
@ -50,6 +52,7 @@ class Migrator {
function subscribers() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'wp_user_id bigint(20) NULL,',
'first_name tinytext NOT NULL,',
'last_name tinytext NOT NULL,',
'email varchar(150) NOT NULL,',
@ -81,6 +84,10 @@ class Migrator {
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'subject varchar(250) NOT NULL,',
'type varchar(20) NOT NULL DEFAULT "standard",',
'sender_address varchar(150) NOT NULL,',
'sender_name varchar(150) NOT NULL,',
'reply_to_address varchar(150) NOT NULL,',
'reply_to_name varchar(150) NOT NULL,',
'preheader varchar(250) NOT NULL,',
'body longtext,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
@ -96,7 +103,9 @@ class Migrator {
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(250) NOT NULL,',
'description varchar(250) NOT NULL,',
'body longtext,',
'body LONGTEXT,',
'thumbnail LONGTEXT,',
'readonly TINYINT(1) DEFAULT 0',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)'
@ -106,14 +115,15 @@ class Migrator {
function segments() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,',
'description varchar(250) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name (name)'
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,',
'type varchar(90) NOT NULL DEFAULT "default",',
'description varchar(250) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name (name)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
@ -123,6 +133,7 @@ class Migrator {
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'subscriber_id mediumint(9) NOT NULL,',
'segment_id mediumint(9) NOT NULL,',
'status varchar(12) NOT NULL DEFAULT "subscribed",',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
@ -199,6 +210,41 @@ class Migrator {
return $this->sqlify(__FUNCTION__, $attributes);
}
function sending_queues() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'subscribers longtext,',
'status varchar(12) NULL DEFAULT NULL,',
'priority mediumint(9) NOT NULL DEFAULT 0,',
'count_total mediumint(9) NOT NULL DEFAULT 0,',
'count_processed mediumint(9) NOT NULL DEFAULT 0,',
'count_to_process mediumint(9) NOT NULL DEFAULT 0,',
'count_failed mediumint(9) NOT NULL DEFAULT 0,',
'processed_at TIMESTAMP NOT NULL DEFAULT 0,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'PRIMARY KEY (id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function newsletter_statistics() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'subscriber_id mediumint(9) NOT NULL,',
'queue_id mediumint(9) NOT NULL,',
'sent_at TIMESTAMP NOT NULL DEFAULT 0,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'PRIMARY KEY (id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function forms() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',

View File

@ -1,120 +1,166 @@
<?php
namespace MailPoet\Config;
if (!defined('ABSPATH')) exit;
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
class Populator {
function __construct() {
$this->prefix = Env::$db_prefix;
$this->models = array(
'newsletter_option_fields',
);
}
function up() {
global $wpdb;
$_this = $this;
$populate = function($model) use($_this, $wpdb) {
$fields = $_this->$model();
$table = $_this->prefix . $model;
array_map(function($field) use ($wpdb, $table) {
$column_conditions = array_map(function($key) use ($field) {
return $key . '=' . $field[$key];
}, $field);
if ($wpdb->get_var("SELECT COUNT(*) FROM " . $table . " WHERE " . implode(' AND ', $column_conditions)) === 0) {
$wpdb->insert(
$table,
$field
);
}
}, $fields);
};
array_map(array($this, 'populate'), $this->models);
}
function newsletter_option_fields() {
return array(
array(
'name' => 'event',
'newsletter_type' => 'welcome',
),
array(
'name' => 'segment',
'newsletter_type' => 'welcome',
),
array(
'name' => 'role',
'newsletter_type' => 'welcome',
),
array(
'name' => 'afterTimeNumber',
'newsletter_type' => 'welcome',
),
array(
'name' => 'afterTimeType',
'newsletter_type' => 'welcome',
),
array(
'name' => 'intervalType',
'newsletter_type' => 'notification',
),
array(
'name' => 'timeOfDay',
'newsletter_type' => 'notification',
),
array(
'name' => 'weekDay',
'newsletter_type' => 'notification',
),
array(
'name' => 'monthDay',
'newsletter_type' => 'notification',
),
array(
'name' => 'nthWeekDay',
'newsletter_type' => 'notification',
),
);
}
private function populate($model) {
$rows = $this->$model();
$table = $this->prefix . $model;
$_this = $this;
array_map(function($row) use ($_this, $table) {
if (!$_this->rowExists($table, $row)) {
$_this->insertRow($table, $row);
}
}, $rows);
}
private function rowExists($table, $columns) {
global $wpdb;
$conditions = array_map(function($key) use ($columns) {
return $key . '=%s';
}, array_keys($columns));
return $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM $table WHERE " . implode(' AND ', $conditions),
array_values($columns)
)) > 0;
}
private function insertRow($table, $row) {
global $wpdb;
return $wpdb->insert(
$table,
$row
);
}
}
<?php
namespace MailPoet\Config;
use MailPoet\Config\PopulatorData\Templates\FranksRoastHouseTemplate;
use MailPoet\Config\PopulatorData\Templates\BlankTemplate;
use \MailPoet\Models\Segment;
use \MailPoet\Segments\WP;
if (!defined('ABSPATH')) exit;
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
class Populator {
function __construct() {
$this->prefix = Env::$db_prefix;
$this->models = array(
'newsletter_option_fields',
'newsletter_templates',
);
}
function up() {
global $wpdb;
$_this = $this;
$populate = function($model) use($_this, $wpdb) {
$fields = $_this->$model();
$table = $_this->prefix . $model;
array_map(function($field) use ($wpdb, $table) {
$column_conditions = array_map(function($key) use ($field) {
return $key . '=' . $field[$key];
}, $field);
if ($wpdb->get_var("SELECT COUNT(*) FROM " . $table . " WHERE " . implode(' AND ', $column_conditions)) === 0) {
$wpdb->insert(
$table,
$field
);
}
}, $fields);
};
array_map(array($this, 'populate'), $this->models);
$this->createDefaultSegments();
}
private function createDefaultSegments() {
// WP Users segment
$wp_users_segment = Segment::getWPUsers();
if($wp_users_segment === false) {
// create the wp users list
$wp_users_segment = Segment::create();
$wp_users_segment->hydrate(array(
'name' => __('WordPress Users'),
'description' =>
__('The list containing all of your WordPress users.'),
'type' => 'wp_users'
));
$wp_users_segment->save();
}
// Synchronize WP Users
WP::synchronizeUsers();
// Default segment
if(Segment::where('type', 'default')->count() === 0) {
$default_segment = Segment::create();
$default_segment->hydrate(array(
'name' => __('My First List'),
'description' =>
__('The list created automatically on install of MailPoet')
));
$default_segment->save();
}
}
function newsletter_option_fields() {
return array(
array(
'name' => 'event',
'newsletter_type' => 'welcome',
),
array(
'name' => 'segment',
'newsletter_type' => 'welcome',
),
array(
'name' => 'role',
'newsletter_type' => 'welcome',
),
array(
'name' => 'afterTimeNumber',
'newsletter_type' => 'welcome',
),
array(
'name' => 'afterTimeType',
'newsletter_type' => 'welcome',
),
array(
'name' => 'intervalType',
'newsletter_type' => 'notification',
),
array(
'name' => 'timeOfDay',
'newsletter_type' => 'notification',
),
array(
'name' => 'weekDay',
'newsletter_type' => 'notification',
),
array(
'name' => 'monthDay',
'newsletter_type' => 'notification',
),
array(
'name' => 'nthWeekDay',
'newsletter_type' => 'notification',
),
);
}
private function newsletter_templates() {
return array(
(new FranksRoastHouseTemplate(Env::$assets_url))->get(),
(new BlankTemplate(Env::$assets_url))->get(),
);
}
private function populate($model) {
$rows = $this->$model();
$table = $this->prefix . $model;
$_this = $this;
array_map(function($row) use ($_this, $table) {
if (!$_this->rowExists($table, $row)) {
$_this->insertRow($table, $row);
}
}, $rows);
}
private function rowExists($table, $columns) {
global $wpdb;
$conditions = array_map(function($key) use ($columns) {
return $key . '=%s';
}, array_keys($columns));
return $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM $table WHERE " . implode(' AND ', $conditions),
array_values($columns)
)) > 0;
}
private function insertRow($table, $row) {
global $wpdb;
return $wpdb->insert(
$table,
$row
);
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

45
lib/Config/PublicAPI.php Normal file
View File

@ -0,0 +1,45 @@
<?php
namespace MailPoet\Config;
use MailPoet\Cron\Daemon;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
class PublicAPI {
function __construct() {
# http://example.com/?mailpoet-api&section=&action=&payload=
$this->api = isset($_GET['mailpoet-api']) ? true : false;
$this->section = isset($_GET['section']) ? $_GET['section'] : false;
$this->action = isset($_GET['action']) ?
Helpers::underscoreToCamelCase($_GET['action']) :
false;
$this->payload = isset($_GET['payload']) ?
json_decode(urldecode($_GET['payload']), true) :
false;
}
function init() {
if(!$this->api && !$this->section) return;
$this->_checkAndCallMethod($this, $this->section, $terminate = true);
}
function queue() {
$queue = new Daemon($this->payload);
$this->_checkAndCallMethod($queue, $this->action);
}
private function _checkAndCallMethod($class, $method, $terminate = false) {
if(!method_exists($class, $method)) {
if(!$terminate) return;
header('HTTP/1.0 404 Not Found');
exit;
}
call_user_func(
array(
$class,
$method
)
);
}
}

View File

@ -61,19 +61,25 @@ class Widget {
}
function setupAdminDependencies() {
wp_enqueue_script('mailpoet_vendor',
Env::$assets_url.'/js/vendor.js',
array(),
Env::$version,
true
);
if(
empty($_GET['page'])
or
isset($_GET['page']) && strpos($_GET['page'], 'mailpoet') === false
) {
wp_enqueue_script('mailpoet_vendor',
Env::$assets_url.'/js/vendor.js',
array(),
Env::$version,
true
);
wp_enqueue_script('mailpoet_admin',
Env::$assets_url.'/js/mailpoet.js',
array(),
Env::$version,
true
);
wp_enqueue_script('mailpoet_admin',
Env::$assets_url.'/js/mailpoet.js',
array(),
Env::$version,
true
);
}
}
function setupActions() {

View File

@ -0,0 +1,36 @@
<?php
namespace MailPoet\Cron;
use Carbon\Carbon;
use MailPoet\Models\Setting;
class BootStrapMenu {
function __construct() {
$this->daemon = Setting::where('name', 'cron_daemon')
->findOne();
}
function bootStrap() {
return ($this->daemon) ?
array_merge(
array(
'timeSinceStart' =>
Carbon::createFromFormat(
'Y-m-d H:i:s',
$this->daemon->created_at,
'UTC'
)
->diffForHumans(),
'timeSinceUpdate' =>
Carbon::createFromFormat(
'Y-m-d H:i:s',
$this->daemon->updated_at,
'UTC'
)
->diffForHumans()
),
json_decode($this->daemon->value, true)
) :
"false";
}
}

132
lib/Cron/Daemon.php Normal file
View File

@ -0,0 +1,132 @@
<?php
namespace MailPoet\Cron;
use MailPoet\Cron\Workers\SendingQueue;
use MailPoet\Models\Setting;
use MailPoet\Util\Security;
require_once(ABSPATH . 'wp-includes/pluggable.php');
if(!defined('ABSPATH')) exit;
class Daemon {
function __construct($payload = array()) {
set_time_limit(0);
ignore_user_abort();
list ($this->daemon, $this->daemonData) = $this->getDaemon();
$this->refreshedToken = $this->refreshToken();
$this->payload = $payload;
$this->timer = microtime(true);
}
function start() {
if(!isset($this->payload['session'])) {
$this->abortWithError('missing session ID');
}
$this->manageSession('start');
$daemon = $this->daemon;
$daemonData = $this->daemonData;
if(!$daemon) {
$daemon = Setting::create();
$daemon->name = 'cron_daemon';
$daemonData = array(
'status' => null,
'counter' => 0
);
$daemon->value = json_encode($daemonData);
$daemon->save();
}
if($daemonData['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(
'result' => false,
'error' => 'already started'
);
}
$this->manageSession('end');
}
function run() {
if(!$this->daemon || $this->daemonData['status'] !== 'started') {
$this->abortWithError('not running');
}
if(!isset($this->payload['token']) ||
$this->payload['token'] !== $this->daemonData['token']
) {
$this->abortWithError('invalid token');
}
try {
$sendingQueue = new SendingQueue($this->timer);
$sendingQueue->process();
} catch(Exception $e) {
}
$elapsedTime = microtime(true) - $this->timer;
if($elapsedTime < 30) {
sleep(30 - $elapsedTime);
}
// after each execution, read daemon in case it's status was modified
list($daemon, $daemonData) = $this->getDaemon();
$daemonData['counter']++;
$daemonData['token'] = $this->refreshedToken;
$daemon->value = json_encode($daemonData);
$daemon->save();
if($daemonData['status'] === 'strated') $this->callSelf();
}
function getDaemon() {
$daemon = Setting::where('name', 'cron_daemon')
->findOne();
return array(
($daemon) ? $daemon : null,
($daemon) ? json_decode($daemon->value, true) : null
);
}
function refreshToken() {
return Security::generateRandomString();
}
function manageSession($action) {
switch($action) {
case 'start':
if(session_id()) {
session_write_close();
}
session_id($this->payload['session']);
session_start();
break;
case 'end':
session_write_close();
break;
}
}
function callSelf() {
$payload = json_encode(array('token' => $this->refreshedToken));
Supervisor::getRemoteUrl(
'/?mailpoet-api&section=queue&action=run&payload=' . urlencode($payload)
);
exit;
}
function abortWithError($error) {
wp_send_json(
array(
'result' => false,
'error' => $error
));
exit;
}
}

84
lib/Cron/Supervisor.php Normal file
View File

@ -0,0 +1,84 @@
<?php
namespace MailPoet\Cron;
use Carbon\Carbon;
use MailPoet\Config\Env;
use MailPoet\Models\Setting;
if(!defined('ABSPATH')) exit;
class Supervisor {
function __construct($forceStart = false) {
$this->forceStart = $forceStart;
if(!Env::isPluginActivated()) {
throw new \Exception('Database has not been configured.');
}
list ($this->daemon, $this->daemonData) = $this->getDaemon();
}
function checkDaemon() {
if(!$this->daemon) {
return $this->startDaemon();
}
if(!$this->forceStart && $this->daemonData['status'] === 'stopped') {
return;
}
$currentTime = Carbon::now('UTC');
$lastUpdateTime = Carbon::createFromFormat(
'Y-m-d H:i:s',
$this->daemon->updated_at, 'UTC'
);
$timeSinceLastStart = $currentTime->diffInSeconds($lastUpdateTime);
if($timeSinceLastStart < 40) return;
$this->daemonData['status'] = null;
$this->daemon->value = json_encode($this->daemonData);
$this->daemon->save();
return $this->startDaemon();
}
function startDaemon() {
if(!session_id()) session_start();
$sessionId = session_id();
session_write_close();
$_SESSION['cron_daemon'] = null;
$payload = json_encode(array('session' => $sessionId));
self::getRemoteUrl(
'/?mailpoet-api&section=queue&action=start&payload=' . urlencode($payload)
);
session_start();
$daemonStatus = $_SESSION['cron_daemon'];
unset($_SESSION['daemon']);
session_write_close();
return $daemonStatus;
}
function getDaemon() {
$daemon = Setting::where('name', 'cron_daemon')
->findOne();
$daemonData = ($daemon) ? json_decode($daemon->value, true) : false;
return array(
$daemon,
$daemonData
);
}
static function getRemoteUrl($url) {
$args = array(
'timeout' => 1,
'user-agent' => 'MailPoet (www.mailpoet.com)'
);
wp_remote_get(
self::getSiteUrl() . $url,
$args
);
}
static function getSiteUrl() {
if(preg_match('!:\d+/!', site_url())) return site_url();
preg_match('!http://(?P<host>.*?):(?P<port>\d+)!', site_url(), $server);
$fp = @fsockopen($server['host'], $server['port'], $errno, $errstr, 1);
return ($fp) ?
site_url() :
preg_replace('/(?=:\d+):\d+/', '$1', site_url());
}
}

View File

@ -0,0 +1,114 @@
<?php
namespace MailPoet\Cron\Workers;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterStatistics;
use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Router\Mailer;
if(!defined('ABSPATH')) exit;
class SendingQueue {
function __construct($timer = false) {
$this->timer = ($timer) ? $timer : microtime(true);
}
function process() {
$queues =
\MailPoet\Models\SendingQueue::orderByDesc('priority')
->whereNull('deleted_at')
->whereNull('status')
->findResultSet();
foreach($queues as $queue) {
$newsletter = Newsletter::findOne($queue->newsletter_id);
if(!$newsletter) {
continue;
};
$newsletter = $newsletter->asArray();
$mailer = new Mailer($httpRequest = false);
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);
$subscribersToProcess = $subscribers['to_process'];
if(!isset($subscribers['failed'])) $subscribers['failed'] = array();
if(!isset($subscribers['processed'])) $subscribers['processed'] = array();
foreach(array_chunk($subscribersToProcess, 200) as $subscriberIds) {
$dbSubscribers = Subscriber::whereIn('id', $subscriberIds)
->findArray();
foreach($dbSubscribers as $i => $dbSubscriber) {
$this->checkExecutionTimer();
// TODO: replace shortcodes in the newsletter
$result = $mailer->mailer->send(
$newsletter,
$mailer->transformSubscriber($dbSubscriber)
);
$newsletterStatistics = NewsletterStatistics::create();
$newsletterStatistics->subscriber_id = $dbSubscriber['id'];
$newsletterStatistics->newsletter_id = $newsletter['id'];
$newsletterStatistics->queue_id = $queue->id;
$newsletterStatistics->save();
if($result) {
$subscribers['processed'][] = $dbSubscriber['id'];
} else {
$subscribers['failed'][] = $dbSubscriber['id'];
}
$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 checkExecutionTimer() {
$elapsedTime = microtime(true) - $this->timer;
if($elapsedTime >= 28) throw new \Exception('Maximum execution time reached.');
}
}

View File

@ -31,16 +31,21 @@ class AmazonSES {
}
function getBody($newsletter, $subscriber) {
return array(
$body = array(
'Action' => 'SendEmail',
'Version' => '2010-12-01',
'Source' => $this->from,
'Destination.ToAddresses.member.1' => $subscriber,
'Message.Subject.Data' => $newsletter['subject'],
'Message.Body.Html.Data' => $newsletter['body']['html'],
'Message.Body.Text.Data' => $newsletter['body']['text'],
'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) {

View File

@ -22,15 +22,20 @@ class ElasticEmail {
}
function getBody($newsletter, $subscriber) {
return array(
$body = array(
'api_key' => $this->apiKey,
'from' => $this->fromEmail,
'from_name' => $this->fromName,
'to' => $subscriber,
'subject' => $newsletter['subject'],
'body_html' => $newsletter['body']['html'],
'body_text' => $newsletter['body']['text']
'subject' => $newsletter['subject']
);
if(!empty($newsletter['body']['html'])) {
$body['body_html'] = $newsletter['body']['html'];
}
if(!empty($newsletter['body']['text'])) {
$body['body_text'] = $newsletter['body']['text'];
}
return $body;
}
function request($newsletter, $subscriber) {

View File

@ -22,13 +22,18 @@ class MailGun {
}
function getBody($newsletter, $subscriber) {
return array(
$body = array(
'from' => $this->from,
'to' => $subscriber,
'subject' => $newsletter['subject'],
'html' => $newsletter['body']['html'],
'text' => $newsletter['body']['text']
'subject' => $newsletter['subject']
);
if(!empty($newsletter['body']['html'])) {
$body['html'] = $newsletter['body']['html'];
}
if(!empty($newsletter['body']['text'])) {
$body['text'] = $newsletter['body']['text'];
}
return $body;
}
function auth() {

View File

@ -37,18 +37,23 @@ class Mandrill {
}
function getBody($newsletter, $subscriber) {
return array(
$body = array(
'key' => $this->apiKey,
'message' => array(
'from_email' => $this->fromEmail,
'from_name' => $this->fromName,
'to' => array($subscriber),
'subject' => $newsletter['subject'],
'html' => $newsletter['body']['html'],
'text' => $newsletter['body']['text']
'subject' => $newsletter['subject']
),
'async' => false,
);
if(!empty($newsletter['body']['html'])) {
$body['message']['html'] = $newsletter['body']['html'];
}
if(!empty($newsletter['body']['text'])) {
$body['message']['text'] = $newsletter['body']['text'];
}
return $body;
}
function request($newsletter, $subscriber) {

View File

@ -25,14 +25,19 @@ class SendGrid {
}
function getBody($newsletter, $subscriber) {
return array(
$body = array(
'to' => $subscriber,
'from' => $this->fromEmail,
'fromname' => $this->fromName,
'subject' => $newsletter['subject'],
'html' => $newsletter['body']['html'],
'text' => $newsletter['body']['text']
'subject' => $newsletter['subject']
);
if(!empty($newsletter['body']['html'])) {
$body['html'] = $newsletter['body']['html'];
}
if(!empty($newsletter['body']['text'])) {
$body['text'] = $newsletter['body']['text'];
}
return $body;
}
function auth() {

View File

@ -36,7 +36,7 @@ class MailPoet {
}
function getBody($newsletter, $subscriber) {
return array(
$body = array(
'to' => (array(
'address' => $subscriber['email'],
'name' => $subscriber['name']
@ -45,10 +45,15 @@ class MailPoet {
'address' => $this->fromEmail,
'name' => $this->fromName
)),
'subject' => $newsletter['subject'],
'html' => $newsletter['body']['html'],
'text' => $newsletter['body']['text']
'subject' => $newsletter['subject']
);
if(!empty($newsletter['body']['html'])) {
$body['html'] = $newsletter['body']['html'];
}
if(!empty($newsletter['body']['text'])) {
$body['text'] = $newsletter['body']['text'];
}
return $body;
}
function auth() {

View File

@ -4,11 +4,13 @@ namespace MailPoet\Mailer;
if(!defined('ABSPATH')) exit;
class SMTP {
function __construct($host, $port, $authentication, $encryption,
function __construct($host, $port, $authentication, $login = null, $password = null, $encryption,
$fromEmail, $fromName) {
$this->host = $host;
$this->port = $port;
$this->authentication = $authentication;
$this->login = $login;
$this->password = $password;
$this->encryption = $encryption;
$this->fromName = $fromName;
$this->fromEmail = $fromEmail;
@ -19,7 +21,8 @@ class SMTP {
try {
$message = $this->createMessage($newsletter, $subscriber);
$result = $this->mailer->send($message);
} catch (\Exception $e) {
} catch(\Exception $e) {
!d($e->getMessage());exit;
$result = false;
}
return ($result === 1);
@ -31,20 +34,25 @@ class SMTP {
$transport->setTimeout(10);
if($this->authentication) {
$transport
->setUsername($this->authentication['login'])
->setPassword($this->authentication['password']);
->setUsername($this->login)
->setPassword($this->password);
}
return \Swift_Mailer::newInstance($transport);
}
function createMessage($newsletter, $subscriber) {
return \Swift_Message::newInstance()
$message = \Swift_Message::newInstance()
->setFrom(array($this->fromEmail => $this->fromName))
->setTo($this->processSubscriber($subscriber))
->setSubject($newsletter['subject'])
->setBody($newsletter['body']['html'], 'text/html')
->addPart($newsletter['body']['text'], 'text/plain');
->setSubject($newsletter['subject']);
if(!empty($newsletter['body']['html'])) {
$message = $message->setBody($newsletter['body']['html'], 'text/html');
}
if(!empty($newsletter['body']['text'])) {
$message = $message->addPart($newsletter['body']['text'], 'text/plain');
}
return $message;
}
function processSubscriber($subscriber) {

View File

@ -20,7 +20,7 @@ class WPMail {
}
function addFilters() {
foreach ($this->filters as $filter => $method) {
foreach($this->filters as $filter => $method) {
add_filter($filter, array(
$this,
$method
@ -29,7 +29,7 @@ class WPMail {
}
function removeFilters() {
foreach ($this->filters as $filter => $method) {
foreach($this->filters as $filter => $method) {
remove_filter($filter, array(
$this,
$method
@ -51,7 +51,10 @@ class WPMail {
function send($newsletter, $subscriber) {
$this->addFilters();
$result = wp_mail($subscriber, $newsletter['subject'], $newsletter['body']['html']);
$result = wp_mail(
$subscriber, $newsletter['subject'],
(!empty($newsletter['body']['html'])) ? $newsletter['body']['html'] : $newsletter['body']['text']
);
$this->removeFilters();
return ($result === true);
}

50
lib/Models/Filter.php Normal file
View File

@ -0,0 +1,50 @@
<?php
namespace MailPoet\Models;
if(!defined('ABSPATH')) exit;
class Filter extends Model {
static $_table = MP_FILTERS_TABLE;
function __construct() {
parent::__construct();
$this->addValidations('name', array(
'required' => __('You need to specify a name.')
));
}
function delete() {
// delete all relations to subscribers
SegmentFilter::where('filter_id', $this->id)->deleteMany();
return parent::delete();
}
function segments() {
return $this->has_many_through(
__NAMESPACE__.'\Segment',
__NAMESPACE__.'\SegmentFilter',
'filter_id',
'segment_id'
);
}
static function createOrUpdate($data = array()) {
$filter = false;
if(isset($data['id']) && (int)$data['id'] > 0) {
$filter = self::findOne((int)$data['id']);
}
if($filter === false) {
$filter = self::create();
$filter->hydrate($data);
} else {
unset($data['id']);
$filter->set($data);
}
$filter->save();
return $filter;
}
}

View File

@ -14,9 +14,17 @@ class Newsletter extends Model {
if(is_string($this->deleted_at) && strlen(trim($this->deleted_at)) === 0) {
$this->set_expr('deleted_at', 'NULL');
}
return parent::save();
}
function delete() {
// delete all relations to segments
NewsletterSegment::where('newsletter_id', $this->id)->deleteMany();
return parent::delete();
}
function segments() {
return $this->has_many_through(
__NAMESPACE__.'\Segment',
@ -35,6 +43,12 @@ class Newsletter extends Model {
)->select_expr(MP_NEWSLETTER_OPTION_TABLE.'.value');
}
function getQueue() {
return SendingQueue::where('newsletter_id', $this->id)
->orderByDesc('updated_at')
->findOne();
}
static function search($orm, $search = '') {
return $orm->where_like('subject', '%' . $search . '%');
}
@ -126,8 +140,8 @@ class Newsletter extends Model {
static function createOrUpdate($data = array()) {
$newsletter = false;
if(isset($data['id']) && (int) $data['id'] > 0) {
$newsletter = self::findOne((int) $data['id']);
if(isset($data['id']) && (int)$data['id'] > 0) {
$newsletter = self::findOne((int)$data['id']);
}
if($newsletter === false) {

View File

@ -0,0 +1,12 @@
<?php
namespace MailPoet\Models;
if(!defined('ABSPATH')) exit;
class NewsletterStatistics extends Model {
public static $_table = MP_NEWSLETTER_STATISTICS_TABLE;
function __construct() {
parent::__construct();
}
}

View File

@ -17,7 +17,7 @@ class Segment extends Model {
function delete() {
// delete all relations to subscribers
SubscriberSegment::where('segment_id', $this->id)->deleteMany();
parent::delete();
return parent::delete();
}
function newsletters() {
@ -38,6 +38,15 @@ class Segment extends Model {
);
}
function segmentFilters() {
return $this->has_many_through(
__NAMESPACE__.'\Filter',
__NAMESPACE__.'\SegmentFilter',
'segment_id',
'filter_id'
);
}
function duplicate($data = array()) {
$duplicate = parent::duplicate($data);
@ -67,6 +76,10 @@ class Segment extends Model {
->delete();
}
static function getWPUsers() {
return self::where('type', 'wp_users')->findOne();
}
static function search($orm, $search = '') {
return $orm->whereLike('name', '%'.$search.'%');
}
@ -94,7 +107,7 @@ class Segment extends Model {
}
}
static function getSegmentsForImport() {
static function getSegmentsWithSubscriberCount() {
return self::selectMany(array(self::$_table.'.id', self::$_table.'.name'))
->select_expr(
'COUNT('.MP_SUBSCRIBER_SEGMENT_TABLE.'.subscriber_id)', 'subscribers'
@ -102,8 +115,13 @@ class Segment extends Model {
->left_outer_join(
MP_SUBSCRIBER_SEGMENT_TABLE,
array(self::$_table.'.id', '=', MP_SUBSCRIBER_SEGMENT_TABLE.'.segment_id'))
->left_outer_join(
MP_SUBSCRIBERS_TABLE,
array(MP_SUBSCRIBER_SEGMENT_TABLE.'.subscriber_id', '=', MP_SUBSCRIBERS_TABLE.'.id'))
->whereNull(MP_SUBSCRIBERS_TABLE.'.deleted_at')
->group_by(self::$_table.'.id')
->group_by(self::$_table.'.name')
->where(self::$_table.'.type', 'default')
->findArray();
}
@ -114,16 +132,18 @@ class Segment extends Model {
'LEFT JOIN ' . self::$_table . ' segments ON segments.id = relation.segment_id ' .
'LEFT JOIN ' . MP_SUBSCRIBERS_TABLE . ' subscribers ON subscribers.id = relation.subscriber_id ' .
(($withConfirmedSubscribers) ?
'WHERE subscribers.status = 1 ' :
'WHERE subscribers.status = "subscribed" ' :
'WHERE relation.segment_id IS NOT NULL ') .
'AND subscribers.deleted_at IS NULL ' .
'GROUP BY segments.id) ' .
'UNION ALL ' .
'(SELECT 0 as id, "' . __('Not In List') . '" as name, COUNT(*) as subscribers ' .
'FROM ' . MP_SUBSCRIBERS_TABLE . ' subscribers ' .
'LEFT JOIN ' . MP_SUBSCRIBER_SEGMENT_TABLE . ' relation on relation.subscriber_id = subscribers.id ' .
(($withConfirmedSubscribers) ?
'WHERE relation.subscriber_id is NULL AND subscribers.status = 1 ' :
'WHERE relation.subscriber_id is NULL AND subscribers.status = "subscribed" ' :
'WHERE relation.subscriber_id is NULL ') .
'AND subscribers.deleted_at IS NULL ' .
'HAVING subscribers) ' .
'ORDER BY name'
)->findArray();
@ -147,4 +167,8 @@ class Segment extends Model {
$segment->save();
return $segment;
}
static function getPublic() {
return self::getPublished()->where('type', 'default');
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace MailPoet\Models;
if(!defined('ABSPATH')) exit;
class SegmentFilter extends Model {
public static $_table = MP_SEGMENT_FILTER_TABLE;
function __construct() {
parent::__construct();
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace MailPoet\Models;
if(!defined('ABSPATH')) exit;
class SendingQueue extends Model {
public static $_table = MP_SENDING_QUEUE_TABLE;
function __construct() {
parent::__construct();
}
function pause() {
if($this->count_processed === $this->count_total) {
return false;
} else {
$this->set('status', 'paused');
return $this->save();
}
}
function resume() {
if($this->count_processed === $this->count_total) {
return $this->complete();
} else {
$this->set_expr('status', 'NULL');
return $this->save();
}
}
function complete() {
$this->set('status', 'completed');
return $this->save();
}
}

View File

@ -28,6 +28,13 @@ class Setting extends Model {
}
}
public static function setValue($key, $value) {
return Setting::createOrUpdate(array(
'name' => $key,
'value' => $value
));
}
public static function getAll() {
$settingsCollection = self::findMany();
$settings = array();

View File

@ -135,20 +135,41 @@ class Subscriber extends Model {
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE . '.value END), NULL) as "' . $customField['name'].'"');
}
$orm = $orm
->left_outer_join(
->leftOuterJoin(
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE,
array(MP_SUBSCRIBERS_TABLE.'.id', '=',
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE.'.subscriber_id'))
->left_outer_join(
->leftOuterJoin(
MP_CUSTOM_FIELDS_TABLE,
array(MP_CUSTOM_FIELDS_TABLE.'.id','=',
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE.'.custom_field_id'))
->group_by(MP_SUBSCRIBERS_TABLE.'.id');
->groupBy(MP_SUBSCRIBERS_TABLE.'.id');
return $orm;
}
static function filterWithCustomFieldsForExport($orm) {
$orm = $orm->select(MP_SUBSCRIBERS_TABLE.'.*');
$customFields = CustomField::findArray();
foreach ($customFields as $customField) {
$orm = $orm->selectExpr(
'CASE WHEN ' .
MP_CUSTOM_FIELDS_TABLE . '.id=' . $customField['id'] . ' THEN ' .
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE . '.value END as "' . $customField['name'].'"');
}
$orm = $orm
->leftOuterJoin(
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE,
array(MP_SUBSCRIBERS_TABLE.'.id', '=',
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE.'.subscriber_id'))
->leftOuterJoin(
MP_CUSTOM_FIELDS_TABLE,
array(MP_CUSTOM_FIELDS_TABLE.'.id','=',
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE.'.custom_field_id'));
return $orm;
}
function customFields() {
return $this->has_many_through(
return $this->hasManyThrough(
__NAMESPACE__.'\CustomField',
__NAMESPACE__.'\SubscriberCustomField',
'subscriber_id',
@ -303,7 +324,7 @@ class Subscriber extends Model {
->whereNull('deleted_at')
->where('status', 'unconfirmed');
}
static function createMultiple($columns, $values) {
return self::rawExecute(
'INSERT INTO `' . self::$_table . '` ' .

View File

@ -50,9 +50,11 @@ class MetaInformationManager {
// check if the user specified a label to be displayed before the author's name
if(strlen($preceded_by) > 0) {
$content = stripslashes($preceded_by) . ' ';
} else {
$content = '';
}
return join(', ', $categories);
return $content . join(', ', $categories);
} else {
return '';
}

View File

@ -14,7 +14,7 @@ class PostListTransformer {
function transform($posts) {
$results = array();
$use_divider = (bool)$this->args['showDivider'];
$use_divider = $this->args['showDivider'] === 'true';
foreach ($posts as $index => $post) {
if ($use_divider && $index > 0) {

View File

@ -22,16 +22,26 @@ class PostTransformer {
$content = $content_manager->filterContent($content);
$structure_transformer = new StructureTransformer();
$structure = $structure_transformer->transform($content, (bool)$this->args['imagePadded']);
$structure = $structure_transformer->transform($content, $this->args['imagePadded'] === 'true');
$structure = $this->appendFeaturedImage($post, (bool)$this->args['imagePadded'], $structure);
$structure = $this->appendFeaturedImage(
$post,
$this->args['displayType'],
$this->args['imagePadded'] === 'true',
$structure
);
$structure = $this->appendPostTitle($post, $structure);
$structure = $this->appendReadMore($post->ID, $structure);
return $structure;
}
private function appendFeaturedImage($post, $image_padded, $structure) {
private function appendFeaturedImage($post, $display_type, $image_padded, $structure) {
if ($display_type === 'full') {
// No featured images for full posts
return $structure;
}
$featured_image = $this->getFeaturedImage(
$post->ID,
$post->post_title,
@ -68,7 +78,7 @@ class PostTransformer {
return array(
'type' => 'image',
'link' => '',
'link' => get_permalink($post_id),
'src' => $image_info[0],
'alt' => $alt_text,
'padded' => $image_padded,
@ -84,6 +94,8 @@ class PostTransformer {
}
private function appendPostTitle($post, $structure) {
$title = $this->getPostTitle($post);
if ($this->args['titlePosition'] === 'inTextBlock') {
// Attach title to the first text block
$text_block_index = null;
@ -94,7 +106,6 @@ class PostTransformer {
}
}
$title = $this->getPostTitle($post);
if ($text_block_index === null) {
$structure[] = array(
'type' => 'text',
@ -103,6 +114,14 @@ class PostTransformer {
} else {
$structure[$text_block_index]['text'] = $title . $structure[$text_block_index]['text'];
}
} elseif ($this->args['titlePosition'] === 'aboveBlock') {
array_unshift(
$structure,
array(
'type' => 'text',
'text' => $title,
)
);
}
return $structure;
@ -113,6 +132,15 @@ class PostTransformer {
$button = $this->args['readMoreButton'];
$button['url'] = get_permalink($post_id);
$structure[] = $button;
} else {
$structure[] = array(
'type' => 'text',
'text' => sprintf(
'<a href="%s">%s</a>',
get_permalink($post_id),
$this->args['readMoreText']
),
);
}
return $structure;
@ -121,7 +149,7 @@ class PostTransformer {
private function getPostTitle($post) {
$title = $post->post_title;
if ((bool)$this->args['titleIsLink']) {
if ($this->args['titleIsLink'] === 'true') {
$title = '<a href="' . get_permalink($post->ID) . '">' . $title . '</a>';
}

194
lib/Router/Cron.php Normal file
View File

@ -0,0 +1,194 @@
<?php
namespace MailPoet\Router;
use MailPoet\Models\Segment;
use MailPoet\Models\SendingQueue;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
class Cron {
function controlDaemon($data) {
switch($data['action']) {
case 'start':
$supervisor = new Supervisor($forceStart = true);
wp_send_json(
array(
'result' => $supervisor->checkDaemon() ?
true :
false
)
);
break;
case 'stop':
$status = 'stopped';
break;
default:
$status = 'paused';
break;
}
$daemon = new Daemon();
if(!$daemon->daemon || $daemon->daemonData['status'] !== 'started') {
$result = false;
} else {
$daemon->daemonData['status'] = $status;
$daemon->daemon->value = json_encode($daemon->daemonData);
$result = $daemon->daemon->save();
}
wp_send_json(
array(
'result' => $result
)
);
}
function getDaemonStatus() {
$daemon = new \MailPoet\Cron\BootStrapMenu();
wp_send_json($daemon->bootStrap());
}
function addQueue($data) {
$queue = SendingQueue::where('newsletter_id', $data['newsletter_id'])
->whereNull('status')
->findArray();
!d($queue);
exit;
$queue = SendingQueue::create();
$queue->newsletter_id = $data['newsletter_id'];
$subscriber_ids = array();
$segments = Segment::whereIn('id', $data['segments'])
->findMany();
foreach($segments as $segment) {
$subscriber_ids = array_merge($subscriber_ids, Helpers::arrayColumn(
$segment->subscribers()
->findArray(),
'id'
));
}
$subscriber_ids = array_unique($subscriber_ids);
$queue->subscribers = json_encode(
array(
'to_process' => $subscriber_ids
)
);
$queue->count_total = $queue->count_to_process = count($subscriber_ids);
$queue->save();
wp_send_json(
!$queue->save() ?
array(
'result' => false,
'error' => 'Queue could not be created.'
) :
array(
'result' => true,
'data' => array($queue->id)
)
);
}
function addQueues($data) {
$result = array_map(function ($queueData) {
$queue = SendingQueue::create();
$queue->newsletter_id = $queueData['newsletter_id'];
$queue->subscribers = json_encode(
array(
'to_process' => $queueData['subscribers']
)
);
$queue->count_total = $queue->count_to_process = count($queueData['subscribers']);
$queue->save();
return array(
'newsletter_id' => $queue->newsletter_id,
'queue_id' => $queue->id
);
}, $data);
$result = Helpers::arrayColumn($result, 'queue_id', 'newsletter_id');
wp_send_json(
count($data) != count($result) ?
array(
'result' => false,
'error' => __('Some queues could not be created.'),
'data' => $result
) :
array(
'result' => true,
'data' => $result
)
);
}
function deleteQueue($data) {
$queue = SendingQueue::whereNull('deleted_at')
->findOne($data['queue_id']);
if(!$queue) {
wp_send_json(
array(
'result' => false,
'error' => __('Queue not found.')
)
);
}
$queue->deleted_at = 'Y-m-d H:i:s';
$queue->save();
wp_send_json(array('result' => true));
}
function deleteQueues($data) {
$queues = SendingQueue::whereNull('deleted_at')
->whereIn('id', $data['queue_ids'])
->findResultSet();
if(!$queues->count()) {
wp_send_json(
array(
'result' => false,
'error' => __('Queues not found.')
)
);
}
foreach($queues as $queue) {
$queue->deleted_at = 'Y-m-d H:i:s';
$queue->save();
}
wp_send_json(array('result' => true));
}
function getQueueStatus($data) {
$queue = SendingQueue::whereNull('deleted_at')
->findOne($data['queue_id'])
->asArray();
wp_send_json(
!$queue ?
array(
'result' => false,
'error' => __('Queue not found.')
) :
array(
'result' => true,
'data' => $queue
)
);
}
function getQueuesStatus($data) {
$queues = SendingQueue::whereNull('deleted_at')
->whereIn('id', $data['queue_ids'])
->findArray();
wp_send_json(
!$queues ?
array(
'result' => false,
'error' => __('Queue not found.')
) :
array(
'result' => true,
'data' => $queues
)
);
}
}

View File

@ -1,80 +1,159 @@
<?php
namespace MailPoet\Router;
use MailPoet\Models\Setting;
require_once(ABSPATH . 'wp-includes/pluggable.php');
if(!defined('ABSPATH')) exit;
class Mailer {
function __construct() {
$this->fromName = $this->getSetting('from_name');
$this->fromEmail = $this->getSetting('from_address');
$this->mailer = $this->getSetting('mailer');
$this->from = sprintf('%s <%s>', $this->fromName, $this->fromEmail);
}
function send($newsletter, $subscriber) {
$subscriber = $this->transformSubscriber($subscriber);
$mailer = $this->buildMailer();
return wp_send_json($mailer->send($newsletter, $subscriber));
}
function buildMailer() {
switch ($this->mailer['name']) {
case 'AmazonSES':
$mailer = new $this->mailer['class'](
$this->mailer['region'],
$this->mailer['access_key'],
$this->mailer['secret_key'],
$this->from
);
break;
case 'ElasticEmail':
$mailer = new $this->mailer['class'](
$this->mailer['api_key'],
$this->fromEmail, $this->fromName
);
break;
case 'MailGun':
$mailer = new $this->mailer['class'](
$this->mailer['domain'],
$this->mailer['api_key'],
$this->from
);
break;
case 'MailPoet':
$mailer = new $this->mailer['class'](
$this->mailer['api_key'],
$this->fromEmail,
$this->fromName
);
break;
case 'Mandrill':
$mailer = new $this->mailer['class'](
$this->mailer['api_key'],
$this->fromEmail, $this->fromName
);
break;
case 'SendGrid':
$mailer = new $this->mailer['class'](
$this->mailer['api_key'],
$this->fromEmail,
$this->fromName
);
break;
case 'SMTP':
$mailer = new $this->mailer['class'](
$this->mailer['host'],
$this->mailer['port'],
$this->mailer['authentication'],
$this->mailer['encryption'],
$this->fromEmail,
$this->fromName);
break;
function __construct($httpRequest = true) {
$this->mailerType = array(
'AmazonSES' => 'API',
'ElasticEmail' => 'API',
'MailGun' => 'API',
'Mandrill' => 'API',
'SendGrid' => 'API',
'MailPoet' => null,
'SMTP' => null,
'WPMail' => null
);
if(!$httpRequest) {
list($this->fromName, $this->fromEmail, $this->fromNameEmail)
= $this->getSetting('sender');
$this->mailer = $this->getSetting('mailer');
}
return $mailer;
}
function send($data) {
$subscriber = $this->transformSubscriber($data['subscriber']);
list($fromName, $fromEmail, $fromNameEmail)
= $this->getSetting('sender');
if(!$fromName && !$fromEmail) {
wp_send_json(
array(
'result' => false,
'errors' => array(__('Please configure your name and e-mail address.'))
)
);
}
$data['mailer']['class'] = 'MailPoet\\Mailer\\' .
(($this->mailerType[$data['mailer']['method']]) ?
$this->mailerType[$data['mailer']['method']] . '\\' . $data['mailer']['method'] :
$data['mailer']['method']
);
$mailer = $this->buildMailer(
$data['mailer'],
$fromName,
$fromEmail,
$fromNameEmail
);
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
);
}
$result = $mailer->send($data['newsletter'], $subscriber);
wp_send_json(
array(
'result' => ($result) ? true : false
)
);
}
function buildMailer($mailer = false, $fromName = false, $fromEmail = false, $fromNameEmail = false) {
if(!$mailer) $mailer = $this->mailer;
if(!$fromName) $fromName = $this->fromName;
if(!$fromEmail) $fromEmail = $this->fromEmail;
if(!$fromNameEmail) $fromNameEmail = $this->fromNameEmail;
switch($mailer['method']) {
case 'AmazonSES':
$mailerInstance = new $mailer['class'](
$mailer['region'],
$mailer['access_key'],
$mailer['secret_key'],
$fromNameEmail
);
break;
case 'ElasticEmail':
$mailerInstance = new $mailer['class'](
$mailer['api_key'],
$fromEmail, $fromName
);
break;
case 'MailGun':
$mailerInstance = new $mailer['class'](
$mailer['domain'],
$mailer['api_key'],
$fromNameEmail
);
break;
case 'MailPoet':
$mailerInstance = new $mailer['class'](
$mailer['mailpoet_api_key'],
$fromEmail,
$fromName
);
break;
case 'Mandrill':
$mailerInstance = new $mailer['class'](
$mailer['api_key'],
$fromEmail, $fromName
);
break;
case 'SendGrid':
$mailerInstance = new $mailer['class'](
$mailer['api_key'],
$fromEmail,
$fromName
);
break;
case 'WPMail':
$mailerInstance = new $mailer['class'](
$fromEmail,
$fromName
);
break;
case 'SMTP':
$mailerInstance = new $mailer['class'](
$mailer['host'],
$mailer['port'],
$mailer['authentication'],
$mailer['login'],
$mailer['password'],
$mailer['encryption'],
$fromEmail,
$fromName
);
break;
default:
throw new \Exception('Mailing method does not exist.');
break;
}
return $mailerInstance;
}
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'];
@ -84,65 +163,29 @@ class Mailer {
}
function getSetting($setting) {
if($setting === 'mailer') {
$mailers = array(
array(
'name' => 'AmazonSES',
'type' => 'API',
'access_key' => 'AKIAJM6Y5HMGXBLDNSRA',
'secret_key' => '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',
'region' => 'us-east-1'
),
array(
'name' => 'ElasticEmail',
'type' => 'API',
'api_key' => '997f1f7f-41de-4d7f-a8cb-86c8481370fa'
),
array(
'name' => 'MailGun',
'type' => 'API',
'api_key' => 'key-6cf5g5qjzenk-7nodj44gdt8phe6vam2',
'domain' => 'mrcasual.com'
),
array(
'name' => 'MailPoet',
'api_key' => 'dhNSqj1XHkVltIliyQDvMiKzQShOA5rs0m_DdRUVZHU'
),
array(
'name' => 'Mandrill',
'type' => 'API',
'api_key' => '692ys1B7REEoZN7R-dYwNA'
),
array(
'name' => 'SendGrid',
'type' => 'API',
'api_key' => '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'
),
array(
'name' => 'SMTP',
'host' => 'email-smtp.us-west-2.amazonaws.com',
'port' => 587,
'authentication' => array(
'login' => 'AKIAIGPBLH6JWG5VCBQQ',
'password' => 'AudVHXHaYkvr54veCzqiqOxDiMMyfQW3/V6F1tYzGXY3'
),
'encryption' => 'tls'
),
array(
'name' => 'WPMail'
)
);
$mailer = $mailers[array_rand($mailers)];
$mailer['class'] = 'MailPoet\\Mailer\\' .
((isset($mailer['type'])) ?
$mailer['type'] . '\\' . $mailer['name'] :
$mailer['name']
switch($setting) {
case 'mailer':
$mailer = Setting::getValue('mta', null);
if(!$mailer || !isset($mailer['method'])) throw new \Exception('Mailing method is not configured.');
$mailer['class'] = 'MailPoet\\Mailer\\' .
(($this->mailerType[$mailer['method']]) ?
$this->mailerType[$mailer['method']] . '\\' . $mailer['method'] :
$mailer['method']
);
return $mailer;
break;
case 'sender':
$sender = Setting::getValue($setting, null);
if(!$sender) throw new \Exception('Sender name and email are not configured.');
return array(
$sender['name'],
$sender['address'],
sprintf('%s <%s>', $sender['name'], $sender['address'])
);
return $mailer;
break;
default:
return Setting::getValue($setting, null);
break;
}
if($setting === 'from_name') return 'Sender';
if($setting === 'from_address') return 'staff@mailpoet.com';
return Setting::where('name', $setting)
->findOne()->value;
}
}

View File

@ -12,6 +12,7 @@ use MailPoet\Models\NewsletterSegment;
use MailPoet\Models\NewsletterOptionField;
use MailPoet\Models\NewsletterOption;
use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Models\SendingQueue;
if(!defined('ABSPATH')) exit;
@ -101,31 +102,51 @@ class Newsletters {
));
}
function delete($data = array()) {
$newsletter = newsletter::findOne($data['id']);
$confirm_delete = filter_var($data['confirm'], FILTER_VALIDATE_BOOLEAN);
function restore($id) {
$result = false;
$newsletter = Newsletter::findOne($id);
if($newsletter !== false) {
if($confirm_delete) {
$newsletter->delete();
$result = array('newsletters' => 1);
} else {
$newsletter->set_expr('deleted_at', 'NOW()');
$result = array('newsletters' => (int)$newsletter->save());
}
} else {
$result = false;
$result = $newsletter->restore();
}
wp_send_json($result);
}
function restore($id) {
function trash($id) {
$result = false;
$newsletter = Newsletter::findOne($id);
if($newsletter !== false) {
$newsletter->set_expr('deleted_at', 'NULL');
$result = array('newsletters' => (int)$newsletter->save());
} else {
$result = false;
$result = $newsletter->trash();
}
wp_send_json($result);
}
function delete($id) {
$result = false;
$newsletter = Newsletter::findOne($id);
if($newsletter !== false) {
$newsletter->delete();
$result = 1;
}
wp_send_json($result);
}
function duplicate($id) {
$result = false;
$newsletter = Newsletter::findOne($id);
if($newsletter !== false) {
$data = array(
'subject' => sprintf(__('Copy of %s'), $newsletter->subject)
);
$result = $newsletter->duplicate($data)->asArray();
}
wp_send_json($result);
}
@ -199,14 +220,20 @@ class Newsletters {
$listing_data = $listing->get();
// fetch segments relations for each returned item
foreach($listing_data['items'] as &$item) {
// get segments
$segments = NewsletterSegment::select('segment_id')
->where('newsletter_id', $item['id'])
->findMany();
$item['segments'] = array_map(function($relation) {
return $relation->segment_id;
}, $segments);
// get queue
$queue = SendingQueue::where('newsletter_id', $item['id'])
->orderByDesc('updated_at')
->findOne();
$item['queue'] = ($queue !== false) ? $queue->asArray() : null;
}
wp_send_json($listing_data);

196
lib/Router/Queue.php Normal file
View File

@ -0,0 +1,196 @@
<?php
namespace MailPoet\Router;
use MailPoet\Models\Segment;
use MailPoet\Models\SendingQueue;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
class Queue {
function controlDaemon($data) {
switch($data['action']) {
case 'start':
$supervisor = new Supervisor($forceStart = true);
wp_send_json(
array(
'result' => $supervisor->checkDaemon() ?
true :
false
)
);
break;
case 'stop':
$status = 'stopped';
break;
default:
$status = 'paused';
break;
}
$daemon = new Daemon();
if(!$daemon->daemon || $daemon->daemonData['status'] !== 'started') {
$result = false;
} else {
$daemon->daemonData['status'] = $status;
$daemon->daemon->value = json_encode($daemon->daemonData);
$result = $daemon->daemon->save();
}
wp_send_json(
array(
'result' => $result
)
);
}
function getDaemonStatus() {
$daemon = new \MailPoet\Cron\BootStrapMenu();
wp_send_json($daemon->bootStrap());
}
function addQueue($data) {
$queue = SendingQueue::where('newsletter_id', $data['newsletter_id'])
->whereNull('status')
->findArray();
!d($queue);
exit;
$queue = SendingQueue::create();
$queue->newsletter_id = $data['newsletter_id'];
$subscriber_ids = array();
$segments = Segment::whereIn('id', $data['segments'])
->findMany();
foreach($segments as $segment) {
$subscriber_ids = array_merge($subscriber_ids, Helpers::arrayColumn(
$segment->subscribers()
->findArray(),
'id'
));
}
$subscriber_ids = array_unique($subscriber_ids);
$queue->subscribers = json_encode(
array(
'to_process' => $subscriber_ids
)
);
$queue->count_total = $queue->count_to_process = count($subscriber_ids);
$queue->save();
wp_send_json(
!$queue->save() ?
array(
'result' => false,
'error' => 'Queue could not be created.'
) :
array(
'result' => true,
'data' => array($queue->id)
)
);
}
function addQueues($data) {
$result = array_map(function ($queueData) {
$queue = SendingQueue::create();
$queue->newsletter_id = $queueData['newsletter_id'];
$queue->subscribers = json_encode(
array(
'to_process' => $queueData['subscribers']
)
);
$queue->count_total = $queue->count_to_process = count($queueData['subscribers']);
$queue->save();
return array(
'newsletter_id' => $queue->newsletter_id,
'queue_id' => $queue->id
);
}, $data);
$result = Helpers::arrayColumn($result, 'queue_id', 'newsletter_id');
wp_send_json(
count($data) != count($result) ?
array(
'result' => false,
'error' => __('Some queues could not be created.'),
'data' => $result
) :
array(
'result' => true,
'data' => $result
)
);
}
function deleteQueue($data) {
$queue = SendingQueue::whereNull('deleted_at')
->findOne($data['queue_id']);
if(!$queue) {
wp_send_json(
array(
'result' => false,
'error' => __('Queue not found.')
)
);
}
$queue->deleted_at = 'Y-m-d H:i:s';
$queue->save();
wp_send_json(array('result' => true));
}
function deleteQueues($data) {
$queues = SendingQueue::whereNull('deleted_at')
->whereIn('id', $data['queue_ids'])
->findResultSet();
if(!$queues->count()) {
wp_send_json(
array(
'result' => false,
'error' => __('Queues not found.')
)
);
}
foreach($queues as $queue) {
$queue->deleted_at = 'Y-m-d H:i:s';
$queue->save();
}
wp_send_json(array('result' => true));
}
function getQueueStatus($data) {
$queue = SendingQueue::whereNull('deleted_at')
->findOne($data['queue_id'])
->asArray();
wp_send_json(
!$queue ?
array(
'result' => false,
'error' => __('Queue not found.')
) :
array(
'result' => true,
'data' => $queue
)
);
}
function getQueuesStatus($data) {
$queues = SendingQueue::whereNull('deleted_at')
->whereIn('id', $data['queue_ids'])
->findArray();
wp_send_json(
!$queues ?
array(
'result' => false,
'error' => __('Queue not found.')
) :
array(
'result' => true,
'data' => $queues
)
);
}
}

View File

@ -2,7 +2,9 @@
namespace MailPoet\Router;
use \MailPoet\Models\Segment;
use \MailPoet\Models\SubscriberSegment;
use \MailPoet\Models\SegmentFilter;
use \MailPoet\Listing;
use \MailPoet\Segments\WP;
if(!defined('ABSPATH')) exit;
@ -42,20 +44,19 @@ class Segments {
'subscribers'
)
->select_expr(
'SUM(CASE status WHEN "subscribed" THEN 1 ELSE 0 END)',
'SUM(CASE subscribers.status WHEN "subscribed" THEN 1 ELSE 0 END)',
'subscribed'
)
->select_expr(
'SUM(CASE status WHEN "unsubscribed" THEN 1 ELSE 0 END)',
'SUM(CASE subscribers.status WHEN "unsubscribed" THEN 1 ELSE 0 END)',
'unsubscribed'
)
->select_expr(
'SUM(CASE status WHEN "unconfirmed" THEN 1 ELSE 0 END)',
'SUM(CASE subscribers.status WHEN "unconfirmed" THEN 1 ELSE 0 END)',
'unconfirmed'
)
->findOne()->asArray();
!d(\ORM::get_last_query());exit;
$item = array_merge($item, $stats);
$item['subscribers_url'] = admin_url(
@ -136,6 +137,12 @@ class Segments {
wp_send_json($result);
}
function synchronize() {
$result = WP::synchronizeUsers();
wp_send_json($result);
}
function bulkAction($data = array()) {
$bulk_action = new Listing\BulkAction(
'\MailPoet\Models\Segment',

211
lib/Router/SendingQueue.php Normal file
View File

@ -0,0 +1,211 @@
<?php
namespace MailPoet\Router;
use MailPoet\Models\Newsletter;
use MailPoet\Models\Segment;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
class SendingQueue {
function add($data) {
$queue = \MailPoet\Models\SendingQueue::where('newsletter_id', $data['newsletter_id'])
->whereNull('status')
->findArray();
if(count($queue)) {
wp_send_json(
array(
'result' => false,
'errors' => array(__('Send operation is already in progress.'))
)
);
exit;
}
$queue = \MailPoet\Models\SendingQueue::create();
$queue->newsletter_id = $data['newsletter_id'];
$subscriber_ids = array();
$segments = Segment::whereIn('id', $data['segments'])
->findMany();
foreach($segments as $segment) {
$subscriber_ids = array_merge($subscriber_ids, Helpers::arrayColumn(
$segment->subscribers()
->findArray(),
'id'
));
}
$subscriber_ids = array_unique($subscriber_ids);
$queue->subscribers = json_encode(
array(
'to_process' => $subscriber_ids
)
);
$queue->count_total = $queue->count_to_process = count($subscriber_ids);
$result = $queue->save();
if($result === false) {
$errors = array(__('Queue could not be created.'));
if(!empty($queue->getValidationErrors())) {
$errors = array_merge($errors, $queue->getValidationErrors());
}
wp_send_json(
array(
'result' => false,
'errors' => $errors
)
);
} else {
wp_send_json(
array(
'result' => true,
'data' => array($queue->id)
)
);
}
}
function pause($newsletter_id) {
$newsletter = Newsletter::findOne($newsletter_id);
$result = false;
if($newsletter !== false) {
$queue = $newsletter->getQueue();
if($queue !== false && $queue->id() > 0) {
$result = $queue->pause();
}
}
wp_send_json(array(
'result' => $result
));
}
function resume($newsletter_id) {
$newsletter = Newsletter::findOne($newsletter_id);
$result = false;
if($newsletter !== false) {
$queue = $newsletter->getQueue();
if($queue !== false && $queue->id() > 0) {
$result = $queue->resume();
}
}
wp_send_json(array(
'result' => $result
));
}
function addQueues($data) {
$newsletterIds = Helpers::arrayColumn($data, 'newsletter_id');
$queues = \MailPoet\Models\SendingQueue::whereIn('newsletter_id', $newsletterIds)
->whereNull('status')
->findArray();
if(count($queues)) {
wp_send_json(
array(
'result' => false,
'errors' => array(__('Send operation is already in progress.'))
)
);
}
$result = array_map(function ($queueData) {
$queue = \MailPoet\Models\SendingQueue::create();
$queue->newsletter_id = $queueData['newsletter_id'];
$queue->subscribers = json_encode(
array(
'to_process' => $queueData['subscribers']
)
);
$queue->count_total = $queue->count_to_process = count($queueData['subscribers']);
$queue->save();
return array(
'newsletter_id' => $queue->newsletter_id,
'queue_id' => $queue->id
);
}, $data);
$result = Helpers::arrayColumn($result, 'queue_id', 'newsletter_id');
wp_send_json(
count($data) != count($result) ?
array(
'result' => false,
'errors' => array(__('Some queues could not be created.')),
'data' => $result
) :
array(
'result' => true,
'data' => $result
)
);
}
function deleteQueue($data) {
$queue = \MailPoet\Models\SendingQueue::whereNull('deleted_at')
->findOne($data['queue_id']);
if(!$queue) {
wp_send_json(
array(
'result' => false,
'errors' => array(__('Queue not found.'))
)
);
}
$queue->deleted_at = 'Y-m-d H:i:s';
$queue->save();
wp_send_json(array('result' => true));
}
function deleteQueues($data) {
$queues = \MailPoet\Models\SendingQueue::whereNull('deleted_at')
->whereIn('id', $data['queue_ids'])
->findResultSet();
if(!$queues->count()) {
wp_send_json(
array(
'result' => false,
'errors' => array(__('Queues not found.'))
)
);
}
foreach($queues as $queue) {
$queue->deleted_at = 'Y-m-d H:i:s';
$queue->save();
}
wp_send_json(array('result' => true));
}
function getQueueStatus($data) {
$queue = \MailPoet\Models\SendingQueue::whereNull('deleted_at')
->findOne($data['queue_id'])
->asArray();
wp_send_json(
!$queue ?
array(
'result' => false,
'errors' => array(__('Queue not found.'))
) :
array(
'result' => true,
'data' => $queue
)
);
}
function getQueuesStatus($data) {
$queues = \MailPoet\Models\SendingQueue::whereNull('deleted_at')
->whereIn('id', $data['queue_ids'])
->findArray();
wp_send_json(
!$queues ?
array(
'result' => false,
'errors' => array(__('Queue not found.'))
) :
array(
'result' => true,
'data' => $queues
)
);
}
}

91
lib/Segments/WP.php Normal file
View File

@ -0,0 +1,91 @@
<?php
namespace MailPoet\Segments;
use \MailPoet\Models\Subscriber;
use \MailPoet\Models\Segment;
class WP {
static function synchronizeUser($wp_user_id) {
$wpUser = \get_userdata($wp_user_id);
$segment = Segment::getWPUsers();
if($wpUser === false or $segment === false) return;
$subscriber = Subscriber::where('wp_user_id', $wpUser->ID)
->findOne();
switch(current_filter()) {
case 'delete_user':
case 'deleted_user':
case 'remove_user_from_blog':
if($subscriber !== false && $subscriber->id()) {
$subscriber->delete();
}
break;
case 'user_register':
case 'added_existing_user':
case 'profile_update':
default:
// get first name & last name
$first_name = $wpUser->first_name;
$last_name = $wpUser->last_name;
if(empty($wpUser->first_name) && empty($wpUser->last_name)) {
$first_name = $wpUser->display_name;
}
// subscriber data
$data = array(
'wp_user_id'=> $wpUser->ID,
'email' => $wpUser->user_email,
'first_name' => $first_name,
'last_name' => $last_name,
'status' => 'subscribed'
);
if($subscriber !== false) {
$data['id'] = $subscriber->id();
}
$subscriber = Subscriber::createOrUpdate($data);
if($subscriber !== false && $subscriber->id()) {
if($segment !== false) {
$segment->addSubscriber($subscriber->id());
}
}
break;
}
}
static function synchronizeUsers() {
// get wordpress users list
$segment = Segment::getWPUsers();
// count WP users
$users_count = \count_users()['total_users'];
$linked_subscribers_count = $segment->subscribers()->count();
if($users_count !== $linked_subscribers_count) {
$linked_subscribers = Subscriber::select('wp_user_id')
->whereNotNull('wp_user_id')
->findArray();
$exclude_ids = array();
if(!empty($linked_subscribers)) {
$exclude_ids = array_map(function($subscriber) {
return $subscriber['wp_user_id'];
}, $linked_subscribers);
}
$users = \get_users(array(
'count_total' => false,
'fields' => 'ID',
'exclude' => $exclude_ids
));
foreach($users as $user) {
static::synchronizeUser($user);
}
}
return true;
}
}

View File

@ -3,29 +3,45 @@ namespace MailPoet\Settings;
class Hosts {
private static $_smtp = array(
'amazon' => array(
'AmazonSES' => array(
'name' => 'Amazon SES',
'api' => false,
'emails' => 100,
'interval' => 5
'interval' => 5,
'fields' => array(
'region',
'access_key',
'secret_key'
),
'regions' => array(
'US East (N. Virginia)' => 'us-east-1.amazonaws.com',
'US West (Oregon)' => 'us-west-2.amazonaws.com',
'EU (Ireland)' => 'eu-west-1.amazonaws.com'
)
),
'elasticemail' => array(
'ElasticEmail' => array(
'name' => 'ElasticEmail',
'api' => true,
'emails' => 100,
'interval' => 5
'interval' => 5,
'fields' => array(
'api_key'
)
),
'mailgun' => array(
'MailGun' => array(
'name' => 'MailGun',
'api' => false,
'emails' => 100,
'interval' => 5
'interval' => 5,
'fields' => array(
'domain',
'api_key'
)
),
'sendgrid' => array(
'SendGrid' => array(
'name' => 'SendGrid',
'api' => true,
'emails' => 100,
'interval' => 5
'interval' => 5,
'fields' => array(
'api_key'
)
)
);

View File

@ -12,7 +12,7 @@ class BootStrapMenu {
function getSegments($withConfirmedSubscribers = false) {
$segments = ($this->action === 'import') ?
Segment::getSegmentsForImport() :
Segment::getSegmentsWithSubscriberCount() :
Segment::getSegmentsForExport($withConfirmedSubscribers);
return array_map(function ($segment) {
return array(
@ -132,4 +132,4 @@ class BootStrapMenu {
}
return $data;
}
}
}

View File

@ -2,10 +2,12 @@
namespace MailPoet\Subscribers\ImportExport\Export;
use MailPoet\Config\Env;
use MailPoet\Subscribers\ImportExport\BootStrapMenu;
use MailPoet\Models\CustomField;
use MailPoet\Models\Segment;
use MailPoet\Models\Subscriber;
use MailPoet\Models\SubscriberSegment;
use MailPoet\Subscribers\ImportExport\BootStrapMenu;
use MailPoet\Util\Helpers;
use MailPoet\Util\XLSXWriter;
class Export {
@ -16,84 +18,86 @@ class Export {
$this->segments = $data['segments'];
$this->subscribersWithoutSegment = array_search(0, $this->segments);
$this->subscriberFields = $data['subscriberFields'];
$this->exportFile = $this->getExportFile($this->exportFormatOption);
$this->exportFileURL = $this->getExportFileURL($this->exportFile);
$this->profilerStart = microtime(true);
$this->exportFile = sprintf(
Env::$temp_path . '/mailpoet_export_%s.%s',
substr(md5(time()), 0, 4),
$this->exportFormatOption
);
$this->exportFileURL = sprintf(
'%s/%s/%s/%s',
plugins_url(),
Env::$plugin_name,
Env::$temp_name,
basename($this->exportFile)
);
}
function process() {
$subscribers = SubscriberSegment::
left_outer_join(
Subscriber::$_table,
array(
Subscriber::$_table . '.id',
'=',
SubscriberSegment::$_table . '.subscriber_id'
))
->left_outer_join(
Segment::$_table,
array(
Segment::$_table . '.id',
'=',
SubscriberSegment::$_table . '.segment_id'
))
->select(Segment::$_table . '.name', 'segment_name')
->orderByAsc('segment_name')
->filter('filterWithCustomFields')
->whereIn(SubscriberSegment::$_table . '.segment_id', $this->segments);
if(!$this->groupBySegmentOption) $subscribers = $subscribers->groupBy(Subscriber::$_table . '.id');
if($this->exportConfirmedOption) $subscribers = $subscribers->where(Subscriber::$_table . '.status', 'confirmed');
$subscribers = $subscribers->findArray();
$formattedSubscriberFields = $this->formatSubscriberFields($this->subscriberFields);
$subscribers = $this->getSubscribers();
$subscriberCustomFields = $this->getSubscriberCustomFields();
$formattedSubscriberFields = $this->formatSubscriberFields(
$this->subscriberFields,
$subscriberCustomFields
);
try {
if($this->exportFormatOption === 'csv') {
$CSVFile = fopen($this->exportFile, 'w');
$formatCSV = function ($row) {
return '"' . str_replace('"', '\"', $row) . '"';
};
// add UTF-8 BOM (3 bytes, hex EF BB BF) at the start of the file for Excel to automatically recognize the encoding
// add UTF-8 BOM (3 bytes, hex EF BB BF) at the start of the file for
// Excel to automatically recognize the encoding
fwrite($CSVFile, chr(0xEF) . chr(0xBB) . chr(0xBF));
if($this->groupBySegmentOption) $formattedSubscriberFields[] = __('List');
fwrite($CSVFile, implode(",", array_map($formatCSV, $formattedSubscriberFields)) . "\n");
if($this->groupBySegmentOption) {
$formattedSubscriberFields[] = __('List');
}
fwrite(
$CSVFile,
implode(
',',
array_map(
$formatCSV,
$formattedSubscriberFields
)
) . "\n"
);
foreach ($subscribers as $subscriber) {
$row = array_map(function ($field) use ($subscriber) {
return $subscriber[$field];
}, $this->subscriberFields);
if($this->groupBySegmentOption) $row[] = $subscriber['segment_name'];
fwrite($CSVFile, implode(",", array_map($formatCSV, $row)) . "\n");
$row = $this->formatSubscriberData(
$subscriber,
$formattedSubscriberFields
);
if($this->groupBySegmentOption) {
$row[] = ucwords($subscriber['segment_name']);
}
fwrite($CSVFile, implode(',', array_map($formatCSV, $row)) . "\n");
}
fclose($CSVFile);
} else {
$writer = new XLSXWriter();
$writer->setAuthor('MailPoet (www.mailpoet.com)');
$headerRow = array($formattedSubscriberFields);
$segment = null;
$lastSegment = false;
$rows = array();
foreach ($subscribers as $subscriber) {
if($segment && $segment != $subscriber['segment_name'] && $this->groupBySegmentOption) {
$writer->writeSheet(array_merge($headerRow, $rows), ucwords($segment));
if($lastSegment && $lastSegment !== $subscriber['segment_name'] &&
$this->groupBySegmentOption
) {
$writer->writeSheet(
array_merge($headerRow, $rows), ucwords($lastSegment)
);
$rows = array();
}
// detect RTL language and set Excel to properly display the sheet
if(!$writer->rtl && preg_grep('/\p{Arabic}|\p{Hebrew}/u', $subscriber)) {
$RTLRegex = '/\p{Arabic}|\p{Hebrew}/u';
if(!$writer->rtl && (
preg_grep($RTLRegex, $subscriber) ||
preg_grep($RTLRegex, $formattedSubscriberFields))
) {
$writer->rtl = true;
}
$rows[] = array_map(function ($field) use ($subscriber) {
return $subscriber[$field];
}, $this->subscriberFields);
$segment = $subscriber['segment_name'];
$rows[] = $this->formatSubscriberData(
$subscriber,
$formattedSubscriberFields
);
$lastSegment = $subscriber['segment_name'];
}
$writer->writeSheet(array_merge($headerRow, $rows), 'MailPoet');
$writer->writeSheet(
array_merge($headerRow, $rows),
($this->groupBySegmentOption) ?
ucwords($subscriber['segment_name']) :
'MailPoet'
);
$writer->writeToFile($this->exportFile);
}
} catch (Exception $e) {
@ -107,20 +111,108 @@ class Export {
'data' => array(
'totalExported' => count($subscribers),
'exportFileURL' => $this->exportFileURL
)
),
'profiler' => $this->timeExecution()
);
}
function formatSubscriberFields($subscriberFields) {
function getSubscribers() {
$subscribers = Subscriber::
left_outer_join(
SubscriberSegment::$_table,
array(
Subscriber::$_table . '.id',
'=',
SubscriberSegment::$_table . '.subscriber_id'
))
->left_outer_join(
Segment::$_table,
array(
Segment::$_table . '.id',
'=',
SubscriberSegment::$_table . '.segment_id'
))
->orderByAsc('segment_name')
->filter('filterWithCustomFieldsForExport');
if($this->subscribersWithoutSegment !== false) {
$subscribers = $subscribers
->selectExpr('CASE WHEN ' . Segment::$_table . '.name IS NOT NULL ' .
'THEN ' . Segment::$_table . '.name ' .
'ELSE "' . __('Not In List') . '" END as segment_name'
)
->whereRaw(
SubscriberSegment::$_table . '.segment_id IN (' .
rtrim(str_repeat('?,', count($this->segments)), ',') . ') ' .
'OR ' . SubscriberSegment::$_table . '.segment_id IS NULL ',
$this->segments
);
} else {
$subscribers = $subscribers
->select(Segment::$_table . '.name', 'segment_name')
->whereIn(SubscriberSegment::$_table . '.segment_id', $this->segments);
}
if(!$this->groupBySegmentOption) {
$subscribers =
$subscribers->groupBy(Subscriber::$_table . '.id');
}
if($this->exportConfirmedOption) {
$subscribers =
$subscribers->where(Subscriber::$_table . '.status', 'subscribed');
}
$subscribers = $subscribers->whereNull(Subscriber::$_table . '.deleted_at');
return $subscribers->findArray();
}
function getExportFileURL($file) {
return sprintf(
'%s/%s/%s',
Env::$plugin_url,
Env::$temp_name,
basename($file)
);
}
function getExportFile($format) {
return sprintf(
Env::$temp_path . '/MailPoet_export_%s.%s',
substr(md5(time()), 0, 4),
$format
);
}
function getSubscriberCustomFields() {
return Helpers::arrayColumn(
CustomField::findArray(),
'name',
'id'
);
}
function formatSubscriberFields($subscriberFields, $subscriberCustomFields) {
$bootStrapMenu = new BootStrapMenu();
$translatedFields = $bootStrapMenu->getSubscriberFields();
return array_map(function ($field) use ($translatedFields) {
return (isset($translatedFields[$field])) ?
return array_map(function ($field) use (
$translatedFields, $subscriberCustomFields
) {
$field = (isset($translatedFields[$field])) ?
ucfirst($translatedFields[$field]) :
ucfirst($field);
return (isset($subscriberCustomFields[$field])) ?
$subscriberCustomFields[$field] : $field;
}, $subscriberFields);
}
function formatSubscriberData($subscriber, $subscriberCustomFields) {
return array_map(function ($field) use (
$subscriber, $subscriberCustomFields
) {
return (isset($subscriberCustomFields[$field])) ?
$subscriberCustomFields[$field] :
$subscriber[$field];
}, $this->subscriberFields);
}
function timeExecution() {
$profilerEnd = microtime(true);
return ($profilerEnd - $this->profilerStart) / 60;

View File

@ -1,10 +1,10 @@
<?php
namespace MailPoet\Subscribers\ImportExport\Import;
use MailPoet\Subscribers\ImportExport\BootStrapMenu;
use MailPoet\Models\Subscriber;
use MailPoet\Models\SubscriberCustomField;
use MailPoet\Models\SubscriberSegment;
use MailPoet\Subscribers\ImportExport\BootStrapMenu;
use MailPoet\Util\Helpers;
class Import {
@ -27,7 +27,9 @@ class Import {
$subscriberFields = $this->subscriberFields;
$subscriberCustomFields = $this->subscriberCustomFields;
$subscribersData = $this->subscribersData;
$subscribersData = $this->filterSubscriberStatus($subscribersData);
list ($subscribersData, $subscriberFields) =
$this->filterSubscriberStatus($subscribersData, $subscriberFields);
$this->deleteExistingTrashedSubscribers($subscribersData);
list($subscribersData, $subscriberFields) = $this->extendSubscribersAndFields(
$subscribersData, $subscriberFields
);
@ -48,7 +50,7 @@ class Import {
$updatedSubscribers =
$this->createOrUpdateSubscribers(
'update',
$existingSubscribers,
$existingSubscribers,
$subscriberFields,
$subscriberCustomFields
);
@ -61,7 +63,7 @@ class Import {
);
}
}
} catch (\PDOException $e) {
} catch(\PDOException $e) {
return array(
'result' => false,
'error' => $e->getMessage()
@ -75,7 +77,7 @@ class Import {
'updated' => count($updatedSubscribers),
'segments' => $segments->getSegments()
),
'profile' => $this->timeExecution()
'profiler' => $this->timeExecution()
);
}
@ -84,6 +86,7 @@ class Import {
array_map(function ($subscriberEmails) {
return Subscriber::selectMany(array('email'))
->whereIn('email', $subscriberEmails)
->whereNull('deleted_at')
->findArray();
}, array_chunk($subscribersData['email'], 200))
);
@ -131,6 +134,25 @@ class Import {
);
}
function deleteExistingTrashedSubscribers($subscribersData) {
$existingTrashedRecords = array_filter(
array_map(function ($subscriberEmails) {
return Subscriber::selectMany(array('id'))
->whereIn('email', $subscriberEmails)
->whereNotNull('deleted_at')
->findArray();
}, array_chunk($subscribersData['email'], 200))
);
if(!$existingTrashedRecords) return;
$existingTrashedRecords = Helpers::flattenArray($existingTrashedRecords);
foreach(array_chunk($existingTrashedRecords, 200) as $subscriberIds) {
Subscriber::whereIn('id', $subscriberIds)
->deleteMany();
SubscriberSegment::whereIn('subscriber_id', $subscriberIds)
->deleteMany();
}
}
function extendSubscribersAndFields($subscribersData, $subscriberFields) {
$subscribersData['created_at'] = $this->filterSubscriberCreatedAtDate();
$subscriberFields[] = 'created_at';
@ -164,8 +186,16 @@ class Import {
return array_fill(0, $this->subscribersCount, $this->currentTime);
}
function filterSubscriberStatus($subscribersData) {
if(!in_array('status', $this->subscriberFields)) return;
function filterSubscriberStatus($subscribersData, $subscriberFields) {
if(!in_array('status', $subscriberFields)) {
$subscribersData['status'] =
array_fill(0, count($subscribersData['email']), 'subscribed');
$subscriberFields[] = 'status';
return array(
$subscribersData,
$subscriberFields
);
}
$statuses = array(
'subscribed' => array(
'subscribed',
@ -174,6 +204,11 @@ class Import {
'1',
'true'
),
'unconfirmed' => array(
'unconfirmed',
0,
"0"
),
'unsubscribed' => array(
'unsubscribed',
-1,
@ -183,14 +218,20 @@ class Import {
);
$subscribersData['status'] = array_map(function ($state) use ($statuses) {
if(in_array(strtolower($state), $statuses['subscribed'])) {
return 'confirmed';
return 'subscribed';
}
if(in_array(strtolower($state), $statuses['unsubscribed'])) {
return 'unsubscribed';
}
return 'confirmed'; // make "subscribed" a default status
if(in_array(strtolower($state), $statuses['unconfirmed'])) {
return 'unconfirmed';
}
return 'subscribed'; // make "subscribed" a default status
}, $subscribersData['status']);
return $subscribersData;
return array(
$subscribersData,
$subscriberFields
);
}
function createOrUpdateSubscribers(
@ -206,7 +247,7 @@ class Import {
}, $subscriberFields);
}, range(0, $subscribersCount));
$currentTime = ($action === 'update') ? date('Y-m-d H:i:s') : $this->currentTime;
foreach (array_chunk($subscribers, 200) as $data) {
foreach(array_chunk($subscribers, 100) as $data) {
if($action == 'create') {
Subscriber::createMultiple(
$subscriberFields,
@ -269,7 +310,7 @@ class Import {
);
}, $count, $subscribersData[$column]);
}, $subscriberCustomFields)[0];
foreach (array_chunk($subscribers, 200) as $data) {
foreach(array_chunk($subscribers, 200) as $data) {
if($action === 'create') {
SubscriberCustomField::createMultiple(
$data
@ -284,7 +325,7 @@ class Import {
}
function addSubscribersToSegments($subscribers, $segments) {
foreach (array_chunk($subscribers, 200) as $data) {
foreach(array_chunk($subscribers, 200) as $data) {
SubscriberSegment::createMultiple($segments, $data);
}
}

View File

@ -76,12 +76,18 @@ class Helpers {
static function getMaxPostSize($bytes = false) {
$maxPostSize = ini_get('post_max_size');
if(!$bytes) return $maxPostSize;
switch (substr ($maxPostSize, -1))
{
case 'M': case 'm': return (int)$maxPostSize * 1048576;
case 'K': case 'k': return (int)$maxPostSize * 1024;
case 'G': case 'g': return (int)$maxPostSize * 1073741824;
default: return $maxPostSize;
switch (substr($maxPostSize, -1)) {
case 'M':
case 'm':
return (int) $maxPostSize * 1048576;
case 'K':
case 'k':
return (int) $maxPostSize * 1024;
case 'G':
case 'g':
return (int) $maxPostSize * 1073741824;
default:
return $maxPostSize;
}
}
@ -164,4 +170,18 @@ class Helpers {
}
return $resultArray;
}
static function underscoreToCamelCase($str, $capitalise_first_char = false) {
if($capitalise_first_char) {
$str[0] = strtoupper($str[0]);
}
$func = create_function('$c', 'return strtoupper($c[1]);');
return preg_replace_callback('/_([a-z])/', $func, $str);
}
static function camelCaseToUnderscore($str) {
$str[0] = strtolower($str[0]);
$func = create_function('$c', 'return "_" . strtolower($c[1]);');
return preg_replace_callback('/([A-Z])/', $func, $str);
}
}

View File

@ -1,8 +1,21 @@
<?php
namespace MailPoet\Util;
require_once(ABSPATH . 'wp-includes/pluggable.php');
class Security {
static function generateToken() {
return wp_create_nonce('mailpoet_token');
}
static function generateRandomString($length = 5) {
// non-cryptographically strong random generator
return substr(
md5(
uniqid(
mt_rand(), true)
),
0,
(!is_int($length) || $length <= 5 || $length >= 32) ? 5 : $length);
}
}

View File

@ -5,7 +5,7 @@ if (!defined('ABSPATH')) exit;
/*
* Plugin Name: MailPoet
* Version: 0.0.4
* Version: 0.0.6
* Plugin URI: http://www.mailpoet.com
* Description: MailPoet Newsletters.
* Author: MailPoet
@ -18,12 +18,12 @@ if (!defined('ABSPATH')) exit;
*
* @package WordPress
* @author MailPoet
* @since 0.0.4
* @since 0.0.6
*/
require 'vendor/autoload.php';
define('MAILPOET_VERSION', '0.0.4');
define('MAILPOET_VERSION', '0.0.6');
$initializer = new Initializer(array(
'file' => __FILE__,

View File

@ -10,7 +10,7 @@
},
"dependencies": {
"backbone": "1.2.3",
"backbone.marionette": "2.4.3",
"backbone.marionette": "2.4.4",
"backbone.radio": "0.9.0",
"backbone.supermodel": "1.2.0",
"c3": "~0.4.10",
@ -24,7 +24,7 @@
"moment": "^2.10.3",
"napa": "^1.2.0",
"papaparse": "4.1.1",
"parsley": "^0.1.0",
"parsleyjs": "^2.1.2",
"react": "^0.14.1",
"react-checkbox-group": "0.2.2",
"react-dom": "^0.14.1",

View File

@ -1,7 +1,7 @@
define([
'newsletter_editor/App',
'newsletter_editor/components/wordpress',
'newsletter_editor/blocks/posts',
'newsletter_editor/blocks/posts'
], function(EditorApplication, WordpressComponent, PostsBlock) {
describe('Posts', function () {
@ -373,12 +373,7 @@ define([
});
describe('when "title as list" is selected', function() {
var model, view;
beforeEach(function() {
model = new (PostsBlock.PostsBlockModel)();
model.request = sinon.stub().returns({$el: {}});
view = new (PostsBlock.PostsBlockSettingsView)({model: model});
view.render();
view.$('.mailpoet_posts_display_type').val('titleOnly').change();
view.$('.mailpoet_posts_title_format').val('ul').change();
});

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