Compare commits

..

106 Commits

Author SHA1 Message Date
d25070829d Bump up release version to 0.0.29 2016-05-20 18:49:10 +03:00
d194502b27 Merge pull request #485 from mailpoet/standard_scheduling_fix
Prevent user from manually editing Standard newsletter scheduling date
2016-05-20 15:09:04 +02:00
2cef99de2b Merge pull request #484 from mailpoet/re_adding_localestring
Re-adding toLocaleString on Segments listing
2016-05-20 08:50:02 -04:00
05d3756a1c Change readOnly field to boolean type 2016-05-20 15:27:46 +03:00
b3d9fc54fe Prevent user from manually editing standard scheduling date 2016-05-20 14:19:38 +03:00
20841eb5e8 Re-adding toLocaleString on Segments listing 2016-05-20 09:25:40 +02:00
9e23326d45 Merge pull request #483 from mailpoet/react_forms
Forms / WP Sync
2016-05-19 10:20:47 -04:00
f81d639b19 Merge pull request #482 from mailpoet/export_fix
Export fix
2016-05-19 15:49:12 +02:00
ab3e272020 extra space 2016-05-19 15:40:14 +02:00
4c265d1339 convert some React fields to ES6
- renamed empty label to placeholder
2016-05-19 15:19:31 +02:00
da08de0e74 - Fixes unit test
- Enables check for ZIP extension
2016-05-19 09:10:26 -04:00
6074aa927b Fix WP Sync
- added missing translations in listings (responsive view)
2016-05-19 14:32:40 +02:00
046127eeba fixed react forms (new bug discovered on new forms with default values not saved) 2016-05-19 14:30:33 +02:00
0c8a8c6854 Merge pull request #473 from mailpoet/standard_scheduling
Standard newsletter scheduling
2016-05-19 13:56:44 +03:00
acfcfdd730 - Refactors scheduler worker and updates logic responsible for scheduling
a new post notification queue
2016-05-18 15:40:41 -04:00
8f935df12a Merge branch 'standard_scheduling' of mailpoet:mailpoet/mailpoet into standard_scheduling 2016-05-18 14:37:11 -04:00
2784bb7282 - Adds queue rescheduling for post notifications 2016-05-18 14:33:55 -04:00
08a8ca4969 Change segments handling for notification newsletters 2016-05-18 21:27:58 +03:00
5a03eb9a17 Fix newsletter calls to use array access and not object access 2016-05-18 19:03:06 +03:00
91bb215e4d Change Date picker to use Wordpress date format 2016-05-18 18:38:59 +03:00
0b3a388a78 - Remove space after if;
- Change Scheduler::standard function name to a more descriptive one
2016-05-18 18:38:58 +03:00
66a93768e1 Remove unnecessary function calls 2016-05-18 18:14:54 +03:00
61df4899cd - Refactor WP DateTime helper
- Add a unit test helper to stub out Wordpress functions
2016-05-18 18:14:54 +03:00
f8b1e153be Make date picker translatable 2016-05-18 18:14:53 +03:00
f322433875 Add standard newsletter scheduling backend 2016-05-18 18:14:53 +03:00
be155c38bc Fix datepicker onChange handler 2016-05-18 17:51:39 +03:00
13ee338fb0 Add jQuery UI datepicker with Melon skin 2016-05-18 17:51:39 +03:00
f8628c1f4e - Add input validation
- Unmount datepicker on destruction
- Change prop propagation pipeline
2016-05-18 17:51:39 +03:00
f7c70be5eb Add standard newsletter scheduling UI 2016-05-18 17:51:39 +03:00
65df28d52e Merge pull request #478 from mailpoet/post_notification_update
Post notification update
2016-05-18 16:10:23 +02:00
3e658033da - Updates conditional check 2016-05-18 10:00:38 -04:00
31e082eb2b - Removes space between IF and statement 2016-05-18 10:00:38 -04:00
bf1ab3a593 - Removes space between (int) and variable 2016-05-18 10:00:38 -04:00
8540c51679 - Changes newsletter object to array in SendingQueue router
- Schedules post notification right after its creation
2016-05-18 10:00:09 -04:00
d2a6b6bd4e - Updates based on code review comments 2016-05-18 10:00:09 -04:00
06417c1e88 - Fix ALC post amount handling of boundary values;
- Change magic number to named constant
2016-05-18 10:00:09 -04:00
be238f4c67 - Fixes unit test
- Corrects method name in scheduler
2016-05-18 10:00:09 -04:00
343da0fdcc - Saves sent posts during rendering by sending queue worker
- Prevents empty notification emails from being sent
- Hooks to WP's post update and rewrite post notification logic
- Prevents scheduling multiple queues of the same newsletter
- Fixes issue with segments not updating when scheduling a newsletter
- Removes depreciated hash field & associated logic
2016-05-18 10:00:09 -04:00
dbb3c96300 Modify ALC to skip already sent posts 2016-05-18 10:00:09 -04:00
c8f7bea419 Let wordpress exclude already used posts in ALC selection 2016-05-18 10:00:09 -04:00
396ab50fa0 - Implements exclusion of duplicate posts from ALC 2016-05-18 10:00:09 -04:00
84e9ecef29 Merge pull request #477 from mailpoet/php7_mysql_fix
Fix for PHP7 & Mysql 5.7
2016-05-18 15:42:36 +02:00
57472d1a2e - Removes comment for an explicitly invoked condition 2016-05-18 09:39:29 -04:00
cb4f055263 - Updates export SQL to always use GROUP BY and aggregate columns 2016-05-17 11:07:12 -04:00
3f1bdd2c59 - Removed debug function
- Corrected comment
2016-05-17 10:01:06 -04:00
a6802a1925 - Fixes GROUP BY when using non-aggregate columns 2016-05-17 09:54:58 -04:00
a02b2d3aa0 - Updates migrator/schema to work with MySQL 5.7
- Fixes unit tests
- Fixes export's SQL query to work with strict ONLY_FULL_GROUP_BY option
2016-05-16 22:26:21 -04:00
607a151c23 Fix for PHP7 & Mysql 5.7
- added default values to not null columns
- fixed passing by reference issue on MailChimp->getDataCenter
- fixed a couple unit tests
2016-05-16 14:50:43 +02:00
c8748e5e6e Merge pull request #475 from mailpoet/uniform_listings
bulk actions / pagination and customizable limit per page for all
2016-05-16 13:37:14 +03:00
6e45a8e788 all listings have bulk actions / pagination and customizable limit per page 2016-05-13 16:53:48 +02:00
395e95bc54 Bump up release version to 0.0.28 2016-05-13 16:18:01 +03:00
a8309b436d Merge pull request #474 from mailpoet/UI_updates
Ui updates
2016-05-13 16:14:23 +03:00
77fe385645 - Disables input fields if subscriber is a WP user. #421 (5)
- Removes 'unconfirmed' status if subscriber is a WP user. #421 (9)
- Displays notice if subscriber is a WP user. #421 (6)
2016-05-12 19:38:34 -04:00
290f749220 - Renames "Update" link to "Force Sync" #421 (12)
- Adds new link "Read more" #421 (13)
2016-05-12 11:48:33 -04:00
8cb5b3729d Merge pull request #472 from mailpoet/UI_updates
Expands MailPoet admin menu for sub-subpages
2016-05-12 17:40:04 +03:00
ce4a3196ee - Highlight main MailPoet menu
- Expands menu for newsletter editor
2016-05-12 10:37:56 -04:00
40ea5a0b84 - Improves menu expansion code
::
2016-05-12 09:59:05 -04:00
af95380a62 - Expands MailPoet admin menu for sub-subpages. #431 2016-05-12 09:10:35 -04:00
483eaffc0e Merge pull request #471 from mailpoet/import_update
Import udate
2016-05-12 12:40:23 +03:00
7e86ee3266 Merge pull request #470 from mailpoet/UI_updates
Various UI fixes
2016-05-12 12:15:52 +03:00
65630e6726 - Formats numbers in export 2016-05-11 15:47:47 -04:00
23682011af - Prevents WP user's first/last name to be updated during import
- Resets "next step" button state when going to step 3 of import
- Closes #469
2016-05-11 14:44:29 -04:00
00eaa768a6 - Updates Segment model to return subscriber count only if subscriber's
status is "subscribed"
2016-05-11 13:06:26 -04:00
bc92d9a61e - Updates all datetime references to use WordPress format
- Removes depreciated datetime conversion method from Helpers
- Fixes translation & error display issue in import
Closes #432
2016-05-11 10:56:34 -04:00
a1d8dec047 - Properly formats subscriber count during import 2016-05-11 10:18:59 -04:00
ff030068b0 Merge pull request #468 from mailpoet/UI_updates
Various UI fixes
2016-05-11 16:39:28 +03:00
79e37018c9 - Fixes unit test
- Updates Settings page heading to h1
2016-05-11 08:56:37 -04:00
7ae8248339 Merge branch 'UI_updates' of mailpoet:mailpoet/mailpoet into UI_updates 2016-05-11 08:52:38 -04:00
30720975ab - Disables "confirm unconfirmed" batch action as per #432 2016-05-11 08:52:13 -04:00
b2359258d4 Update newsletter sending process to format numbers >1000 2016-05-11 11:47:23 +03:00
1bd7639cc2 - Formats all numbers >1000 to use comma
- Removes subscriber count from segments if its === 0
Fixes #431
2016-05-10 20:09:02 -04:00
2dab89135f - Sets MailPoet notice timeout to 5sec
- Removes custom timeout from import
2016-05-10 18:59:31 -04:00
c3368d69fd - Updates Segment model to return ASC sorted results
- Updates Subscriber listing to display segmnets with count
2016-05-10 18:37:37 -04:00
751d8e7852 - Adds missing page titles 2016-05-10 13:01:40 -04:00
8a4dec08e1 - Converts page title headings to H1
- Adds "back to list" to subpages
2016-05-10 10:13:57 -04:00
8b9f5ad5b1 Merge pull request #467 from mailpoet/cron_fix
- Corrects the daemon stop status
2016-05-10 15:51:53 +03:00
fc597a53bb - Corrects the daemon stop status 2016-05-09 20:28:02 -04:00
2f25dc6a20 Bump up release version to 0.0.27 2016-05-06 14:46:12 +03:00
fc38ee2f08 Merge pull request #466 from mailpoet/rendering_engine_update
Fixes empty paragraph spacing
2016-05-05 13:04:26 +03:00
33bebc6629 - Replaces empty paragraphs with single space according to specific
condition
2016-05-04 19:57:10 -04:00
14a8e02f99 Merge pull request #465 from mailpoet/browser_preview
Newsletter preview
2016-05-04 17:58:41 +03:00
0bf6c87ec7 - Fixes shortcode category name for view in browser url
- Updates shortcode regex
2016-05-04 10:45:30 -04:00
422fba2835 - Updates based on code review comments 2016-05-03 18:41:57 -04:00
f36dbb78e6 - Updates unit test 2016-05-02 13:44:36 -04:00
3213dd0d08 - Removes unused exception handlers 2016-05-02 13:19:10 -04:00
3f2199fd63 - Updates link tracking to use the same Public API data format as other
endpoints
2016-05-02 13:15:48 -04:00
a4477a9bd6 - Updates CONST values 2016-05-02 12:27:00 -04:00
52790d7bd6 - Updates code comment 2016-05-02 12:24:56 -04:00
0b9812210f - Updates CONST values 2016-05-02 11:30:37 -04:00
756dbb4641 - Updates daemon status to CONSTs
- Uncomments temporary commented out code
- Removes unnecessary exception handles in Public API
2016-05-02 10:32:34 -04:00
b38742ddc0 - Changes divider to CONST 2016-05-02 10:02:22 -04:00
49e38549ec - Fixes URLs not being properly generated when tracking is enabled 2016-05-02 10:00:35 -04:00
afcb0a0d7f - Upperases all constants 2016-05-02 09:47:54 -04:00
d18f0e50b5 - Removes debug backtrace
- Removes uncommented queue save
2016-05-02 09:23:37 -04:00
6868a07ead - Moves link saving logic into Links class 2016-05-01 21:55:24 -04:00
cca76d0d97 - Adds custom shortcode processing logic
- Updates shortcode categories code
- Completely rewrites shortcodes unit tess
2016-05-01 14:07:06 -04:00
70fa77d333 - Creates temporary folder if it doesn't exist 2016-04-30 22:38:55 -04:00
412201d965 - Updates unit test 2016-04-30 22:38:39 -04:00
045a92c7d6 - Standardizes variable names 2016-04-30 22:21:03 -04:00
2ba2e3eca5 - Refactors sending queue worker 2016-04-30 22:19:59 -04:00
90c294f60e - Updates editor/router to use the new browser preview class 2016-04-30 22:19:59 -04:00
57b953dd14 - Rewrites shortcode processing class to work with other changes 2016-04-30 22:19:59 -04:00
0f81a8db60 - Updates method to utilize constant value 2016-04-30 22:19:59 -04:00
2d6971f8df - Refactors link tracking class 2016-04-30 22:19:59 -04:00
0abe8b5371 - Implements view in browser 2016-04-30 22:19:59 -04:00
5bad682879 - Merges all shortcode links under one category
- Adds filter hooks for custom links
- Renames old shortcodes in the editor and elsewhere
2016-04-30 22:19:59 -04:00
145 changed files with 3618 additions and 1400 deletions

View File

@ -1,6 +1,7 @@
@import 'nib'
@require 'select2/dist/css/select2.css'
@require 'datepicker/datepicker'
@require 'common'
@require 'modal'
@ -18,4 +19,4 @@
@require 'settings'
@require 'progress_bar'
@require 'subscribers'
@require 'subscribers'

View File

@ -0,0 +1,2 @@
@require 'jquery-ui-1.10.1.css'
@require 'melon.datepicker.css'

View File

@ -0,0 +1,649 @@
/*! jQuery UI - v1.10.1 - 2013-03-10
* http://jqueryui.com
* Includes: jquery.ui.core.css, jquery.ui.datepicker.css
* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS%2CTahoma%2CVerdana%2CArial%2Csans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=gloss_wave&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=highlight_soft&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=glass&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=glass&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=highlight_soft&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=diagonals_thick&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=diagonals_thick&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=flat&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px
* Copyright (c) 2013 jQuery Foundation and other contributors Licensed MIT */
/* Layout helpers
----------------------------------*/
.ui-helper-hidden {
display: none;
}
.ui-helper-hidden-accessible {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
.ui-helper-reset {
margin: 0;
padding: 0;
border: 0;
outline: 0;
line-height: 1.3;
text-decoration: none;
font-size: 100%;
list-style: none;
}
.ui-helper-clearfix:before,
.ui-helper-clearfix:after {
content: "";
display: table;
border-collapse: collapse;
}
.ui-helper-clearfix:after {
clear: both;
}
.ui-helper-clearfix {
min-height: 0; /* support: IE7 */
}
.ui-helper-zfix {
width: 100%;
height: 100%;
top: 0;
left: 0;
position: absolute;
opacity: 0;
filter:Alpha(Opacity=0);
}
.ui-front {
z-index: 100;
}
/* Interaction Cues
----------------------------------*/
.ui-state-disabled {
cursor: default !important;
}
/* Icons
----------------------------------*/
/* states and images */
.ui-icon {
display: block;
text-indent: -99999px;
overflow: hidden;
background-repeat: no-repeat;
}
/* Misc visuals
----------------------------------*/
/* Overlays */
.ui-widget-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.ui-datepicker {
width: 17em;
padding: .2em .2em 0;
display: none;
}
.ui-datepicker .ui-datepicker-header {
position: relative;
padding: .2em 0;
}
.ui-datepicker .ui-datepicker-prev,
.ui-datepicker .ui-datepicker-next {
position: absolute;
top: 2px;
width: 1.8em;
height: 1.8em;
}
.ui-datepicker .ui-datepicker-prev-hover,
.ui-datepicker .ui-datepicker-next-hover {
top: 1px;
}
.ui-datepicker .ui-datepicker-prev {
left: 2px;
}
.ui-datepicker .ui-datepicker-next {
right: 2px;
}
.ui-datepicker .ui-datepicker-prev-hover {
left: 1px;
}
.ui-datepicker .ui-datepicker-next-hover {
right: 1px;
}
.ui-datepicker .ui-datepicker-prev span,
.ui-datepicker .ui-datepicker-next span {
display: block;
position: absolute;
left: 50%;
margin-left: -8px;
top: 50%;
margin-top: -8px;
}
.ui-datepicker .ui-datepicker-title {
margin: 0 2.3em;
line-height: 1.8em;
text-align: center;
}
.ui-datepicker .ui-datepicker-title select {
font-size: 1em;
margin: 1px 0;
}
.ui-datepicker select.ui-datepicker-month-year {
width: 100%;
}
.ui-datepicker select.ui-datepicker-month,
.ui-datepicker select.ui-datepicker-year {
width: 49%;
}
.ui-datepicker table {
width: 100%;
font-size: .9em;
border-collapse: collapse;
margin: 0 0 .4em;
}
.ui-datepicker th {
padding: .7em .3em;
text-align: center;
font-weight: bold;
border: 0;
}
.ui-datepicker td {
border: 0;
padding: 1px;
}
.ui-datepicker td span,
.ui-datepicker td a {
display: block;
padding: .2em;
text-align: right;
text-decoration: none;
}
.ui-datepicker .ui-datepicker-buttonpane {
background-image: none;
margin: .7em 0 0 0;
padding: 0 .2em;
border-left: 0;
border-right: 0;
border-bottom: 0;
}
.ui-datepicker .ui-datepicker-buttonpane button {
float: right;
margin: .5em .2em .4em;
cursor: pointer;
padding: .2em .6em .3em .6em;
width: auto;
overflow: visible;
}
.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current {
float: left;
}
/* with multiple calendars */
.ui-datepicker.ui-datepicker-multi {
width: auto;
}
.ui-datepicker-multi .ui-datepicker-group {
float: left;
}
.ui-datepicker-multi .ui-datepicker-group table {
width: 95%;
margin: 0 auto .4em;
}
.ui-datepicker-multi-2 .ui-datepicker-group {
width: 50%;
}
.ui-datepicker-multi-3 .ui-datepicker-group {
width: 33.3%;
}
.ui-datepicker-multi-4 .ui-datepicker-group {
width: 25%;
}
.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,
.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header {
border-left-width: 0;
}
.ui-datepicker-multi .ui-datepicker-buttonpane {
clear: left;
}
.ui-datepicker-row-break {
clear: both;
width: 100%;
font-size: 0;
}
/* RTL support */
.ui-datepicker-rtl {
direction: rtl;
}
.ui-datepicker-rtl .ui-datepicker-prev {
right: 2px;
left: auto;
}
.ui-datepicker-rtl .ui-datepicker-next {
left: 2px;
right: auto;
}
.ui-datepicker-rtl .ui-datepicker-prev:hover {
right: 1px;
left: auto;
}
.ui-datepicker-rtl .ui-datepicker-next:hover {
left: 1px;
right: auto;
}
.ui-datepicker-rtl .ui-datepicker-buttonpane {
clear: right;
}
.ui-datepicker-rtl .ui-datepicker-buttonpane button {
float: left;
}
.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,
.ui-datepicker-rtl .ui-datepicker-group {
float: right;
}
.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,
.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header {
border-right-width: 0;
border-left-width: 1px;
}
/* Component containers
----------------------------------*/
.ui-widget {
font-family: Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;
font-size: 1.1em;
}
.ui-widget .ui-widget {
font-size: 1em;
}
.ui-widget input,
.ui-widget select,
.ui-widget textarea,
.ui-widget button {
font-family: Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;
font-size: 1em;
}
.ui-widget-content {
border: 1px solid #dddddd;
background: #eeeeee url(../img/datepicker/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x;
color: #333333;
}
.ui-widget-content a {
color: #333333;
}
.ui-widget-header {
border: 1px solid #e78f08;
background: #f6a828 url(../img/datepicker/ui-bg_gloss-wave_35_f6a828_500x100.png) 50% 50% repeat-x;
color: #ffffff;
font-weight: bold;
}
.ui-widget-header a {
color: #ffffff;
}
/* Interaction states
----------------------------------*/
.ui-state-default,
.ui-widget-content .ui-state-default,
.ui-widget-header .ui-state-default {
border: 1px solid #cccccc;
background: #f6f6f6 url(../img/datepicker/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x;
font-weight: bold;
color: #1c94c4;
}
.ui-state-default a,
.ui-state-default a:link,
.ui-state-default a:visited {
color: #1c94c4;
text-decoration: none;
}
.ui-state-hover,
.ui-widget-content .ui-state-hover,
.ui-widget-header .ui-state-hover,
.ui-state-focus,
.ui-widget-content .ui-state-focus,
.ui-widget-header .ui-state-focus {
border: 1px solid #fbcb09;
background: #fdf5ce url(../img/datepicker/ui-bg_glass_100_fdf5ce_1x400.png) 50% 50% repeat-x;
font-weight: bold;
color: #c77405;
}
.ui-state-hover a,
.ui-state-hover a:hover,
.ui-state-hover a:link,
.ui-state-hover a:visited {
color: #c77405;
text-decoration: none;
}
.ui-state-active,
.ui-widget-content .ui-state-active,
.ui-widget-header .ui-state-active {
border: 1px solid #fbd850;
background: #ffffff url(../img/datepicker/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x;
font-weight: bold;
color: #eb8f00;
}
.ui-state-active a,
.ui-state-active a:link,
.ui-state-active a:visited {
color: #eb8f00;
text-decoration: none;
}
/* Interaction Cues
----------------------------------*/
.ui-state-highlight,
.ui-widget-content .ui-state-highlight,
.ui-widget-header .ui-state-highlight {
border: 1px solid #fed22f;
background: #ffe45c url(../img/datepicker/ui-bg_highlight-soft_75_ffe45c_1x100.png) 50% top repeat-x;
color: #363636;
}
.ui-state-highlight a,
.ui-widget-content .ui-state-highlight a,
.ui-widget-header .ui-state-highlight a {
color: #363636;
}
.ui-state-error,
.ui-widget-content .ui-state-error,
.ui-widget-header .ui-state-error {
border: 1px solid #cd0a0a;
background: #b81900 url(../img/datepicker/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat;
color: #ffffff;
}
.ui-state-error a,
.ui-widget-content .ui-state-error a,
.ui-widget-header .ui-state-error a {
color: #ffffff;
}
.ui-state-error-text,
.ui-widget-content .ui-state-error-text,
.ui-widget-header .ui-state-error-text {
color: #ffffff;
}
.ui-priority-primary,
.ui-widget-content .ui-priority-primary,
.ui-widget-header .ui-priority-primary {
font-weight: bold;
}
.ui-priority-secondary,
.ui-widget-content .ui-priority-secondary,
.ui-widget-header .ui-priority-secondary {
opacity: .7;
filter:Alpha(Opacity=70);
font-weight: normal;
}
.ui-state-disabled,
.ui-widget-content .ui-state-disabled,
.ui-widget-header .ui-state-disabled {
opacity: .35;
filter:Alpha(Opacity=35);
background-image: none;
}
.ui-state-disabled .ui-icon {
filter:Alpha(Opacity=35); /* For IE8 - See #6059 */
}
/* Icons
----------------------------------*/
/* states and images */
.ui-icon {
width: 16px;
height: 16px;
background-position: 16px 16px;
}
.ui-icon,
.ui-widget-content .ui-icon {
background-image: url(../img/datepicker/ui-icons_222222_256x240.png);
}
.ui-widget-header .ui-icon {
background-image: url(../img/datepicker/ui-icons_ffffff_256x240.png);
}
.ui-state-default .ui-icon {
background-image: url(../img/datepicker/ui-icons_ef8c08_256x240.png);
}
.ui-state-hover .ui-icon,
.ui-state-focus .ui-icon {
background-image: url(../img/datepicker/ui-icons_ef8c08_256x240.png);
}
.ui-state-active .ui-icon {
background-image: url(../img/datepicker/ui-icons_ef8c08_256x240.png);
}
.ui-state-highlight .ui-icon {
background-image: url(../img/datepicker/ui-icons_228ef1_256x240.png);
}
.ui-state-error .ui-icon,
.ui-state-error-text .ui-icon {
background-image: url(../img/datepicker/ui-icons_ffd27a_256x240.png);
}
/* positioning */
.ui-icon-carat-1-n { background-position: 0 0; }
.ui-icon-carat-1-ne { background-position: -16px 0; }
.ui-icon-carat-1-e { background-position: -32px 0; }
.ui-icon-carat-1-se { background-position: -48px 0; }
.ui-icon-carat-1-s { background-position: -64px 0; }
.ui-icon-carat-1-sw { background-position: -80px 0; }
.ui-icon-carat-1-w { background-position: -96px 0; }
.ui-icon-carat-1-nw { background-position: -112px 0; }
.ui-icon-carat-2-n-s { background-position: -128px 0; }
.ui-icon-carat-2-e-w { background-position: -144px 0; }
.ui-icon-triangle-1-n { background-position: 0 -16px; }
.ui-icon-triangle-1-ne { background-position: -16px -16px; }
.ui-icon-triangle-1-e { background-position: -32px -16px; }
.ui-icon-triangle-1-se { background-position: -48px -16px; }
.ui-icon-triangle-1-s { background-position: -64px -16px; }
.ui-icon-triangle-1-sw { background-position: -80px -16px; }
.ui-icon-triangle-1-w { background-position: -96px -16px; }
.ui-icon-triangle-1-nw { background-position: -112px -16px; }
.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
.ui-icon-arrow-1-n { background-position: 0 -32px; }
.ui-icon-arrow-1-ne { background-position: -16px -32px; }
.ui-icon-arrow-1-e { background-position: -32px -32px; }
.ui-icon-arrow-1-se { background-position: -48px -32px; }
.ui-icon-arrow-1-s { background-position: -64px -32px; }
.ui-icon-arrow-1-sw { background-position: -80px -32px; }
.ui-icon-arrow-1-w { background-position: -96px -32px; }
.ui-icon-arrow-1-nw { background-position: -112px -32px; }
.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
.ui-icon-arrow-4 { background-position: 0 -80px; }
.ui-icon-arrow-4-diag { background-position: -16px -80px; }
.ui-icon-extlink { background-position: -32px -80px; }
.ui-icon-newwin { background-position: -48px -80px; }
.ui-icon-refresh { background-position: -64px -80px; }
.ui-icon-shuffle { background-position: -80px -80px; }
.ui-icon-transfer-e-w { background-position: -96px -80px; }
.ui-icon-transferthick-e-w { background-position: -112px -80px; }
.ui-icon-folder-collapsed { background-position: 0 -96px; }
.ui-icon-folder-open { background-position: -16px -96px; }
.ui-icon-document { background-position: -32px -96px; }
.ui-icon-document-b { background-position: -48px -96px; }
.ui-icon-note { background-position: -64px -96px; }
.ui-icon-mail-closed { background-position: -80px -96px; }
.ui-icon-mail-open { background-position: -96px -96px; }
.ui-icon-suitcase { background-position: -112px -96px; }
.ui-icon-comment { background-position: -128px -96px; }
.ui-icon-person { background-position: -144px -96px; }
.ui-icon-print { background-position: -160px -96px; }
.ui-icon-trash { background-position: -176px -96px; }
.ui-icon-locked { background-position: -192px -96px; }
.ui-icon-unlocked { background-position: -208px -96px; }
.ui-icon-bookmark { background-position: -224px -96px; }
.ui-icon-tag { background-position: -240px -96px; }
.ui-icon-home { background-position: 0 -112px; }
.ui-icon-flag { background-position: -16px -112px; }
.ui-icon-calendar { background-position: -32px -112px; }
.ui-icon-cart { background-position: -48px -112px; }
.ui-icon-pencil { background-position: -64px -112px; }
.ui-icon-clock { background-position: -80px -112px; }
.ui-icon-disk { background-position: -96px -112px; }
.ui-icon-calculator { background-position: -112px -112px; }
.ui-icon-zoomin { background-position: -128px -112px; }
.ui-icon-zoomout { background-position: -144px -112px; }
.ui-icon-search { background-position: -160px -112px; }
.ui-icon-wrench { background-position: -176px -112px; }
.ui-icon-gear { background-position: -192px -112px; }
.ui-icon-heart { background-position: -208px -112px; }
.ui-icon-star { background-position: -224px -112px; }
.ui-icon-link { background-position: -240px -112px; }
.ui-icon-cancel { background-position: 0 -128px; }
.ui-icon-plus { background-position: -16px -128px; }
.ui-icon-plusthick { background-position: -32px -128px; }
.ui-icon-minus { background-position: -48px -128px; }
.ui-icon-minusthick { background-position: -64px -128px; }
.ui-icon-close { background-position: -80px -128px; }
.ui-icon-closethick { background-position: -96px -128px; }
.ui-icon-key { background-position: -112px -128px; }
.ui-icon-lightbulb { background-position: -128px -128px; }
.ui-icon-scissors { background-position: -144px -128px; }
.ui-icon-clipboard { background-position: -160px -128px; }
.ui-icon-copy { background-position: -176px -128px; }
.ui-icon-contact { background-position: -192px -128px; }
.ui-icon-image { background-position: -208px -128px; }
.ui-icon-video { background-position: -224px -128px; }
.ui-icon-script { background-position: -240px -128px; }
.ui-icon-alert { background-position: 0 -144px; }
.ui-icon-info { background-position: -16px -144px; }
.ui-icon-notice { background-position: -32px -144px; }
.ui-icon-help { background-position: -48px -144px; }
.ui-icon-check { background-position: -64px -144px; }
.ui-icon-bullet { background-position: -80px -144px; }
.ui-icon-radio-on { background-position: -96px -144px; }
.ui-icon-radio-off { background-position: -112px -144px; }
.ui-icon-pin-w { background-position: -128px -144px; }
.ui-icon-pin-s { background-position: -144px -144px; }
.ui-icon-play { background-position: 0 -160px; }
.ui-icon-pause { background-position: -16px -160px; }
.ui-icon-seek-next { background-position: -32px -160px; }
.ui-icon-seek-prev { background-position: -48px -160px; }
.ui-icon-seek-end { background-position: -64px -160px; }
.ui-icon-seek-start { background-position: -80px -160px; }
/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
.ui-icon-seek-first { background-position: -80px -160px; }
.ui-icon-stop { background-position: -96px -160px; }
.ui-icon-eject { background-position: -112px -160px; }
.ui-icon-volume-off { background-position: -128px -160px; }
.ui-icon-volume-on { background-position: -144px -160px; }
.ui-icon-power { background-position: 0 -176px; }
.ui-icon-signal-diag { background-position: -16px -176px; }
.ui-icon-signal { background-position: -32px -176px; }
.ui-icon-battery-0 { background-position: -48px -176px; }
.ui-icon-battery-1 { background-position: -64px -176px; }
.ui-icon-battery-2 { background-position: -80px -176px; }
.ui-icon-battery-3 { background-position: -96px -176px; }
.ui-icon-circle-plus { background-position: 0 -192px; }
.ui-icon-circle-minus { background-position: -16px -192px; }
.ui-icon-circle-close { background-position: -32px -192px; }
.ui-icon-circle-triangle-e { background-position: -48px -192px; }
.ui-icon-circle-triangle-s { background-position: -64px -192px; }
.ui-icon-circle-triangle-w { background-position: -80px -192px; }
.ui-icon-circle-triangle-n { background-position: -96px -192px; }
.ui-icon-circle-arrow-e { background-position: -112px -192px; }
.ui-icon-circle-arrow-s { background-position: -128px -192px; }
.ui-icon-circle-arrow-w { background-position: -144px -192px; }
.ui-icon-circle-arrow-n { background-position: -160px -192px; }
.ui-icon-circle-zoomin { background-position: -176px -192px; }
.ui-icon-circle-zoomout { background-position: -192px -192px; }
.ui-icon-circle-check { background-position: -208px -192px; }
.ui-icon-circlesmall-plus { background-position: 0 -208px; }
.ui-icon-circlesmall-minus { background-position: -16px -208px; }
.ui-icon-circlesmall-close { background-position: -32px -208px; }
.ui-icon-squaresmall-plus { background-position: -48px -208px; }
.ui-icon-squaresmall-minus { background-position: -64px -208px; }
.ui-icon-squaresmall-close { background-position: -80px -208px; }
.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
/* Misc visuals
----------------------------------*/
/* Corner radius */
.ui-corner-all,
.ui-corner-top,
.ui-corner-left,
.ui-corner-tl {
border-top-left-radius: 4px;
}
.ui-corner-all,
.ui-corner-top,
.ui-corner-right,
.ui-corner-tr {
border-top-right-radius: 4px;
}
.ui-corner-all,
.ui-corner-bottom,
.ui-corner-left,
.ui-corner-bl {
border-bottom-left-radius: 4px;
}
.ui-corner-all,
.ui-corner-bottom,
.ui-corner-right,
.ui-corner-br {
border-bottom-right-radius: 4px;
}
/* Overlays */
.ui-widget-overlay {
background: #666666 url(../img/datepicker/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat;
opacity: .5;
filter: Alpha(Opacity=50);
}
.ui-widget-shadow {
margin: -5px 0 0 -5px;
padding: 5px;
background: #000000 url(../img/datepicker/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x;
opacity: .2;
filter: Alpha(Opacity=20);
border-radius: 5px;
}

View File

@ -0,0 +1,115 @@
/**
* Melon skin from: https://github.com/rtsinani/jquery-datepicker-skins
*/
.wp-admin {
font-size: 90%;
}
.wp-admin .ui-widget {
font-family: "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif;
background: #2e3641;
border: none;
border-radius: 0;
-webkit-border-radius: 0;
-moz-border-radius: 0;
}
.wp-admin .ui-datepicker {
padding: 0;
}
.wp-admin .ui-datepicker-header {
border: none;
background: transparent;
font-weight: normal;
font-size: 15px;
}
.wp-admin .ui-datepicker-header .ui-state-hover {
background: transparent;
border-color: transparent;
cursor: pointer;
border-radius: 0;
-webkit-border-radius: 0;
-moz-border-radius: 0;
}
.wp-admin .ui-datepicker .ui-datepicker-title {
margin-top: .4em;
margin-bottom: .3em;
color: #e9f0f4;
}
.wp-admin .ui-datepicker .ui-datepicker-prev-hover,
.wp-admin .ui-datepicker .ui-datepicker-next-hover,
.wp-admin .ui-datepicker .ui-datepicker-next,
.wp-admin .ui-datepicker .ui-datepicker-prev {
top: .9em;
border:none;
}
.wp-admin .ui-datepicker .ui-datepicker-prev-hover {
left: 2px;
}
.wp-admin .ui-datepicker .ui-datepicker-next-hover {
right: 2px;
}
.wp-admin .ui-datepicker .ui-datepicker-next span,
.wp-admin .ui-datepicker .ui-datepicker-prev span {
background-image: url(../img/datepicker/ui-icons_ffffff_256x240.png);
background-position: -32px 0;
margin-top: 0;
top: 0;
font-weight: normal;
}
.wp-admin .ui-datepicker .ui-datepicker-prev span {
background-position: -96px 0;
}
.wp-admin .ui-datepicker table {
margin: 0;
}
.wp-admin .ui-datepicker th {
padding: 1em 0;
color: #ccc;
font-size: 13px;
font-weight: normal;
border: none;
border-top: 1px solid #3a414d;
}
.wp-admin .ui-datepicker td {
background: #f97e76;
border: none;
padding: 0;
}
.wp-admin td .ui-state-default {
background: transparent;
border: none;
text-align: center;
padding: .5em;
margin: 0;
font-weight: normal;
color: #efefef;
font-size: 16px;
}
.wp-admin .ui-state-disabled {
opacity: 1;
}
.wp-admin .ui-state-disabled .ui-state-default {
color: #fba49e;
}
.wp-admin td .ui-state-active,
.wp-admin td .ui-state-hover {
background: #2e3641;
}

View File

@ -1,2 +1,5 @@
.mailpoet_form
margin: 0 0 20px 0
.mailpoet_form td
vertical-align: top !important
vertical-align: top !important

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -40,8 +40,17 @@ define('date',
return this;
},
format: function(date, options) {
options = options || {};
this.init(options);
return Moment(date).format(this.convertFormat(this.options.format));
return Moment(date, this.convertFormat(options.parseFormat))
.format(this.convertFormat(this.options.format));
},
toDate: function(date, options) {
options = options || {};
this.init(options);
return Moment(date, this.convertFormat(options.parseFormat)).toDate();
},
short: function(date) {
return this.format(date, {
@ -113,6 +122,8 @@ define('date',
}
};
if (!format || format.length <= 0) return format;
const replacements = format_mappings['date'];
let outputFormat = '';
@ -131,4 +142,4 @@ define('date',
return outputFormat;
}
};
});
});

View File

@ -9,6 +9,13 @@ define([
render() {
const yearsRange = 100;
const years = [];
if (this.props.placeholder !== undefined) {
years.push((
<option value="" key={ 0 }>{ this.props.placeholder }</option>
));
}
const currentYear = Moment().year();
for (let i = currentYear; i >= currentYear - yearsRange; i--) {
years.push((
@ -33,6 +40,13 @@ define([
class FormFieldDateMonth extends React.Component {
render() {
const months = [];
if (this.props.placeholder !== undefined) {
months.push((
<option value="" key={ 0 }>{ this.props.placeholder }</option>
));
}
for (let i = 1; i <= 12; i++) {
months.push((
<option
@ -56,6 +70,13 @@ define([
class FormFieldDateDay extends React.Component {
render() {
const days = [];
if (this.props.placeholder !== undefined) {
days.push((
<option value="" key={ 0 }>{ this.props.placeholder }</option>
));
}
for (let i = 1; i <= 31; i++) {
days.push((
<option
@ -81,9 +102,9 @@ define([
constructor(props) {
super(props);
this.state = {
year: Moment().year(),
month: 1,
day: 1
year: undefined,
month: undefined,
day: undefined
}
}
componentDidMount() {
@ -98,7 +119,6 @@ define([
}
extractTimeStamp() {
const timeStamp = parseInt(this.props.item[this.props.field.name], 10);
this.setState({
year: Moment.unix(timeStamp).year(),
// Moment returns the month as [0..11]
@ -112,7 +132,7 @@ define([
`${this.state.month}/${this.state.day}/${this.state.year}`,
'M/D/YYYY'
).valueOf();
if (!isNaN(newTimeStamp) && parseInt(newTimeStamp, 10) > 0) {
if (~~(newTimeStamp) > 0) {
// convert milliseconds to seconds
newTimeStamp /= 1000;
return this.props.onValueChange({
@ -158,6 +178,7 @@ define([
key={ 'year' }
name={ this.props.field.name }
year={ this.state.year }
placeholder={ this.props.field.year_placeholder }
/>);
break;
@ -169,6 +190,7 @@ define([
name={ this.props.field.name }
month={ this.state.month }
monthNames={ monthNames }
placeholder={ this.props.field.month_placeholder }
/>);
break;
@ -179,6 +201,7 @@ define([
key={ 'day' }
name={ this.props.field.name }
day={ this.state.day }
placeholder={ this.props.field.day_placeholder }
/>);
break;
}

View File

@ -1,40 +1,54 @@
define([
'react'
],
function(
React
) {
const FormFieldSelect = React.createClass({
render: function() {
if (this.props.field.values === undefined) {
return false;
}
import React from 'react'
const options = Object.keys(this.props.field.values).map(
(value, index) => {
return (
<option
key={ 'option-' + index }
value={ value }>
{ this.props.field.values[value] }
</option>
);
}
);
const FormFieldSelect = React.createClass({
render() {
if (this.props.field.values === undefined) {
return false;
}
return (
<select
name={ this.props.field.name }
id={ 'field_'+this.props.field.name }
value={ this.props.item[this.props.field.name] }
onChange={ this.props.onValueChange }
{...this.props.field.validation}
>
{options}
</select>
let filter = false;
let placeholder = false;
if (this.props.field.placeholder !== undefined) {
placeholder = (
<option value="">{ this.props.field.placeholder }</option>
);
}
});
return FormFieldSelect;
});
if (this.props.field['filter'] !== undefined) {
filter = this.props.field.filter;
}
const options = Object.keys(this.props.field.values).map(
(value, index) => {
if (filter !== false && filter(this.props.item, value) === false) {
return;
}
return (
<option
key={ 'option-' + index }
value={ value }>
{ this.props.field.values[value] }
</option>
);
}
);
return (
<select
name={ this.props.field.name }
id={ 'field_'+this.props.field.name }
value={ this.props.item[this.props.field.name] }
onChange={ this.props.onValueChange }
{...this.props.field.validation}
>
{placeholder}
{options}
</select>
);
}
});
module.exports = FormFieldSelect;

View File

@ -1,35 +1,35 @@
define([
'react'
],
function(
React
) {
var FormFieldText = React.createClass({
render: function() {
var value = this.props.item[this.props.field.name];
if(value === undefined) {
value = this.props.field.defaultValue || '';
}
import React from 'react'
return (
<input
type="text"
className={ (this.props.field.size) ? '' : 'regular-text' }
size={
(this.props.field.size !== 'auto' && this.props.field.size > 0)
? this.props.field.size
: false
}
name={ this.props.field.name }
id={ 'field_'+this.props.field.name }
value={ value }
placeholder={ this.props.field.placeholder }
onChange={ this.props.onValueChange }
{...this.props.field.validation}
/>
);
const FormFieldText = React.createClass({
render() {
let value = this.props.item[this.props.field.name];
if (value === undefined) {
value = this.props.field.defaultValue || '';
}
});
return FormFieldText;
return (
<input
type="text"
disabled={
(this.props.field['disabled'] !== undefined)
? this.props.field.disabled(this.props.item)
: false
}
className={ (this.props.field.size) ? '' : 'regular-text' }
size={
(this.props.field.size !== 'auto' && this.props.field.size > 0)
? this.props.field.size
: false
}
name={ this.props.field.name }
id={ 'field_'+this.props.field.name }
value={ value }
placeholder={ this.props.field.placeholder }
onChange={ this.props.onValueChange }
{...this.props.field.validation}
/>
);
}
});
module.exports = FormFieldText;

View File

@ -37,9 +37,13 @@ define(
return this.props.errors ? this.props.errors : this.state.errors;
},
componentDidMount: function() {
if(this.props.params.id !== undefined) {
if(this.isMounted()) {
if(this.isMounted()) {
if(this.props.params.id !== undefined) {
this.loadItem(this.props.params.id);
} else {
this.setState({
item: jQuery('.mailpoet_form').serializeObject()
});
}
}
},
@ -102,7 +106,6 @@ define(
item[field.name] = this.state.item[field.name];
}
}.bind(this));
// set id if specified
if(this.props.params.id !== undefined) {
item.id = this.props.params.id;
@ -167,6 +170,17 @@ define(
{ 'mailpoet_form_loading': this.state.loading || this.props.loading }
);
var beforeFormContent = false;
var afterFormContent = false;
if (this.props.beforeFormContent !== undefined) {
beforeFormContent = this.props.beforeFormContent(this.getValues());
}
if (this.props.afterFormContent !== undefined) {
afterFormContent = this.props.afterFormContent(this.getValues());
}
var fields = this.props.fields.map(function(field, i) {
return (
<FormField
@ -191,26 +205,30 @@ define(
}
return (
<form
id={ this.props.id }
ref="form"
className={ formClasses }
onSubmit={
(this.props.onSubmit !== undefined)
? this.props.onSubmit
: this.handleSubmit
}
>
{ errors }
<div>
{ beforeFormContent }
<form
id={ this.props.id }
ref="form"
className={ formClasses }
onSubmit={
(this.props.onSubmit !== undefined)
? this.props.onSubmit
: this.handleSubmit
}
>
{ errors }
<table className="form-table">
<tbody>
{fields}
</tbody>
</table>
<table className="form-table">
<tbody>
{fields}
</tbody>
</table>
{ actions }
</form>
{ actions }
</form>
{ afterFormContent }
</div>
);
}
});

View File

@ -71,6 +71,14 @@ const messages = {
}
};
const bulk_actions = [
{
name: 'trash',
label: MailPoet.I18n.t('trash'),
onSuccess: messages.onTrash
}
];
const item_actions = [
{
name: 'edit',
@ -141,7 +149,7 @@ const FormList = React.createClass({
{ segments }
</td>
<td className="column-date" data-colname={MailPoet.I18n.t('createdOn')}>
<abbr>{ MailPoet.Date.full(form.created_at) }</abbr>
<abbr>{ MailPoet.Date.format(form.created_at) }</abbr>
</td>
</div>
);
@ -149,23 +157,24 @@ const FormList = React.createClass({
render() {
return (
<div>
<h2 className="title">
<h1 className="title">
{MailPoet.I18n.t('pageTitle')} <a
className="add-new-h2"
className="page-title-action"
href="javascript:;"
onClick={ this.createForm }
>{MailPoet.I18n.t('new')}</a>
</h2>
</h1>
<Listing
limit={ mailpoet_listing_per_page }
location={ this.props.location }
params={ this.props.params }
messages={ messages }
search={ false }
limit={ 1000 }
endpoint="forms"
onRenderItem={ this.renderItem }
columns={ columns }
bulk_actions={ bulk_actions }
item_actions={ item_actions }
/>
</div>

View File

@ -21,7 +21,7 @@ define(['react', 'classnames'], function(React, classNames) {
href="javascript:;"
className={classes}
onClick={this.handleSelect.bind(this, group.name)} >
{group.label} <span className="count">({ group.count })</span>
{group.label} <span className="count">({ group.count.toLocaleString() })</span>
</a>
</li>
);

View File

@ -733,6 +733,7 @@ define(
{ search }
<div className="tablenav top clearfix">
<ListingBulkActions
count={ this.state.count }
bulk_actions={ bulk_actions }
selection={ this.state.selection }
selected_ids={ this.state.selected_ids }
@ -795,6 +796,7 @@ define(
</table>
<div className="tablenav bottom">
<ListingBulkActions
count={ this.state.count }
bulk_actions={ bulk_actions }
selection={ this.state.selection }
selected_ids={ this.state.selected_ids }

View File

@ -148,7 +148,7 @@ define([
className="current-page" />
&nbsp;{MailPoet.I18n.t('pageOutOf')}&nbsp;
<span className="total-pages">
{Math.ceil(this.props.count / this.props.limit)}
{Math.ceil(this.props.count / this.props.limit).toLocaleString()}
</span>
</span>
&nbsp;
@ -167,7 +167,7 @@ define([
return (
<div className={ classes }>
<span className="displaying-num">{
MailPoet.I18n.t('numberOfItems').replace('%$1d', this.props.count)
MailPoet.I18n.t('numberOfItems').replace('%$1d', this.props.count.toLocaleString())
}</span>
{ pagination }
</div>

View File

@ -16,7 +16,7 @@ define([
defaults: function() {
return this._getDefaults({
type: 'footer',
text: '<a href="[subscription:unsubscribe_url]">Unsubscribe</a> | <a href="[subscription:manage_url]">Manage subscription</a><br /><b>Add your postal address here!</b>',
text: '<a href="[link:subscription_unsubscribe_url]">Unsubscribe</a> | <a href="[link:subscription_manage_url]">Manage subscription</a><br /><b>Add your postal address here!</b>',
styles: {
block: {
backgroundColor: 'transparent',

View File

@ -16,7 +16,7 @@ define([
defaults: function() {
return this._getDefaults({
type: 'header',
text: 'Display problems? <a href="[newsletter:view_in_browser_url]">View it in your browser</a>',
text: 'Display problems? <a href="[link:newsletter_view_in_browser_url]">View it in your browser</a>',
styles: {
block: {
backgroundColor: 'transparent',

View File

@ -284,7 +284,7 @@ define([
}
if (App.getConfig().get('validation.validateUnsubscribeLinkPresent') &&
JSON.stringify(jsonObject).indexOf("[subscription:unsubscribe_url]") < 0) {
JSON.stringify(jsonObject).indexOf("[link:subscription_unsubscribe_url]") < 0) {
this.showValidationError(MailPoet.I18n.t('unsubscribeLinkMissing'));
return;
}

View File

@ -245,14 +245,20 @@ define([
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'render',
action: 'showPreview',
data: json,
}).done(function(response){
MailPoet.Modal.loading(false);
window.open('data:text/html;charset=utf-8,' + encodeURIComponent(response.rendered_body), '_blank');
if (response.result === true) {
window.open(response.data.url, '_blank')
}
MailPoet.Notice.error(response.errors);
}).fail(function(error) {
MailPoet.Modal.loading(false);
alert('Something went wrong, check console');
MailPoet.Notice.error(
MailPoet.I18n.t('newsletterPreviewFailed')
);
});
},
sendPreview: function() {

View File

@ -148,7 +148,7 @@ define(
} else {
if (item.queue.status === 'scheduled') {
return (
<span>{MailPoet.I18n.t('scheduledFor')} { item.queue.scheduled_at } </span>
<span>{MailPoet.I18n.t('scheduledFor')} { MailPoet.Date.format(item.queue.scheduled_at) } </span>
)
}
var progressClasses = classNames(
@ -253,7 +253,7 @@ define(
var statistics_column =
(!mailpoet_settings.tracking || !mailpoet_settings.tracking.enabled) ?
false :
<td className="column {statistics_class}" data-colname="Statistics">
<td className="column {statistics_class}" data-colname={ MailPoet.I18n.t('statistics') }>
{ this.renderStatistics(newsletter) }
</td>;
@ -265,18 +265,18 @@ define(
</strong>
{ actions }
</td>
<td className="column" data-colname="Status">
<td className="column" data-colname={ MailPoet.I18n.t('status') }>
{ this.renderStatus(newsletter) }
</td>
<td className="column" data-colname="Lists">
<td className="column" data-colname={ MailPoet.I18n.t('lists') }>
{ segments }
</td>
{ statistics_column }
<td className="column-date" data-colname="Subscribed on">
<abbr>{ MailPoet.Date.full(newsletter.created_at) }</abbr>
<td className="column-date" data-colname={ MailPoet.I18n.t('createdOn') }>
<abbr>{ MailPoet.Date.format(newsletter.created_at) }</abbr>
</td>
<td className="column-date" data-colname="Last modified on">
<abbr>{ MailPoet.Date.full(newsletter.updated_at) }</abbr>
<td className="column-date" data-colname={ MailPoet.I18n.t('lastModifiedOn') }>
<abbr>{ MailPoet.Date.format(newsletter.updated_at) }</abbr>
</td>
</div>
);
@ -287,11 +287,12 @@ define(
}
return (
<div>
<h2 className="title">
{MailPoet.I18n.t('pageTitle')} <Link className="add-new-h2" to="/new">{MailPoet.I18n.t('new')}</Link>
</h2>
<h1 className="title">
{MailPoet.I18n.t('pageTitle')} <Link className="page-title-action" to="/new">{MailPoet.I18n.t('new')}</Link>
</h1>
<Listing
limit={ mailpoet_listing_per_page }
params={ this.props.params }
endpoint="newsletters"
onRenderItem={this.renderItem}

View File

@ -42,7 +42,7 @@ define(
getLabel: function(segment) {
var name = segment.name;
if (segment.subscribers > 0) {
name += ' (%$1s)'.replace('%$1s', segment.subscribers);
name += ' (%$1s)'.replace('%$1s', parseInt(segment.subscribers).toLocaleString());
}
return name;
},

View File

@ -1,12 +1,320 @@
define(
[
'mailpoet'
'react',
'jquery',
'underscore',
'mailpoet',
'form/fields/checkbox.jsx',
'form/fields/select.jsx',
'form/fields/text.jsx',
],
function(
MailPoet
React,
jQuery,
_,
MailPoet,
Checkbox,
Select,
Text
) {
var settings = window.mailpoet_settings || {};
var settings = window.mailpoet_settings || {},
currentTime = window.mailpoet_current_time || '00:00',
defaultDateTime = window.mailpoet_current_date + ' ' + '00:00:00';
timeOfDayItems = window.mailpoet_schedule_time_of_day,
dateDisplayFormat = window.mailpoet_date_display_format,
dateStorageFormat = window.mailpoet_date_storage_format;
var datepickerTranslations = {
closeText: MailPoet.I18n.t('close'),
currentText: MailPoet.I18n.t('today'),
nextText: MailPoet.I18n.t('next'),
prevText: MailPoet.I18n.t('previous'),
monthNames: [
MailPoet.I18n.t('january'),
MailPoet.I18n.t('february'),
MailPoet.I18n.t('march'),
MailPoet.I18n.t('april'),
MailPoet.I18n.t('may'),
MailPoet.I18n.t('june'),
MailPoet.I18n.t('july'),
MailPoet.I18n.t('august'),
MailPoet.I18n.t('september'),
MailPoet.I18n.t('october'),
MailPoet.I18n.t('november'),
MailPoet.I18n.t('december')
],
monthNamesShort: [
MailPoet.I18n.t('januaryShort'),
MailPoet.I18n.t('februaryShort'),
MailPoet.I18n.t('marchShort'),
MailPoet.I18n.t('aprilShort'),
MailPoet.I18n.t('mayShort'),
MailPoet.I18n.t('juneShort'),
MailPoet.I18n.t('julyShort'),
MailPoet.I18n.t('augustShort'),
MailPoet.I18n.t('septemberShort'),
MailPoet.I18n.t('octoberShort'),
MailPoet.I18n.t('novemberShort'),
MailPoet.I18n.t('decemberShort')
],
dayNames: [
MailPoet.I18n.t('sunday'),
MailPoet.I18n.t('monday'),
MailPoet.I18n.t('tuesday'),
MailPoet.I18n.t('wednesday'),
MailPoet.I18n.t('thursday'),
MailPoet.I18n.t('friday'),
MailPoet.I18n.t('saturday')
],
dayNamesShort: [
MailPoet.I18n.t('sundayShort'),
MailPoet.I18n.t('mondayShort'),
MailPoet.I18n.t('tuesdayShort'),
MailPoet.I18n.t('wednesdayShort'),
MailPoet.I18n.t('thursdayShort'),
MailPoet.I18n.t('fridayShort'),
MailPoet.I18n.t('saturdayShort')
],
dayNamesMin: [
MailPoet.I18n.t('sundayMin'),
MailPoet.I18n.t('mondayMin'),
MailPoet.I18n.t('tuesdayMin'),
MailPoet.I18n.t('wednesdayMin'),
MailPoet.I18n.t('thursdayMin'),
MailPoet.I18n.t('fridayMin'),
MailPoet.I18n.t('saturdayMin')
],
};
var DateText = React.createClass({
onChange: function(event) {
// Swap display format to storage format
var displayDate = event.target.value,
storageDate = this.getStorageDate(displayDate);
event.target.value = storageDate;
this.props.onChange(event);
},
componentDidMount: function() {
var $element = jQuery(this.refs.dateInput),
that = this;
if ($element.datepicker) {
// Override jQuery UI datepicker Date parsing and formatting
jQuery.datepicker.parseDate = function(format, value) {
// Transform string format to Date object
return MailPoet.Date.toDate(value, {
parseFormat: dateDisplayFormat,
format: format
});
};
jQuery.datepicker.formatDate = function(format, value) {
// Transform Date object to string format
var newValue = MailPoet.Date.format(value, {
format: format
});
return newValue;
};
$element.datepicker(_.extend({
dateFormat: this.props.displayFormat,
isRTL: false,
onSelect: function(value) {
that.onChange({
target: {
name: that.getFieldName(),
value: value,
},
});
},
}, datepickerTranslations));
this.datepickerInitialized = true;
}
},
componentWillUnmount: function() {
if (this.datepickerInitialized) {
jQuery(this.refs.dateInput).datepicker('destroy');
}
},
getFieldName: function() {
return this.props.name || 'date';
},
getDisplayDate: function(date) {
return MailPoet.Date.format(date, {
parseFormat: this.props.storageFormat,
format: this.props.displayFormat
});
},
getStorageDate: function(date) {
return MailPoet.Date.format(date, {
parseFormat: this.props.displayFormat,
format: this.props.storageFormat
});
},
render: function() {
return (
<input
type="text"
size="10"
name={this.getFieldName()}
value={this.getDisplayDate(this.props.value)}
readOnly={ true }
onChange={this.onChange}
ref="dateInput"
{...this.props.validation} />
);
},
});
var TimeSelect = React.createClass({
render: function() {
const options = Object.keys(timeOfDayItems).map(
(value, index) => {
return (
<option
key={ 'option-' + index }
value={ value }>
{ timeOfDayItems[value] }
</option>
);
}
);
return (
<select
name={this.props.name || 'time'}
value={this.props.value}
onChange={this.props.onChange}
{...this.props.validation}
>
{options}
</select>
);
}
});
var DateTime = React.createClass({
_DATE_TIME_SEPARATOR: " ",
getInitialState: function() {
return this._buildStateFromProps(this.props);
},
componentWillReceiveProps: function(nextProps) {
this.setState(this._buildStateFromProps(nextProps));
},
_buildStateFromProps: function(props) {
let value = props.value || defaultDateTime;
const [date, time] = value.split(this._DATE_TIME_SEPARATOR)
return {
date: date,
time: time,
};
},
handleChange: function(event) {
var newState = {};
newState[event.target.name] = event.target.value;
this.setState(newState, function() {
this.propagateChange();
});
},
propagateChange: function() {
if (this.props.onChange) {
return this.props.onChange({
target: {
name: this.props.name || '',
value: this.getDateTime(),
}
})
}
},
getDateTime: function() {
return [this.state.date, this.state.time].join(this._DATE_TIME_SEPARATOR);
},
render: function() {
return (
<span>
<DateText
name="date"
value={this.state.date}
onChange={this.handleChange}
displayFormat={dateDisplayFormat}
storageFormat={dateStorageFormat}
validation={this.props.dateValidation}/>
<TimeSelect
name="time"
value={this.state.time}
onChange={this.handleChange}
validation={this.props.timeValidation} />
</span>
);
}
});
var StandardScheduling = React.createClass({
_getCurrentValue: function() {
return this.props.item[this.props.field.name] || {};
},
handleValueChange: function(event) {
var oldValue = this._getCurrentValue(),
newValue = {};
newValue[event.target.name] = event.target.value;
return this.props.onValueChange({
target: {
name: this.props.field.name,
value: _.extend({}, oldValue, newValue)
}
});
},
handleCheckboxChange: function(event) {
event.target.value = this.refs.isScheduled.checked ? '1' : '0';
return this.handleValueChange(event);
},
isScheduled: function() {
return this._getCurrentValue().isScheduled === '1';
},
getDateValidation: function() {
return {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('noScheduledDateError'),
'data-parsley-errors-container': '#mailpoet_scheduling',
};
},
render: function() {
var schedulingOptions;
if (this.isScheduled()) {
schedulingOptions = (
<span id="mailpoet_scheduling">
<DateTime
name="scheduledAt"
value={this._getCurrentValue().scheduledAt}
onChange={this.handleValueChange}
dateValidation={this.getDateValidation()} />
&nbsp;
<span>
{MailPoet.I18n.t('websiteTimeIs')} <code>{currentTime}</code>
</span>
</span>
);
}
return (
<div>
<input
ref="isScheduled"
type="checkbox"
value="1"
checked={this.isScheduled()}
name="isScheduled"
onChange={this.handleCheckboxChange} />
{schedulingOptions}
</div>
);
},
});
var fields = [
{
@ -34,7 +342,7 @@ define(
getLabel: function(segment) {
var name = segment.name;
if (segment.subscribers > 0) {
name += ' (%$1s)'.replace('%$1s', segment.subscribers);
name += ' (%$1s)'.replace('%$1s', parseInt(segment.subscribers).toLocaleString());
}
return name;
},
@ -84,6 +392,12 @@ define(
placeholder: MailPoet.I18n.t('replyToAddressPlaceholder')
}
]
},
{
name: 'options',
label: MailPoet.I18n.t('scheduleIt'),
type: 'reactComponent',
component: StandardScheduling,
}
];

View File

@ -34,7 +34,7 @@ define(
function(segment) {
var name = segment.name;
if (segment.subscribers > 0) {
name += ' (%$1d)'.replace('%$1d', segment.subscribers);
name += ' (%$1s)'.replace('%$1s', parseInt(segment.subscribers).toLocaleString());
}
return [segment.id, name];
}

View File

@ -51,7 +51,7 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
id: null,
positionAfter: false,
scroll: false,
timeout: 2000,
timeout: 5000,
onOpen: null,
onClose: null
},

View File

@ -1,11 +1,13 @@
define(
[
'react',
'react-router',
'mailpoet',
'form/form.jsx'
],
function(
React,
Router,
MailPoet,
Form
) {
@ -33,13 +35,16 @@ define(
}
};
var Link = Router.Link;
const SegmentForm = React.createClass({
render: function() {
return (
<div>
<h2 className="title">
<h1 className="title">
{MailPoet.I18n.t('segment')}
</h2>
<Link className="page-title-action" to="/">{MailPoet.I18n.t('backToList')}</Link>
</h1>
<Form
endpoint="segments"

View File

@ -88,10 +88,17 @@ const messages = {
}
};
const bulk_actions = [
{
name: 'trash',
label: MailPoet.I18n.t('trash'),
onSuccess: messages.onTrash
}
];
const item_actions = [
{
name: 'edit',
label: MailPoet.I18n.t('edit'),
link: function(item) {
return (
<Link to={ `/edit/${item.id}` }>{MailPoet.I18n.t('edit')}</Link>
@ -103,7 +110,7 @@ const item_actions = [
},
{
name: 'duplicate_segment',
label: 'Duplicate',
label: MailPoet.I18n.t('duplicate'),
onClick: function(item, refresh) {
return MailPoet.Ajax.post({
endpoint: 'segments',
@ -120,10 +127,23 @@ const item_actions = [
return (segment.type !== 'wp_users');
}
},
{
name: 'read_more',
link: function(item) {
return (
<a
href="https://www.mailpoet.com/#TODO"
target="_blank"
>{MailPoet.I18n.t('readMore')}</a>
);
},
display: function(segment) {
return (segment.type === 'wp_users');
}
},
{
name: 'synchronize_segment',
label: MailPoet.I18n.t('update'),
className: 'update',
label: MailPoet.I18n.t('forceSync'),
onClick: function(item, refresh) {
MailPoet.Modal.loading(true);
MailPoet.Ajax.post({
@ -166,28 +186,42 @@ const SegmentList = React.createClass({
'column-primary',
'has-row-actions'
);
const subscribed = ~~(segment.subscribers_count.subscribed || 0);
const unconfirmed = ~~(segment.subscribers_count.unconfirmed || 0);
const unsubscribed = ~~(segment.subscribers_count.unsubscribed || 0);
let segment_name = (
<Link to={ `/edit/${segment.id}` }>{ segment.name }</Link>
);
// the WP users segment is not editable so just display its name
if (segment.type === 'wp_users') {
segment_name = segment.name;
}
return (
<div>
<td className={ rowClasses }>
<strong>
<a>{ segment.name }</a>
{ segment_name }
</strong>
{ actions }
</td>
<td className="column-date" data-colname="Description">
<td className="column-date" data-colname={ MailPoet.I18n.t('description') }>
<abbr>{ segment.description }</abbr>
</td>
<td className="column-date" data-colname="Subscribed">
<abbr>{ segment.subscribers_count.subscribed || 0 }</abbr>
<td className="column-date" data-colname={ MailPoet.I18n.t('subscribed') }>
<abbr>{ subscribed.toLocaleString() }</abbr>
</td>
<td className="column-date" data-colname="Unconfirmed">
<abbr>{ segment.subscribers_count.unconfirmed || 0 }</abbr>
<td className="column-date" data-colname={ MailPoet.I18n.t('unconfirmed') }>
<abbr>{ unconfirmed.toLocaleString() }</abbr>
</td>
<td className="column-date" data-colname="Unsubscribed">
<abbr>{ segment.subscribers_count.unsubscribed || 0 }</abbr>
<td className="column-date" data-colname={ MailPoet.I18n.t('unsubscribed') }>
<abbr>{ unsubscribed.toLocaleString() }</abbr>
</td>
<td className="column-date" data-colname="Created on">
<abbr>{ MailPoet.Date.full(segment.created_at) }</abbr>
<td className="column-date" data-colname={ MailPoet.I18n.t('createdOn') }>
<abbr>{ MailPoet.Date.format(segment.created_at) }</abbr>
</td>
</div>
);
@ -195,19 +229,20 @@ const SegmentList = React.createClass({
render: function() {
return (
<div>
<h2 className="title">
{MailPoet.I18n.t('pageTitle')} <Link className="add-new-h2" to="/new">{MailPoet.I18n.t('new')}</Link>
</h2>
<h1 className="title">
{MailPoet.I18n.t('pageTitle')} <Link className="page-title-action" to="/new">{MailPoet.I18n.t('new')}</Link>
</h1>
<Listing
limit={ mailpoet_listing_per_page }
location={ this.props.location }
params={ this.props.params }
messages={ messages }
search={ false }
limit={ 1000 }
endpoint="segments"
onRenderItem={ this.renderItem }
columns={ columns }
bulk_actions={ bulk_actions }
item_actions={ item_actions }
/>
</div>

View File

@ -3,38 +3,55 @@ define(
'react',
'react-router',
'mailpoet',
'form/form.jsx'
'form/form.jsx',
'react-string-replace'
],
function(
React,
Router,
MailPoet,
Form
Form,
ReactStringReplace
) {
var fields = [
{
name: 'email',
label: MailPoet.I18n.t('email'),
type: 'text'
type: 'text',
disabled: function(subscriber) {
return ~~(subscriber.wp_user_id > 0);
}
},
{
name: 'first_name',
label: MailPoet.I18n.t('firstname'),
type: 'text'
type: 'text',
disabled: function(subscriber) {
return ~~(subscriber.wp_user_id > 0);
}
},
{
name: 'last_name',
label: MailPoet.I18n.t('lastname'),
type: 'text'
type: 'text',
disabled: function(subscriber) {
return ~~(subscriber.wp_user_id > 0);
}
},
{
name: 'status',
label: MailPoet.I18n.t('status'),
type: 'select',
values: {
'unconfirmed': MailPoet.I18n.t('unconfirmed'),
'subscribed': MailPoet.I18n.t('subscribed'),
'unconfirmed': MailPoet.I18n.t('unconfirmed'),
'unsubscribed': MailPoet.I18n.t('unsubscribed')
},
filter: function(subscriber, value) {
if (~~(subscriber.wp_user_id) > 0 && value === 'unconfirmed') {
return false;
}
return true;
}
},
{
@ -100,6 +117,19 @@ define(
field.values = custom_field.params.values;
}
// add placeholders for selects (date, select)
switch(custom_field.type) {
case 'date':
field.year_placeholder = MailPoet.I18n.t('year');
field.month_placeholder = MailPoet.I18n.t('month');
field.day_placeholder = MailPoet.I18n.t('day');
break;
case 'select':
field.placeholder = '-';
break;
}
fields.push(field);
});
@ -112,21 +142,54 @@ define(
}
};
var beforeFormContent = function(subscriber) {
if (~~(subscriber.wp_user_id) > 0) {
return (
<p className="description">
{ ReactStringReplace(
MailPoet.I18n.t('WPUserEditNotice'),
/\[link\](.*?)\[\/link\]/g,
(match, i) => (
<a
key={ i }
href={`user-edit.php?user_id=${ subscriber.wp_user_id }`}
>{ match }</a>
)
)
}
</p>
);
}
};
var afterFormContent = function(subscriber) {
return (
<p className="description">
<strong>
{ MailPoet.I18n.t('tip') }
</strong> { MailPoet.I18n.t('customFieldsTip') }
</p>
);
}
var Link = Router.Link;
var SubscriberForm = React.createClass({
render: function() {
return (
<div>
<h2 className="title">
<h1 className="title">
{MailPoet.I18n.t('subscriber')}
</h2>
<Link className="page-title-action" to="/">{MailPoet.I18n.t('backToList')}</Link>
</h1>
<Form
endpoint="subscribers"
fields={ fields }
params={ this.props.params }
messages={ messages }
beforeFormContent={ beforeFormContent }
afterFormContent={ afterFormContent }
/>
</div>
);

View File

@ -43,12 +43,12 @@ define(
width: '20em',
templateResult: function (item) {
return (item.subscriberCount > 0)
? item.name + ' (' + item.subscriberCount + ')'
? item.name + ' (' + parseInt(item.subscriberCount).toLocaleString() + ')'
: item.name;
},
templateSelection: function (item) {
return (item.subscriberCount > 0)
? item.name + ' (' + item.subscriberCount + ')'
? item.name + ' (' + parseInt(item.subscriberCount).toLocaleString() + ')'
: item.name;
}
})
@ -151,7 +151,7 @@ define(
MailPoet.Notice.error(response.errors);
} else {
resultMessage = MailPoet.I18n.t('exportMessage')
.replace('%1$s', '<strong>' + response.data.totalExported + '</strong>')
.replace('%1$s', '<strong>' + parseInt(response.data.totalExported).toLocaleString() + '</strong>')
.replace('[link]', '<a href="' + response.data.exportFileURL + '" target="_blank" >')
.replace('[/link]', '</a>');
jQuery('#export_result_notice > ul > li').html(resultMessage);

View File

@ -24,7 +24,6 @@ define(
return;
}
jQuery(document).ready(function () {
var noticeTimeout = 3000;
jQuery('input[name="select_method"]').attr('checked', false);
// configure router
router = new (Backbone.Router.extend({
@ -128,9 +127,7 @@ define(
// get an approximate size of textarea paste in bytes
var pasteSize = encodeURI(pasteInputElement.val()).split(/%..|./).length - 1;
if (pasteSize > maxPostSizeBytes) {
MailPoet.Notice.error(MailPoet.I18n.t('maxPostSizeNotice'), {
timeout: noticeTimeout,
});
MailPoet.Notice.error(MailPoet.I18n.t('maxPostSizeNotice'));
return;
}
// delay loading indicator for 10ms or else it's just too fast :)
@ -148,9 +145,7 @@ define(
var ext = this.value.match(/\.(.+)$/);
if (ext === null || ext[1].toLowerCase() !== 'csv') {
this.value = '';
MailPoet.Notice.error(MailPoet.I18n.t('wrongFileFormat'), {
timeout: noticeTimeout,
});
MailPoet.Notice.error(MailPoet.I18n.t('wrongFileFormat'));
}
toggleNextStepButton(
@ -198,9 +193,7 @@ define(
}).done(function (response) {
if (response.result === false) {
MailPoet.Notice.hide();
MailPoet.Notice.error(response.errors, {
timeout: noticeTimeout,
});
MailPoet.Notice.error(response.errors);
jQuery('.mailpoet_mailchimp-key-status')
.removeClass()
.addClass('mailpoet_mailchimp-key-status mailpoet_mailchimp-error');
@ -223,9 +216,7 @@ define(
}).error(function (error) {
MailPoet.Modal.loading(false);
MailPoet.Notice.error(
MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.', {
timeout: noticeTimeout,
}
MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.'
);
});
MailPoet.Modal.loading(false);
@ -250,17 +241,13 @@ define(
}
else {
MailPoet.Notice.hide();
MailPoet.Notice.error(response.errors, {
timeout: noticeTimeout,
});
MailPoet.Notice.error(response.errors);
}
MailPoet.Modal.loading(false);
}).error(function () {
MailPoet.Modal.loading(false);
MailPoet.Notice.error(
MailPoet.I18n.t('serverError') + result.statusText.toLowerCase() + '.', {
timeout: noticeTimeout,
}
MailPoet.I18n.t('serverError') + result.statusText.toLowerCase() + '.'
);
});
});
@ -350,9 +337,7 @@ define(
comments: advancedOptionComments,
error: function () {
MailPoet.Notice.hide();
MailPoet.Notice.error(MailPoet.I18n.t('dataProcessingError'), {
timeout: noticeTimeout,
});
MailPoet.Notice.error(MailPoet.I18n.t('dataProcessingError'));
},
complete: function (CSV) {
for (var rowCount in CSV.data) {
@ -434,9 +419,7 @@ define(
var errorNotice = MailPoet.I18n.t('noValidRecords');
errorNotice = errorNotice.replace('[link]', MailPoet.I18n.t('csvKBLink'));
errorNotice = errorNotice.replace('[/link]', '</a>');
MailPoet.Notice.error(errorNotice, {
timeout: noticeTimeout,
});
MailPoet.Notice.error(errorNotice);
}
}
}
@ -507,7 +490,7 @@ define(
),
invalid: (subscribers.invalid.length)
? MailPoet.I18n.t('importNoticeInvalid')
.replace('%1$s', '<strong>' + subscribers.invalid.length + '</strong>')
.replace('%1$s', '<strong>' + subscribers.invalid.length.toLocaleString() + '</strong>')
.replace('%2$s', subscribers.invalid.join(', '))
: null,
duplicate: (subscribers.duplicate.length)
@ -550,13 +533,15 @@ define(
data: segments,
width: '20em',
templateResult: function (item) {
item.subscriberCount = parseInt(item.subscriberCount);
return (item.subscriberCount > 0)
? item.name + ' (' + item.subscriberCount + ')'
? item.name + ' (' + item.subscriberCount.toLocaleString() + ')'
: item.name;
},
templateSelection: function (item) {
item.subscriberCount = parseInt(item.subscriberCount);
return (item.subscriberCount > 0)
? item.name + ' (' + item.subscriberCount + ')'
? item.name + ' (' + item.subscriberCount.toLocaleString() + ')'
: item.name;
}
})
@ -566,7 +551,6 @@ define(
if (!segmentSelectionNotice.length) {
MailPoet.Notice.error(MailPoet.I18n.t('segmentSelectionRequired'), {
static: true,
timeout: noticeTimeout,
scroll: true,
id: 'segmentSelection',
hideClose: true
@ -644,18 +628,14 @@ define(
else {
MailPoet.Modal.close();
MailPoet.Notice.error(
MailPoet.I18n.t('segmentCreateError') + response.message + '.', {
timeout: noticeTimeout,
}
MailPoet.I18n.t('segmentCreateError') + response.message + '.'
);
}
})
.error(function (error) {
MailPoet.Modal.close();
MailPoet.Notice.error(
MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.', {
timeout: noticeTimeout
}
MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.'
);
});
}
@ -733,7 +713,7 @@ define(
}
// if we're on the last line, show the total count of subscribers data
else if (index === (subscribers.subscribers.length - 1)) {
return subscribers.subscribersCount;
return subscribers.subscribersCount.toLocaleString();
} else {
return index + 1;
}
@ -860,18 +840,14 @@ define(
filterSubscribers();
}
else {
MailPoet.Notice.error(MailPoet.I18n.t('customFieldCreateError'), {
timeout: noticeTimeout,
});
MailPoet.Notice.error(MailPoet.I18n.t('customFieldCreateError'));
}
MailPoet.Modal.loading(false);
})
.error(function (error) {
MailPoet.Modal.loading(false);
MailPoet.Notice.error(
MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.', {
timeout: noticeTimeout,
}
MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.'
);
});
}
@ -935,7 +911,6 @@ define(
if (!jQuery('[data-id="notice_invalidEmail"]').length) {
MailPoet.Notice.error(MailPoet.I18n.t('columnContainsInvalidElement'), {
static: true,
timeout: noticeTimeout,
scroll: true,
hideClose: true,
id: 'invalidEmail'
@ -1001,7 +976,7 @@ define(
data[matchedColumn] +=
'<span class="mailpoet_data_match" title="'
+ MailPoet.I18n.t('verifyDateMatch') + '">'
+ date + '</span>';
+ MailPoet.Date.format(date) + '</span>';
}
else {
data[matchedColumn] +=
@ -1015,7 +990,6 @@ define(
if (preventNextStep && !jQuery('.mailpoet_invalidDate').length) {
MailPoet.Notice.error(MailPoet.I18n.t('columnContainsInvalidDate'), {
static: true,
timeout: noticeTimeout,
scroll: true,
hideClose: true,
id: 'invalidDate'
@ -1127,9 +1101,7 @@ define(
queue.onComplete(function () {
MailPoet.Modal.loading(false);
if (importResults.errors.length > 0 && !importResults.updated && !importResults.created) {
MailPoet.Notice.error(_.flatten(importResults.errors), {
timeout: noticeTimeout,
}
MailPoet.Notice.error(_.flatten(importResults.errors)
);
}
else {
@ -1140,6 +1112,7 @@ define(
});
importData.step2 = importResults;
enableSegmentSelection(mailpoetSegments);
toggleNextStepButton('off');
router.navigate('step3', {trigger: true});
}
});
@ -1158,9 +1131,7 @@ define(
showCurrentStep();
if (importData.step2.errors.length > 0) {
MailPoet.Notice.error(_.flatten(importData.step2.errors), {
timeout: noticeTimeout,
});
MailPoet.Notice.error(_.flatten(importData.step2.errors));
}
// display statistics
@ -1172,12 +1143,12 @@ define(
importResults = {
created: (importData.step2.created)
? MailPoet.I18n.t('subscribersCreated')
.replace('%1$s', '<strong>' + importData.step2.created + '</strong>')
.replace('%1$s', '<strong>' + importData.step2.created.toLocaleString() + '</strong>')
.replace('%2$s', '"' + importData.step2.segments.join('", "') + '"')
: false,
updated: (importData.step2.updated)
? MailPoet.I18n.t('subscribersUpdated')
.replace('%1$s', '<strong>' + importData.step2.updated + '</strong>')
.replace('%1$s', '<strong>' + importData.step2.updated.toLocaleString() + '</strong>')
.replace('%2$s', '"' + importData.step2.segments.join('", "') + '"')
: false,
noaction: (!importData.step2.updated && !importData.step2.created)

View File

@ -106,6 +106,11 @@ const bulk_actions = [
return !!(
!segment.deleted_at && segment.type === 'default'
);
},
getLabel: function (segment) {
return (segment.subscribers > 0) ?
segment.name + ' (' + parseInt(segment.subscribers).toLocaleString() + ')' :
segment.name;
}
};
@ -137,6 +142,11 @@ const bulk_actions = [
return !!(
!segment.deleted_at && segment.type === 'default'
);
},
getLabel: function (segment) {
return (segment.subscribers > 0) ?
segment.name + ' (' + parseInt(segment.subscribers).toLocaleString() + ')' :
segment.name;
}
};
@ -168,6 +178,11 @@ const bulk_actions = [
return !!(
segment.type === 'default'
);
},
getLabel: function (segment) {
return (segment.subscribers > 0) ?
segment.name + ' (' + parseInt(segment.subscribers).toLocaleString() + ')' :
segment.name;
}
};
@ -198,16 +213,6 @@ const bulk_actions = [
);
}
},
{
name: 'confirmUnconfirmed',
label: MailPoet.I18n.t('confirmUnconfirmed'),
onSuccess: function(response) {
MailPoet.Notice.success(
MailPoet.I18n.t('multipleSubscribersConfirmed')
.replace('%$1d', ~~response)
);
}
},
{
name: 'sendConfirmationEmail',
label: MailPoet.I18n.t('resendConfirmationEmail'),
@ -275,10 +280,15 @@ const SubscriberList = React.createClass({
}
let segments = false;
let subscribed_segments = [];
// WordPress Users
if (~~(subscriber.wp_user_id) > 0) {
subscribed_segments.push(MailPoet.I18n.t('WPUsersSegment'));
}
// Subscriptions
if (subscriber.subscriptions.length > 0) {
let subscribed_segments = [];
subscriber.subscriptions.map((subscription) => {
const segment = this.getSegmentFromId(subscription.segment_id);
if(segment === false) return;
@ -286,15 +296,14 @@ const SubscriberList = React.createClass({
subscribed_segments.push(segment.name);
}
});
segments = (
<span>
<span className="mailpoet_segments_subscribed">
{ subscribed_segments.join(', ') }
</span>
</span>
);
}
segments = (
<span>
{ subscribed_segments.join(', ') }
</span>
);
let avatar = false;
if(subscriber.avatar_url) {
avatar = (
@ -326,10 +335,10 @@ const SubscriberList = React.createClass({
{ segments }
</td>
<td className="column-date" data-colname={MailPoet.I18n.t('subscribedOn')}>
<abbr>{ MailPoet.Date.full(subscriber.created_at) }</abbr>
<abbr>{ MailPoet.Date.format(subscriber.created_at) }</abbr>
</td>
<td className="column-date" data-colname={MailPoet.I18n.t('lastModifiedOn')}>
<abbr>{ MailPoet.Date.full(subscriber.updated_at) }</abbr>
<abbr>{ MailPoet.Date.format(subscriber.updated_at) }</abbr>
</td>
</div>
);
@ -340,11 +349,11 @@ const SubscriberList = React.createClass({
render: function() {
return (
<div>
<h2 className="title">
{MailPoet.I18n.t('pageTitle')} <Link className="add-new-h2" to="/new">{MailPoet.I18n.t('new')}</Link>
<a className="add-new-h2" href="?page=mailpoet-import#step1">{MailPoet.I18n.t('import')}</a>
<a id="mailpoet_export_button" className="add-new-h2" href="?page=mailpoet-export">{MailPoet.I18n.t('export')}</a>
</h2>
<h1 className="title">
{MailPoet.I18n.t('pageTitle')} <Link className="page-title-action" to="/new">{MailPoet.I18n.t('new')}</Link>
<a className="page-title-action" href="?page=mailpoet-import#step1">{MailPoet.I18n.t('import')}</a>
<a id="mailpoet_export_button" className="page-title-action" href="?page=mailpoet-export">{MailPoet.I18n.t('export')}</a>
</h1>
<Listing
limit={ mailpoet_listing_per_page }

262
composer.lock generated
View File

@ -251,16 +251,16 @@
},
{
"name": "phpmailer/phpmailer",
"version": "v5.2.14",
"version": "v5.2.15",
"source": {
"type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "e774bc9152de85547336e22b8926189e582ece95"
"reference": "d0186171b28af4f06ac2ad8a84a8f3d6cbc3ba6c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/e774bc9152de85547336e22b8926189e582ece95",
"reference": "e774bc9152de85547336e22b8926189e582ece95",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/d0186171b28af4f06ac2ad8a84a8f3d6cbc3ba6c",
"reference": "d0186171b28af4f06ac2ad8a84a8f3d6cbc3ba6c",
"shasum": ""
},
"require": {
@ -271,8 +271,7 @@
"phpunit/phpunit": "4.7.*"
},
"suggest": {
"league/oauth2-client": "Needed for XOAUTH2 authentication",
"league/oauth2-google": "Needed for Gmail XOAUTH2"
"league/oauth2-google": "Needed for Google XOAUTH2 authentication"
},
"type": "library",
"autoload": {
@ -308,7 +307,7 @@
}
],
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
"time": "2015-11-01 10:15:28"
"time": "2016-05-10 18:39:36"
},
{
"name": "phpseclib/phpseclib",
@ -494,16 +493,16 @@
},
{
"name": "swiftmailer/swiftmailer",
"version": "v5.4.1",
"version": "v5.4.2",
"source": {
"type": "git",
"url": "https://github.com/swiftmailer/swiftmailer.git",
"reference": "0697e6aa65c83edf97bb0f23d8763f94e3f11421"
"reference": "d8db871a54619458a805229a057ea2af33c753e8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/0697e6aa65c83edf97bb0f23d8763f94e3f11421",
"reference": "0697e6aa65c83edf97bb0f23d8763f94e3f11421",
"url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/d8db871a54619458a805229a057ea2af33c753e8",
"reference": "d8db871a54619458a805229a057ea2af33c753e8",
"shasum": ""
},
"require": {
@ -543,7 +542,7 @@
"mail",
"mailer"
],
"time": "2015-06-06 14:19:39"
"time": "2016-05-01 08:45:47"
},
{
"name": "symfony/polyfill-mbstring",
@ -606,16 +605,16 @@
},
{
"name": "symfony/translation",
"version": "v2.8.3",
"version": "v2.8.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
"reference": "b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7"
"reference": "d60b8e076d22953aabebeebda53bf334438e7aca"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7",
"reference": "b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7",
"url": "https://api.github.com/repos/symfony/translation/zipball/d60b8e076d22953aabebeebda53bf334438e7aca",
"reference": "d60b8e076d22953aabebeebda53bf334438e7aca",
"shasum": ""
},
"require": {
@ -666,7 +665,7 @@
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
"time": "2016-02-02 09:49:18"
"time": "2016-03-25 01:40:30"
},
{
"name": "tburry/pquery",
@ -785,16 +784,16 @@
"packages-dev": [
{
"name": "codeception/codeception",
"version": "2.1.7",
"version": "2.1.8",
"source": {
"type": "git",
"url": "https://github.com/Codeception/Codeception.git",
"reference": "65971b0dee4972710365b6102154cd412a9bf7b1"
"reference": "f3daa61f0f11c531b33eb3623ab0daa599d88a79"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/65971b0dee4972710365b6102154cd412a9bf7b1",
"reference": "65971b0dee4972710365b6102154cd412a9bf7b1",
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/f3daa61f0f11c531b33eb3623ab0daa599d88a79",
"reference": "f3daa61f0f11c531b33eb3623ab0daa599d88a79",
"shasum": ""
},
"require": {
@ -862,7 +861,7 @@
"functional testing",
"unit testing"
],
"time": "2016-03-12 01:15:25"
"time": "2016-04-15 02:56:43"
},
{
"name": "codeception/verify",
@ -899,16 +898,16 @@
},
{
"name": "codegyre/robo",
"version": "0.7.1",
"version": "0.7.2",
"source": {
"type": "git",
"url": "https://github.com/Codegyre/Robo.git",
"reference": "1f4e0621fdc37521e2eaca67f33d1c4e240dc7d8"
"reference": "9982edecb19f59420031dd9855b0d31e861b8b68"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Codegyre/Robo/zipball/1f4e0621fdc37521e2eaca67f33d1c4e240dc7d8",
"reference": "1f4e0621fdc37521e2eaca67f33d1c4e240dc7d8",
"url": "https://api.github.com/repos/Codegyre/Robo/zipball/9982edecb19f59420031dd9855b0d31e861b8b68",
"reference": "9982edecb19f59420031dd9855b0d31e861b8b68",
"shasum": ""
},
"require": {
@ -950,7 +949,7 @@
}
],
"description": "Modern task runner",
"time": "2016-02-25 19:28:51"
"time": "2016-04-13 20:14:17"
},
{
"name": "doctrine/instantiator",
@ -1051,16 +1050,16 @@
},
{
"name": "guzzlehttp/guzzle",
"version": "6.1.1",
"version": "6.2.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "c6851d6e48f63b69357cbfa55bca116448140e0c"
"reference": "d094e337976dff9d8e2424e8485872194e768662"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/c6851d6e48f63b69357cbfa55bca116448140e0c",
"reference": "c6851d6e48f63b69357cbfa55bca116448140e0c",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/d094e337976dff9d8e2424e8485872194e768662",
"reference": "d094e337976dff9d8e2424e8485872194e768662",
"shasum": ""
},
"require": {
@ -1076,7 +1075,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.1-dev"
"dev-master": "6.2-dev"
}
},
"autoload": {
@ -1109,7 +1108,7 @@
"rest",
"web service"
],
"time": "2015-11-23 00:47:50"
"time": "2016-03-21 20:02:09"
},
{
"name": "guzzlehttp/promises",
@ -1164,16 +1163,16 @@
},
{
"name": "guzzlehttp/psr7",
"version": "1.2.3",
"version": "1.3.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b"
"reference": "31382fef2889136415751badebbd1cb022a4ed72"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/2e89629ff057ebb49492ba08e6995d3a6a80021b",
"reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/31382fef2889136415751badebbd1cb022a4ed72",
"reference": "31382fef2889136415751badebbd1cb022a4ed72",
"shasum": ""
},
"require": {
@ -1218,7 +1217,7 @@
"stream",
"uri"
],
"time": "2016-02-18 21:54:00"
"time": "2016-04-13 19:56:01"
},
{
"name": "henrikbjorn/lurker",
@ -1976,16 +1975,16 @@
},
{
"name": "sebastian/environment",
"version": "1.3.5",
"version": "1.3.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
"reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf"
"reference": "2292b116f43c272ff4328083096114f84ea46a56"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
"reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/2292b116f43c272ff4328083096114f84ea46a56",
"reference": "2292b116f43c272ff4328083096114f84ea46a56",
"shasum": ""
},
"require": {
@ -2022,7 +2021,7 @@
"environment",
"hhvm"
],
"time": "2016-02-26 18:40:46"
"time": "2016-05-04 07:59:13"
},
{
"name": "sebastian/exporter",
@ -2231,16 +2230,16 @@
},
{
"name": "symfony/browser-kit",
"version": "v3.0.3",
"version": "v3.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
"reference": "dde849a0485b70a24b36f826ed3fb95b904d80c3"
"reference": "e07127ac31230b30887c2dddf3708d883d239b14"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/dde849a0485b70a24b36f826ed3fb95b904d80c3",
"reference": "dde849a0485b70a24b36f826ed3fb95b904d80c3",
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/e07127ac31230b30887c2dddf3708d883d239b14",
"reference": "e07127ac31230b30887c2dddf3708d883d239b14",
"shasum": ""
},
"require": {
@ -2284,20 +2283,20 @@
],
"description": "Symfony BrowserKit Component",
"homepage": "https://symfony.com",
"time": "2016-01-27 11:34:55"
"time": "2016-03-04 07:55:57"
},
{
"name": "symfony/config",
"version": "v2.8.3",
"version": "v2.8.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
"reference": "0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19"
"reference": "edbbcf33cffa2a85104fc80de8dc052cc51596bb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/config/zipball/0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19",
"reference": "0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19",
"url": "https://api.github.com/repos/symfony/config/zipball/edbbcf33cffa2a85104fc80de8dc052cc51596bb",
"reference": "edbbcf33cffa2a85104fc80de8dc052cc51596bb",
"shasum": ""
},
"require": {
@ -2337,20 +2336,20 @@
],
"description": "Symfony Config Component",
"homepage": "https://symfony.com",
"time": "2016-02-22 16:12:45"
"time": "2016-04-20 18:52:26"
},
{
"name": "symfony/console",
"version": "v3.0.3",
"version": "v3.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "2ed5e2706ce92313d120b8fe50d1063bcfd12e04"
"reference": "34a214710e0714b6efcf40ba3cd1e31373a97820"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/2ed5e2706ce92313d120b8fe50d1063bcfd12e04",
"reference": "2ed5e2706ce92313d120b8fe50d1063bcfd12e04",
"url": "https://api.github.com/repos/symfony/console/zipball/34a214710e0714b6efcf40ba3cd1e31373a97820",
"reference": "34a214710e0714b6efcf40ba3cd1e31373a97820",
"shasum": ""
},
"require": {
@ -2397,20 +2396,20 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2016-02-28 16:24:34"
"time": "2016-04-28 09:48:42"
},
{
"name": "symfony/css-selector",
"version": "v3.0.3",
"version": "v3.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
"reference": "6605602690578496091ac20ec7a5cbd160d4dff4"
"reference": "65e764f404685f2dc20c057e889b3ad04b2e2db0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/6605602690578496091ac20ec7a5cbd160d4dff4",
"reference": "6605602690578496091ac20ec7a5cbd160d4dff4",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/65e764f404685f2dc20c057e889b3ad04b2e2db0",
"reference": "65e764f404685f2dc20c057e889b3ad04b2e2db0",
"shasum": ""
},
"require": {
@ -2450,20 +2449,20 @@
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
"time": "2016-01-27 05:14:46"
"time": "2016-03-04 07:55:57"
},
{
"name": "symfony/dom-crawler",
"version": "v3.0.3",
"version": "v3.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
"reference": "981c8edb4538f88ba976ed44bdcaa683fce3d6c6"
"reference": "49b588841225b205700e5122fa01911cabada857"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/981c8edb4538f88ba976ed44bdcaa683fce3d6c6",
"reference": "981c8edb4538f88ba976ed44bdcaa683fce3d6c6",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/49b588841225b205700e5122fa01911cabada857",
"reference": "49b588841225b205700e5122fa01911cabada857",
"shasum": ""
},
"require": {
@ -2506,20 +2505,20 @@
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
"time": "2016-02-28 16:24:34"
"time": "2016-04-12 18:09:53"
},
{
"name": "symfony/event-dispatcher",
"version": "v2.8.3",
"version": "v2.8.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "78c468665c9568c3faaa9c416a7134308f2d85c3"
"reference": "a158f13992a3147d466af7a23b564ac719a4ddd8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/78c468665c9568c3faaa9c416a7134308f2d85c3",
"reference": "78c468665c9568c3faaa9c416a7134308f2d85c3",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a158f13992a3147d466af7a23b564ac719a4ddd8",
"reference": "a158f13992a3147d466af7a23b564ac719a4ddd8",
"shasum": ""
},
"require": {
@ -2566,20 +2565,20 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"time": "2016-01-27 05:14:19"
"time": "2016-05-03 18:59:18"
},
{
"name": "symfony/filesystem",
"version": "v2.8.3",
"version": "v2.8.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "65cb36b6539b1d446527d60457248f30d045464d"
"reference": "dee379131dceed90a429e951546b33edfe7dccbb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/65cb36b6539b1d446527d60457248f30d045464d",
"reference": "65cb36b6539b1d446527d60457248f30d045464d",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/dee379131dceed90a429e951546b33edfe7dccbb",
"reference": "dee379131dceed90a429e951546b33edfe7dccbb",
"shasum": ""
},
"require": {
@ -2615,20 +2614,20 @@
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
"time": "2016-02-22 15:02:30"
"time": "2016-04-12 18:01:21"
},
{
"name": "symfony/finder",
"version": "v3.0.3",
"version": "v3.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "623bda0abd9aa29e529c8e9c08b3b84171914723"
"reference": "c54e407b35bc098916704e9fd090da21da4c4f52"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/623bda0abd9aa29e529c8e9c08b3b84171914723",
"reference": "623bda0abd9aa29e529c8e9c08b3b84171914723",
"url": "https://api.github.com/repos/symfony/finder/zipball/c54e407b35bc098916704e9fd090da21da4c4f52",
"reference": "c54e407b35bc098916704e9fd090da21da4c4f52",
"shasum": ""
},
"require": {
@ -2664,20 +2663,20 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
"time": "2016-01-27 05:14:46"
"time": "2016-03-10 11:13:05"
},
{
"name": "symfony/form",
"version": "v2.8.3",
"version": "v2.8.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/form.git",
"reference": "25b71aec36879b4a84c2ea06603dbe7498b31f06"
"reference": "922807a06e25c0c6e06b86f83ffe4710080a8b2e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/form/zipball/25b71aec36879b4a84c2ea06603dbe7498b31f06",
"reference": "25b71aec36879b4a84c2ea06603dbe7498b31f06",
"url": "https://api.github.com/repos/symfony/form/zipball/922807a06e25c0c6e06b86f83ffe4710080a8b2e",
"reference": "922807a06e25c0c6e06b86f83ffe4710080a8b2e",
"shasum": ""
},
"require": {
@ -2738,20 +2737,20 @@
],
"description": "Symfony Form Component",
"homepage": "https://symfony.com",
"time": "2016-02-28 15:05:09"
"time": "2016-04-28 09:59:09"
},
{
"name": "symfony/intl",
"version": "v3.0.3",
"version": "v3.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/intl.git",
"reference": "cafee6f65148dab9058cdb6de5631222aca820d0"
"reference": "a602fe5a36cfc6367c5495b38a88c443570e75da"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/intl/zipball/cafee6f65148dab9058cdb6de5631222aca820d0",
"reference": "cafee6f65148dab9058cdb6de5631222aca820d0",
"url": "https://api.github.com/repos/symfony/intl/zipball/a602fe5a36cfc6367c5495b38a88c443570e75da",
"reference": "a602fe5a36cfc6367c5495b38a88c443570e75da",
"shasum": ""
},
"require": {
@ -2813,20 +2812,20 @@
"l10n",
"localization"
],
"time": "2016-02-23 15:16:06"
"time": "2016-04-01 06:34:33"
},
{
"name": "symfony/options-resolver",
"version": "v2.8.3",
"version": "v2.8.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
"reference": "d1e6e9182d9e5af6367bf85175e708f8b4a828c0"
"reference": "5e4a8ee6e823428257f2002f6daf52de854d8384"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/d1e6e9182d9e5af6367bf85175e708f8b4a828c0",
"reference": "d1e6e9182d9e5af6367bf85175e708f8b4a828c0",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/5e4a8ee6e823428257f2002f6daf52de854d8384",
"reference": "5e4a8ee6e823428257f2002f6daf52de854d8384",
"shasum": ""
},
"require": {
@ -2867,7 +2866,7 @@
"configuration",
"options"
],
"time": "2016-01-21 09:05:51"
"time": "2016-05-09 18:12:35"
},
{
"name": "symfony/polyfill-intl-icu",
@ -2929,16 +2928,16 @@
},
{
"name": "symfony/process",
"version": "v3.0.3",
"version": "v3.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "dfecef47506179db2501430e732adbf3793099c8"
"reference": "53f9407c0bb1c5a79127db8f7bfe12f0f6f3dcdb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/dfecef47506179db2501430e732adbf3793099c8",
"reference": "dfecef47506179db2501430e732adbf3793099c8",
"url": "https://api.github.com/repos/symfony/process/zipball/53f9407c0bb1c5a79127db8f7bfe12f0f6f3dcdb",
"reference": "53f9407c0bb1c5a79127db8f7bfe12f0f6f3dcdb",
"shasum": ""
},
"require": {
@ -2974,20 +2973,20 @@
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
"time": "2016-02-02 13:44:19"
"time": "2016-04-14 15:30:28"
},
{
"name": "symfony/property-access",
"version": "v3.0.3",
"version": "v3.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/property-access.git",
"reference": "f138bcc0cdaf6a6aba99ecab20d235b394f46eba"
"reference": "d9deeca5d2f0dffd90f9547d3d9a2007bd6ee61b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/property-access/zipball/f138bcc0cdaf6a6aba99ecab20d235b394f46eba",
"reference": "f138bcc0cdaf6a6aba99ecab20d235b394f46eba",
"url": "https://api.github.com/repos/symfony/property-access/zipball/d9deeca5d2f0dffd90f9547d3d9a2007bd6ee61b",
"reference": "d9deeca5d2f0dffd90f9547d3d9a2007bd6ee61b",
"shasum": ""
},
"require": {
@ -3034,20 +3033,20 @@
"property path",
"reflection"
],
"time": "2016-02-13 09:23:44"
"time": "2016-04-20 18:53:54"
},
{
"name": "symfony/routing",
"version": "v2.8.3",
"version": "v2.8.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
"reference": "ae38e64bae52753c0f4a1a6583f5ba9bb2b742ab"
"reference": "0d814e0c1dc07cb688ca2298bbf7b32ace80cee1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/routing/zipball/ae38e64bae52753c0f4a1a6583f5ba9bb2b742ab",
"reference": "ae38e64bae52753c0f4a1a6583f5ba9bb2b742ab",
"url": "https://api.github.com/repos/symfony/routing/zipball/0d814e0c1dc07cb688ca2298bbf7b32ace80cee1",
"reference": "0d814e0c1dc07cb688ca2298bbf7b32ace80cee1",
"shasum": ""
},
"require": {
@ -3109,20 +3108,20 @@
"uri",
"url"
],
"time": "2016-02-04 13:53:00"
"time": "2016-05-03 12:21:46"
},
{
"name": "symfony/twig-bridge",
"version": "v2.8.3",
"version": "v2.8.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/twig-bridge.git",
"reference": "ba898e4714734948dc00c4d776e1d6be165ff8c4"
"reference": "789ffb6f63574d8d5573520ba12156f5c395fcd5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/twig-bridge/zipball/ba898e4714734948dc00c4d776e1d6be165ff8c4",
"reference": "ba898e4714734948dc00c4d776e1d6be165ff8c4",
"url": "https://api.github.com/repos/symfony/twig-bridge/zipball/789ffb6f63574d8d5573520ba12156f5c395fcd5",
"reference": "789ffb6f63574d8d5573520ba12156f5c395fcd5",
"shasum": ""
},
"require": {
@ -3134,7 +3133,7 @@
"symfony/console": "~2.8|~3.0.0",
"symfony/expression-language": "~2.4|~3.0.0",
"symfony/finder": "~2.3|~3.0.0",
"symfony/form": "~2.8",
"symfony/form": "~2.8.4",
"symfony/http-kernel": "~2.8|~3.0.0",
"symfony/polyfill-intl-icu": "~1.0",
"symfony/routing": "~2.2|~3.0.0",
@ -3190,20 +3189,20 @@
],
"description": "Symfony Twig Bridge",
"homepage": "https://symfony.com",
"time": "2016-02-22 15:02:30"
"time": "2016-03-30 10:37:34"
},
{
"name": "symfony/yaml",
"version": "v3.0.3",
"version": "v3.0.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "b5ba64cd67ecd6887f63868fa781ca094bd1377c"
"reference": "0047c8366744a16de7516622c5b7355336afae96"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/b5ba64cd67ecd6887f63868fa781ca094bd1377c",
"reference": "b5ba64cd67ecd6887f63868fa781ca094bd1377c",
"url": "https://api.github.com/repos/symfony/yaml/zipball/0047c8366744a16de7516622c5b7355336afae96",
"reference": "0047c8366744a16de7516622c5b7355336afae96",
"shasum": ""
},
"require": {
@ -3239,7 +3238,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2016-02-23 15:16:06"
"time": "2016-03-04 07:55:57"
},
{
"name": "twig/extensions",
@ -3344,23 +3343,23 @@
},
{
"name": "vlucas/phpdotenv",
"version": "v2.2.0",
"version": "v2.2.1",
"source": {
"type": "git",
"url": "https://github.com/vlucas/phpdotenv.git",
"reference": "9caf304153dc2288e4970caec6f1f3b3bc205412"
"reference": "63f37b9395e8041cd4313129c08ece896d06ca8e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/9caf304153dc2288e4970caec6f1f3b3bc205412",
"reference": "9caf304153dc2288e4970caec6f1f3b3bc205412",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/63f37b9395e8041cd4313129c08ece896d06ca8e",
"reference": "63f37b9395e8041cd4313129c08ece896d06ca8e",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"phpunit/phpunit": "^4.8|^5.0"
"phpunit/phpunit": "^4.8 || ^5.0"
},
"type": "library",
"extra": {
@ -3375,7 +3374,7 @@
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD"
"BSD-3-Clause-Attribution"
],
"authors": [
{
@ -3385,13 +3384,12 @@
}
],
"description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
"homepage": "http://github.com/vlucas/phpdotenv",
"keywords": [
"dotenv",
"env",
"environment"
],
"time": "2015-12-29 15:10:30"
"time": "2016-04-15 10:48:49"
}
],
"aliases": [],

View File

@ -39,6 +39,9 @@ class Env {
self::$assets_url = plugins_url('/assets', $file);
$wp_upload_dir = wp_upload_dir();
self::$temp_path = $wp_upload_dir['basedir'].'/'.self::$plugin_name;
if(!is_dir(self::$temp_path)) {
mkdir(self::$temp_path);
}
self::$temp_url = $wp_upload_dir['baseurl'].'/'.self::$plugin_name;
self::$languages_path = self::$path . '/lang';
self::$lib_path = self::$path . '/lib';

View File

@ -13,6 +13,7 @@ class Hooks {
$this->setupImageSize();
$this->setupListing();
$this->setupSubscriptionEvents();
$this->setupPostNotifications();
}
function setupSubscriptionEvents() {
@ -146,4 +147,12 @@ class Hooks {
return $status;
}
}
function setupPostNotifications() {
add_filter(
'publish_post',
'\MailPoet\Newsletter\Scheduler\Scheduler::schedulePostNotification',
10, 1
);
}
}

View File

@ -82,6 +82,7 @@ class Initializer {
$newsletter_option_fields = Env::$db_prefix . 'newsletter_option_fields';
$newsletter_option = Env::$db_prefix . 'newsletter_option';
$newsletter_links = Env::$db_prefix . 'newsletter_links';
$newsletter_posts = Env::$db_prefix . 'newsletter_posts';
$statistics_newsletters = Env::$db_prefix . 'statistics_newsletters';
$statistics_clicks = Env::$db_prefix . 'statistics_clicks';
$statistics_opens = Env::$db_prefix . 'statistics_opens';
@ -101,6 +102,7 @@ class Initializer {
define('MP_NEWSLETTER_SEGMENT_TABLE', $newsletter_segment);
define('MP_NEWSLETTER_OPTION_FIELDS_TABLE', $newsletter_option_fields);
define('MP_NEWSLETTER_LINKS_TABLE', $newsletter_links);
define('MP_NEWSLETTER_POSTS_TABLE', $newsletter_posts);
define('MP_NEWSLETTER_OPTION_TABLE', $newsletter_option);
define('MP_STATISTICS_NEWSLETTERS_TABLE', $statistics_newsletters);
define('MP_STATISTICS_CLICKS_TABLE', $statistics_clicks);

View File

@ -14,6 +14,7 @@ use MailPoet\Subscribers\ImportExport\BootStrapMenu;
use MailPoet\Util\DKIM;
use MailPoet\Util\Permissions;
use MailPoet\Listing;
use MailPoet\WP\DateTime;
if(!defined('ABSPATH')) exit;
@ -46,9 +47,9 @@ class Menu {
$this->assets_url . '/img/menu_icon.png',
30
);
add_submenu_page(
$newsletters_page = add_submenu_page(
'mailpoet',
__('Newsletters'),
$this->setPageTitle(__('Newsletters')),
__('Newsletters'),
'manage_options',
'mailpoet-newsletters',
@ -57,9 +58,21 @@ class Menu {
'newsletters'
)
);
add_submenu_page(
// add limit per page to screen options
add_action('load-'.$newsletters_page, function() {
add_screen_option('per_page', array(
'label' => _x(
'Number of newsletters per page',
'newsletters per page (screen options)'
),
'option' => 'mailpoet_newsletters_per_page'
));
});
$forms_page = add_submenu_page(
'mailpoet',
__('Forms'),
$this->setPageTitle(__('Forms')),
__('Forms'),
'manage_options',
'mailpoet-forms',
@ -68,9 +81,20 @@ class Menu {
'forms'
)
);
// add limit per page to screen options
add_action('load-'.$forms_page, function() {
add_screen_option('per_page', array(
'label' => _x(
'Number of forms per page',
'forms per page (screen options)'
),
'option' => 'mailpoet_forms_per_page'
));
});
$subscribers_page = add_submenu_page(
'mailpoet',
__('Subscribers'),
$this->setPageTitle(__('Subscribers')),
__('Subscribers'),
'manage_options',
'mailpoet-subscribers',
@ -90,9 +114,9 @@ class Menu {
));
});
add_submenu_page(
$segments_page = add_submenu_page(
'mailpoet',
__('Segments'),
$this->setPageTitle(__('Segments')),
__('Segments'),
'manage_options',
'mailpoet-segments',
@ -101,9 +125,21 @@ class Menu {
'segments'
)
);
// add limit per page to screen options
add_action('load-'.$segments_page, function() {
add_screen_option('per_page', array(
'label' => _x(
'Number of segments per page',
'segments per page (screen options)'
),
'option' => 'mailpoet_segments_per_page'
));
});
add_submenu_page(
'mailpoet',
__('Settings'),
$this->setPageTitle( __('Settings')),
__('Settings'),
'manage_options',
'mailpoet-settings',
@ -113,8 +149,8 @@ class Menu {
)
);
add_submenu_page(
null,
__('Import'),
'admin.php?page=mailpoet-subscribers',
$this->setPageTitle( __('Import')),
__('Import'),
'manage_options',
'mailpoet-import',
@ -123,9 +159,10 @@ class Menu {
'import'
)
);
add_submenu_page(
null,
__('Export'),
true,
$this->setPageTitle(__('Export')),
__('Export'),
'manage_options',
'mailpoet-export',
@ -136,8 +173,8 @@ class Menu {
);
add_submenu_page(
null,
__('Welcome'),
true,
$this->setPageTitle(__('Welcome')),
__('Welcome'),
'manage_options',
'mailpoet-welcome',
@ -148,8 +185,8 @@ class Menu {
);
add_submenu_page(
null,
__('Update'),
true,
$this->setPageTitle(__('Update')),
__('Update'),
'manage_options',
'mailpoet-update',
@ -160,8 +197,8 @@ class Menu {
);
add_submenu_page(
null,
__('Form editor'),
true,
$this->setPageTitle(__('Form')),
__('Form editor'),
'manage_options',
'mailpoet-form-editor',
@ -172,8 +209,8 @@ class Menu {
);
add_submenu_page(
null,
__('Newsletter editor'),
true,
$this->setPageTitle(__('Newsletter')),
__('Newsletter editor'),
'manage_options',
'mailpoet-newsletter-editor',
@ -185,7 +222,7 @@ class Menu {
add_submenu_page(
'mailpoet',
__('Cron'),
$this->setPageTitle(__('Cron')),
__('Cron'),
'manage_options',
'mailpoet-cron',
@ -317,15 +354,8 @@ class Menu {
function subscribers() {
$data = array();
// listing: limit per page
$listing_per_page = get_user_meta(
get_current_user_id(), 'mailpoet_subscribers_per_page', true
);
$data['per_page'] = (!empty($listing_per_page))
? (int)$listing_per_page
: Listing\Handler::DEFAULT_LIMIT_PER_PAGE;
$data['segments'] = Segment::findArray();
$data['items_per_page'] = $this->getLimitPerPage('subscribers');
$data['segments'] = Segment::getSegmentsWithSubscriberCount();
$data['custom_fields'] = array_map(function($field) {
$field['params'] = unserialize($field['params']);
@ -349,11 +379,14 @@ class Menu {
function segments() {
$data = array();
$data['items_per_page'] = $this->getLimitPerPage('segments');
echo $this->renderer->render('segments.html', $data);
}
function forms() {
$data = array();
$data['items_per_page'] = $this->getLimitPerPage('forms');
$data['segments'] = Segment::findArray();
echo $this->renderer->render('forms.html', $data);
@ -364,11 +397,24 @@ class Menu {
$data = array();
$data['items_per_page'] = $this->getLimitPerPage('newsletters');
$data['segments'] = Segment::getSegmentsWithSubscriberCount();
$data['settings'] = Setting::getAll();
$data['roles'] = $wp_roles->get_names();
$data['roles']['mailpoet_all'] = __('In any WordPress role');
$date_time = new DateTime();
$data['current_date'] = $date_time->getCurrentDate(DateTime::DEFAULT_DATE_FORMAT);
$data['current_time'] = $date_time->getCurrentTime();
$data['schedule_time_of_day'] = $date_time->getTimeInterval(
'00:00:00',
'+1 hour',
24
);
wp_enqueue_script('jquery-ui');
wp_enqueue_script('jquery-ui-datepicker');
echo $this->renderer->render('newsletters.html', $data);
}
@ -383,6 +429,7 @@ class Menu {
$data = array(
'customFields' => $custom_fields,
'settings' => Setting::getAll(),
'sub_menu' => 'mailpoet-newsletters'
);
wp_enqueue_media();
wp_enqueue_script('tinymce-wplink', includes_url('js/tinymce/plugins/wplink/plugin.js'));
@ -393,17 +440,19 @@ class Menu {
function import() {
$import = new BootStrapMenu('import');
$data = $import->bootstrap();
$data['sub_menu'] = 'mailpoet-subscribers';
echo $this->renderer->render('subscribers/importExport/import.html', $data);
}
function export() {
$export = new BootStrapMenu('export');
$data = $export->bootstrap();
$data['sub_menu'] = 'mailpoet-subscribers';
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();
@ -412,12 +461,12 @@ class Menu {
$data = array(
'form' => $form,
'pages' => Pages::getAll(),
'segments' => Segment::getPublic()
->findArray(),
'segments' => Segment::getPublic()->findArray(),
'styles' => FormRenderer::getStyles($form),
'date_types' => Block\Date::getDateTypes(),
'date_formats' => Block\Date::getDateFormats(),
'month_names' => Block\Date::getMonthNames()
'month_names' => Block\Date::getMonthNames(),
'sub_menu' => 'mailpoet-forms'
);
echo $this->renderer->render('form/editor.html', $data);
@ -426,4 +475,25 @@ class Menu {
function cron() {
echo $this->renderer->render('cron.html');
}
function setPageTitle($title) {
return sprintf(
'%s - %s',
__('MailPoet'),
$title
);
}
function getLimitPerPage($model = null) {
if($model === null) {
return Listing\Handler::DEFAULT_LIMIT_PER_PAGE;
}
$listing_per_page = get_user_meta(
get_current_user_id(), 'mailpoet_'.$model.'_per_page', true
);
return (!empty($listing_per_page))
? (int)$listing_per_page
: Listing\Handler::DEFAULT_LIMIT_PER_PAGE;
}
}

View File

@ -23,6 +23,7 @@ class Migrator {
'newsletter_option',
'newsletter_segment',
'newsletter_links',
'newsletter_posts',
'forms',
'statistics_newsletters',
'statistics_clicks',
@ -59,10 +60,10 @@ class Migrator {
'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,',
'description varchar(250) NOT NULL DEFAULT "",',
'created_at TIMESTAMP NULL,',
'deleted_at TIMESTAMP NULL,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name (name)'
);
@ -101,7 +102,6 @@ class Migrator {
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'newsletter_rendered_body longtext,',
'newsletter_rendered_body_hash varchar(250) NULL DEFAULT NULL,',
'newsletter_rendered_subject varchar(250) NULL DEFAULT NULL,',
'subscribers longtext,',
'status varchar(12) NULL DEFAULT NULL,',
@ -110,11 +110,11 @@ class Migrator {
'count_processed mediumint(9) NOT NULL DEFAULT 0,',
'count_to_process mediumint(9) NOT NULL DEFAULT 0,',
'count_failed mediumint(9) NOT NULL DEFAULT 0,',
'scheduled_at TIMESTAMP NOT NULL DEFAULT 0,',
'processed_at TIMESTAMP NOT NULL DEFAULT 0,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'scheduled_at TIMESTAMP NULL,',
'processed_at TIMESTAMP NULL,',
'created_at TIMESTAMP NULL,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'deleted_at TIMESTAMP NULL,',
'PRIMARY KEY (id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
@ -124,13 +124,13 @@ class Migrator {
$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,',
'first_name tinytext NOT NULL DEFAULT "",',
'last_name tinytext NOT NULL DEFAULT "",',
'email varchar(150) NOT NULL,',
'status varchar(12) NOT NULL DEFAULT "unconfirmed",',
'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,',
'created_at TIMESTAMP NULL,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'deleted_at TIMESTAMP NULL,',
'PRIMARY KEY (id),',
'UNIQUE KEY email (email)'
);
@ -143,8 +143,8 @@ class Migrator {
'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,',
'created_at TIMESTAMP NULL,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY subscriber_segment (subscriber_id,segment_id)'
);
@ -156,9 +156,9 @@ class Migrator {
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'subscriber_id mediumint(9) NOT NULL,',
'custom_field_id mediumint(9) NOT NULL,',
'value varchar(255) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'value varchar(255) NOT NULL DEFAULT "",',
'created_at TIMESTAMP NULL,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY subscriber_id_custom_field_id (subscriber_id,custom_field_id)'
);
@ -168,17 +168,17 @@ class Migrator {
function newsletters() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'subject varchar(250) NOT NULL,',
'subject varchar(250) NOT NULL DEFAULT "",',
'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,',
'sender_address varchar(150) NOT NULL DEFAULT "",',
'sender_name varchar(150) NOT NULL DEFAULT "",',
'reply_to_address varchar(150) NOT NULL DEFAULT "",',
'reply_to_name varchar(150) NOT NULL DEFAULT "",',
'preheader varchar(250) NOT NULL DEFAULT "",',
'body longtext,',
'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,',
'created_at TIMESTAMP NULL,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'deleted_at TIMESTAMP NULL,',
'PRIMARY KEY (id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
@ -192,8 +192,8 @@ class Migrator {
'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,',
'created_at TIMESTAMP NULL,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
@ -204,8 +204,8 @@ class Migrator {
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,',
'newsletter_type varchar(90) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'created_at TIMESTAMP NULL,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name_newsletter_type (newsletter_type,name)'
);
@ -217,9 +217,9 @@ class Migrator {
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'option_field_id mediumint(9) NOT NULL,',
'value varchar(255) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'value varchar(255) NOT NULL DEFAULT "",',
'created_at TIMESTAMP NULL,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY newsletter_id_option_field_id (newsletter_id,option_field_id)'
);
@ -231,8 +231,8 @@ class Migrator {
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'segment_id mediumint(9) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'created_at TIMESTAMP NULL,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY newsletter_segment (newsletter_id,segment_id)'
);
@ -246,6 +246,18 @@ class Migrator {
'queue_id mediumint(9) NOT NULL,',
'url varchar(255) NOT NULL,',
'hash varchar(20) NOT NULL,',
'created_at TIMESTAMP NULL,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function newsletter_posts() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'post_id mediumint(9) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)',
@ -260,9 +272,9 @@ class Migrator {
'body longtext,',
'settings longtext,',
'styles longtext,',
'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,',
'created_at TIMESTAMP NULL,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'deleted_at TIMESTAMP NULL,',
'PRIMARY KEY (id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
@ -274,7 +286,7 @@ class Migrator {
'newsletter_id mediumint(9) NOT NULL,',
'subscriber_id mediumint(9) NOT NULL,',
'queue_id mediumint(9) NOT NULL,',
'sent_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'sent_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
@ -288,8 +300,8 @@ class Migrator {
'queue_id mediumint(9) NOT NULL,',
'link_id mediumint(9) NOT NULL,',
'count mediumint(9) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'created_at TIMESTAMP NULL,',
'updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
@ -301,7 +313,7 @@ class Migrator {
'newsletter_id mediumint(9) NOT NULL,',
'subscriber_id mediumint(9) NOT NULL,',
'queue_id mediumint(9) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'created_at TIMESTAMP NULL,',
'PRIMARY KEY (id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
@ -313,7 +325,7 @@ class Migrator {
'newsletter_id mediumint(9) NOT NULL,',
'subscriber_id mediumint(9) NOT NULL,',
'queue_id mediumint(9) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'created_at TIMESTAMP NULL,',
'PRIMARY KEY (id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
@ -324,7 +336,7 @@ class Migrator {
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'form_id mediumint(9) NOT NULL,',
'subscriber_id mediumint(9) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'created_at TIMESTAMP NULL,',
'PRIMARY KEY (id),',
'UNIQUE KEY form_subscriber (form_id,subscriber_id)'
);

View File

@ -10,7 +10,7 @@ use \MailPoet\Segments\WP;
use \MailPoet\Models\Setting;
use \MailPoet\Settings\Pages;
if (!defined('ABSPATH')) exit;
if(!defined('ABSPATH')) exit;
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
@ -36,7 +36,7 @@ class Populator {
$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) {
if($wpdb->get_var("SELECT COUNT(*) FROM " . $table . " WHERE " . implode(' AND ', $column_conditions)) === 0) {
$wpdb->insert(
$table,
$field
@ -140,6 +140,14 @@ class Populator {
function newsletter_option_fields() {
return array(
array(
'name' => 'isScheduled',
'newsletter_type' => 'standard',
),
array(
'name' => 'scheduledAt',
'newsletter_type' => 'standard',
),
array(
'name' => 'event',
'newsletter_type' => 'welcome',
@ -184,10 +192,6 @@ class Populator {
array(
'name' => 'schedule',
'newsletter_type' => 'notification',
),
array(
'name' => 'segments',
'newsletter_type' => 'notification',
)
);
}
@ -207,7 +211,7 @@ class Populator {
$_this = $this;
array_map(function($row) use ($_this, $table) {
if (!$_this->rowExists($table, $row)) {
if(!$_this->rowExists($table, $row)) {
$_this->insertRow($table, $row);
}
}, $rows);

View File

@ -142,7 +142,7 @@ class BlankTemplate {
"blocks" => array(
array(
"type" => "footer",
"text" => "<a href=\"[subscription:unsubscribe_url]\">Unsubscribe</a> | <a href=\"[subscription:manage_url]\">Manage subscription</a><br /><b>Add your postal address here!</b>",
"text" => "<a href=\"[link:subscription_unsubscribe_url]\">Unsubscribe</a> | <a href=\"[link:subscription_manage_url]\">Manage subscription</a><br /><b>Add your postal address here!</b>",
"styles" => array(
"block" => array(
"backgroundColor" => "transparent"

View File

@ -46,7 +46,7 @@ class FranksRoastHouseTemplate {
"blocks" => array(
array(
"type" => "header",
"text" => __("Display problems?&nbsp;<a href=\"[newsletter:view_in_browser_url]\">View it in your browser</a>"),
"text" => __("Display problems?&nbsp;<a href=\"[link:newsletter_view_in_browser_url]\">View it in your browser</a>"),
"styles" => array(
"block" => array(
"backgroundColor" => "#ccc6c6"
@ -280,7 +280,7 @@ class FranksRoastHouseTemplate {
"blocks" => array(
array(
"type" => "footer",
"text" => __("<p><a href=\"[subscription:unsubscribe_url]\">Unsubscribe</a> | <a href=\"[subscription:manage_url]\">Manage subscription</a><br />12345 MailPoet Drive, EmailVille, 76543</p>"),
"text" => __("<p><a href=\"[link:subscription_unsubscribe_url]\">Unsubscribe</a> | <a href=\"[link:subscription_manage_url]\">Manage subscription</a><br />12345 MailPoet Drive, EmailVille, 76543</p>"),
"styles" => array(
"block" => array(
"backgroundColor" => "#a9a7a7"

View File

@ -242,7 +242,7 @@ class PostNotificationsBlankTemplate {
"blocks" => array(
array(
"type" => "footer",
"text" => __("<a href=\"[subscription:unsubscribe_url]\">Unsubscribe</a> | <a href=\"[subscription:manage_url]\">Manage subscription</a><br /><b>Add your postal address here!</b>"),
"text" => __("<a href=\"[link:subscription_unsubscribe_url]\">Unsubscribe</a> | <a href=\"[link:subscription_manage_url]\">Manage subscription</a><br /><b>Add your postal address here!</b>"),
"styles" => array(
"block" => array(
"backgroundColor" => "transparent"

View File

@ -46,7 +46,7 @@ class WelcomeTemplate {
"blocks" => array(
array(
"type" => "header",
"text" => __("Display problems?&nbsp;<a href=\"[newsletter:view_in_browser_url]\">View it in your browser</a>"),
"text" => __("Display problems?&nbsp;<a href=\"[link:newsletter_view_in_browser_url]\">View it in your browser</a>"),
"styles" => array(
"block" => array(
"backgroundColor" => "transparent"
@ -224,7 +224,7 @@ class WelcomeTemplate {
"blocks" => array(
array(
"type" => "footer",
"text" => __("<a href=\"[subscription:unsubscribe_url]\">Unsubscribe</a> | <a href=\"[subscription:manage_url]\">Manage subscription</a><br /><b>Add your postal address here!</b>"),
"text" => __("<a href=\"[link:subscription_unsubscribe_url]\">Unsubscribe</a> | <a href=\"[link:subscription_manage_url]\">Manage subscription</a><br /><b>Add your postal address here!</b>"),
"styles" => array(
"block" => array(
"backgroundColor" => "transparent"

View File

@ -2,9 +2,10 @@
namespace MailPoet\Config;
use MailPoet\Cron\Daemon;
use MailPoet\Newsletter\Viewer\ViewInBrowser;
use MailPoet\Statistics\Track\Clicks;
use MailPoet\Statistics\Track\Opens;
use MailPoet\Subscription;
use MailPoet\Statistics\Track\Clicks;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
@ -25,7 +26,7 @@ class PublicAPI {
Helpers::underscoreToCamelCase($_GET['action']) :
false;
$this->data = isset($_GET['data']) ?
$_GET['data'] :
unserialize(base64_decode($_GET['data'])) :
false;
}
@ -35,33 +36,29 @@ class PublicAPI {
}
function queue() {
try {
$queue = new Daemon($this->_decodeData());
$this->_checkAndCallMethod($queue, $this->action);
} catch(\Exception $e) {
}
$queue = new Daemon($this->data);
$this->_checkAndCallMethod($queue, $this->action);
}
function subscription() {
try {
$subscription = new Subscription\Pages($this->action, $this->_decodeData());
$this->_checkAndCallMethod($subscription, $this->action);
} catch(\Exception $e) {
}
$subscription = new Subscription\Pages($this->action, $this->data);
$this->_checkAndCallMethod($subscription, $this->action);
}
function track() {
try {
if($this->action === 'click') {
$track_class = new Clicks($this->data);
}
if($this->action === 'open') {
$track_class = new Opens($this->data);
}
if(!isset($track_class)) return;
$track_class->track();
} catch(\Exception $e) {
if($this->action === 'click') {
$track_class = new Clicks($this->data);
}
if($this->action === 'open') {
$track_class = new Opens($this->data);
}
if(!isset($track_class)) return;
$track_class->track();
}
function viewInBrowser() {
$viewer = new ViewInBrowser($this->data);
$viewer->view();
}
private function _checkAndCallMethod($class, $method, $terminate_request = false) {
@ -77,12 +74,4 @@ class PublicAPI {
)
);
}
private function _decodeData() {
if($this->data !== false) {
return unserialize(base64_decode($this->data));
} else {
return array();
}
}
}

View File

@ -7,13 +7,13 @@ use MailPoet\Util\Security;
if(!defined('ABSPATH')) exit;
class CronHelper {
const daemon_execution_limit = 20;
const daemon_execution_timeout = 35;
const daemon_request_timeout = 2;
const DAEMON_EXECUTION_LIMIT = 20;
const DAEMON_EXECUTION_TIMEOUT = 35;
const DAEMON_REQUEST_TIMEOUT = 2;
static function createDaemon($token) {
$daemon = array(
'status' => 'starting',
'status' => Daemon::STATUS_STARTING,
'counter' => 0,
'token' => $token
);
@ -37,7 +37,7 @@ class CronHelper {
return Security::generateRandomString();
}
static function accessDaemon($token, $timeout = self::daemon_request_timeout) {
static function accessDaemon($token, $timeout = self::DAEMON_REQUEST_TIMEOUT) {
$data = serialize(array('token' => $token));
$url = '/?mailpoet&endpoint=queue&action=run&data=' .
base64_encode($data);
@ -71,7 +71,7 @@ class CronHelper {
static function checkExecutionTimer($timer) {
$elapsed_time = microtime(true) - $timer;
if($elapsed_time >= self::daemon_execution_limit) {
if($elapsed_time >= self::DAEMON_EXECUTION_LIMIT) {
throw new \Exception(__('Maximum execution time reached.'));
}
}

View File

@ -12,12 +12,15 @@ class Daemon {
public $daemon;
public $data;
public $refreshed_token;
const daemon_request_timeout = 5;
const STATUS_STOPPED = 'stopped';
const STATUS_STOPPING = 'stopping';
const STATUS_STARTED = 'started';
const STATUS_STARTING = 'starting';
const REQUEST_TIMEOUT = 5;
private $timer;
function __construct($data) {
if(empty($data)) $this->abortWithError(__('Invalid or missing cron data.'));
set_time_limit(0);
ignore_user_abort();
$this->daemon = CronHelper::getDaemon();
$this->token = CronHelper::createToken();
@ -27,6 +30,7 @@ class Daemon {
function run() {
$daemon = $this->daemon;
set_time_limit(0);
if(!$daemon) {
$this->abortWithError(__('Daemon does not exist.'));
}
@ -42,10 +46,11 @@ class Daemon {
$queue = new SendingQueue();
$queue->process($this->timer);
} catch(\Exception $e) {
// continue processing, no need to catch errors
}
$elapsed_time = microtime(true) - $this->timer;
if($elapsed_time < CronHelper::daemon_execution_limit) {
sleep(CronHelper::daemon_execution_limit - $elapsed_time);
if($elapsed_time < CronHelper::DAEMON_EXECUTION_LIMIT) {
sleep(CronHelper::DAEMON_EXECUTION_LIMIT - $elapsed_time);
}
// after each execution, re-read daemon data in case it was deleted or
// its status has changed
@ -55,8 +60,8 @@ class Daemon {
}
$daemon['counter']++;
$this->abortIfStopped($daemon);
if($daemon['status'] === 'starting') {
$daemon['status'] = 'started';
if($daemon['status'] === self::STATUS_STARTING) {
$daemon['status'] = self::STATUS_STARTED;
}
$daemon['token'] = $this->token;
CronHelper::saveDaemon($daemon);
@ -64,9 +69,9 @@ class Daemon {
}
function abortIfStopped($daemon) {
if($daemon['status'] === 'stopped') exit;
if($daemon['status'] === 'stopping') {
$daemon['status'] = 'stopped';
if($daemon['status'] === self::STATUS_STOPPED) exit;
if($daemon['status'] === self::STATUS_STOPPING) {
$daemon['status'] = self::STATUS_STOPPED;
CronHelper::saveDaemon($daemon);
exit;
}
@ -77,7 +82,7 @@ class Daemon {
}
function callSelf() {
CronHelper::accessDaemon($this->token, self::daemon_request_timeout);
CronHelper::accessDaemon($this->token, self::REQUEST_TIMEOUT);
exit;
}
}

View File

@ -23,25 +23,25 @@ class Supervisor {
// if the daemon is stopped, return its status and do nothing
if(!$this->force_run &&
isset($daemon['status']) &&
$daemon['status'] === 'stopped'
$daemon['status'] === Daemon::STATUS_STOPPED
) {
return $this->formatDaemonStatusMessage($daemon['status']);
}
$elapsed_time = time() - (int) $daemon['updated_at'];
$elapsed_time = time() - (int)$daemon['updated_at'];
// if it's been less than 40 seconds since last execution and we're not
// force-running the daemon, return its status and do nothing
if($elapsed_time < CronHelper::daemon_execution_timeout && !$this->force_run) {
if($elapsed_time < CronHelper::DAEMON_EXECUTION_TIMEOUT && !$this->force_run) {
return $this->formatDaemonStatusMessage($daemon['status']);
}
// if it's been less than 40 seconds since last execution, we are
// force-running the daemon and it's either being started or stopped,
// return its status and do nothing
elseif($elapsed_time < CronHelper::daemon_execution_timeout &&
elseif($elapsed_time < CronHelper::DAEMON_EXECUTION_TIMEOUT &&
$this->force_run &&
in_array($daemon['status'], array(
'stopping',
'starting'
Daemon::STATUS_STOPPING,
Daemon::STATUS_STARTING
))
) {
return $this->formatDaemonStatusMessage($daemon['status']);

View File

@ -2,16 +2,13 @@
namespace MailPoet\Cron\Workers;
use Carbon\Carbon;
use Cron\CronExpression as Cron;
use MailPoet\Cron\CronHelper;
use MailPoet\Models\Newsletter;
use MailPoet\Models\SendingQueue;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Models\SubscriberSegment;
use MailPoet\Newsletter\Renderer\PostProcess\OpenTracking;
use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Util\Helpers;
use MailPoet\Newsletter\Scheduler\Scheduler as NewsletterScheduler;
require_once(ABSPATH . 'wp-includes/pluggable.php');
@ -19,6 +16,7 @@ if(!defined('ABSPATH')) exit;
class Scheduler {
public $timer;
const UNCONFIRMED_SUBSCRIBER_RESCHEDULE_TIMEOUT = 5;
function __construct($timer = false) {
$this->timer = ($timer) ? $timer : microtime(true);
@ -30,17 +28,17 @@ class Scheduler {
->whereLte('scheduled_at', Carbon::createFromTimestamp(current_time('timestamp')))
->findMany();
if(!count($scheduled_queues)) return;
foreach($scheduled_queues as $i=>$queue) {
foreach($scheduled_queues as $i => $queue) {
$newsletter = Newsletter::filter('filterWithOptions')
->findOne($queue->newsletter_id);
if(!$newsletter || $newsletter->deleted_at !== null) {
$queue->delete();
}
else if($newsletter->type === 'welcome') {
} elseif($newsletter->type === 'welcome') {
$this->processWelcomeNewsletter($newsletter, $queue);
}
else if($newsletter->type === 'notification') {
} elseif($newsletter->type === 'notification') {
$this->processPostNotificationNewsletter($newsletter, $queue);
} elseif($newsletter->type === 'standard') {
$this->processScheduledStandardNewsletter($newsletter, $queue);
}
CronHelper::checkExecutionTimer($this->timer);
}
@ -48,16 +46,20 @@ class Scheduler {
function processWelcomeNewsletter($newsletter, $queue) {
$subscriber = unserialize($queue->subscribers);
$subscriber_id = $subscriber['to_process'][0];
if(empty($subscriber['to_process'][0])) {
$queue->delete();
return;
}
$subscriber_id = (int)$subscriber['to_process'][0];
if($newsletter->event === 'segment') {
if ($this->verifyMailPoetSubscriber($subscriber_id, $newsletter, $queue) === false) {
if($this->verifyMailPoetSubscriber($subscriber_id, $newsletter, $queue) === false) {
return;
}
}
else if($newsletter->event === 'user') {
if ($this->verifyWPSubscriber($subscriber_id, $newsletter) === false) {
$queue->delete();
return;
} else {
if($newsletter->event === 'user') {
if($this->verifyWPSubscriber($subscriber_id, $newsletter, $queue) === false) {
return;
}
}
}
$queue->status = null;
@ -65,17 +67,50 @@ class Scheduler {
}
function processPostNotificationNewsletter($newsletter, $queue) {
$next_run_date = $this->getQueueNextRunDate($newsletter->schedule);
$segments = unserialize($newsletter->segments);
$subscribers = Subscriber::getSubscribedInSegments($segments)
$segments = $newsletter->segments()->findArray();
if(empty($segments)) {
$this->deleteQueueOrUpdateNextRunDate($queue, $newsletter);
return;
}
$segment_ids = array_map(function($segment) {
return $segment['id'];
}, $segments);
$subscribers = Subscriber::getSubscribedInSegments($segment_ids)
->findArray();
$subscribers = Helpers::arrayColumn($subscribers, 'subscriber_id');
$subscribers = array_unique($subscribers);
if(!count($subscribers) || !$this->checkIfNewsletterChanged($newsletter)) {
$queue->scheduled_at = $next_run_date;
$queue->save();
if(empty($subscribers)) {
$this->deleteQueueOrUpdateNextRunDate($queue, $newsletter);
return;
}
// schedule new queue if the post notification is not destined for immediate delivery
if ($newsletter->intervalType !== NewsletterScheduler::INTERVAL_IMMEDIATELY) {
$new_queue = SendingQueue::create();
$new_queue->newsletter_id = $newsletter->id;
$new_queue->status = NewsletterScheduler::STATUS_SCHEDULED;
self::deleteQueueOrUpdateNextRunDate($new_queue, $newsletter);
}
$queue->subscribers = serialize(
array(
'to_process' => $subscribers
)
);
$queue->count_total = $queue->count_to_process = count($subscribers);
$queue->status = null;
$queue->save();
}
function processScheduledStandardNewsletter($newsletter, $queue) {
$segments = $newsletter->segments()->findArray();
$segment_ids = array_map(function($segment) {
return $segment['id'];
}, $segments);
$subscribers = Subscriber::getSubscribedInSegments($segment_ids)
->findArray();
$subscribers = Helpers::arrayColumn($subscribers, 'subscriber_id');
$subscribers = array_unique($subscribers);
// update current queue
$queue->subscribers = serialize(
array(
@ -85,84 +120,58 @@ class Scheduler {
$queue->count_total = $queue->count_to_process = count($subscribers);
$queue->status = null;
$queue->save();
// schedule newsletter for next delivery
$new_queue = SendingQueue::create();
$new_queue->newsletter_id = $newsletter->id;
$new_queue->scheduled_at = $next_run_date;
$new_queue->status = 'scheduled';
$new_queue->save();
}
private function verifyMailPoetSubscriber($subscriber_id, $newsletter, $queue) {
function verifyMailPoetSubscriber($subscriber_id, $newsletter, $queue) {
$subscriber = Subscriber::findOne($subscriber_id);
// check if subscriber is in proper segment
$subscriber_in_segment =
SubscriberSegment::where('subscriber_id', $subscriber_id)
->where('segment_id', $newsletter->segment)
->where('status', 'subscribed')
->findOne();
if (!$subscriber_in_segment) {
if(!$subscriber || !$subscriber_in_segment) {
$queue->delete();
return false;
}
// check if subscriber is confirmed (subscribed)
$subscriber = $subscriber_in_segment->subscriber()->findOne();
if ($subscriber->status !== 'subscribed') {
if($subscriber->status !== 'subscribed') {
// reschedule delivery in 5 minutes
$scheduled_at = Carbon::createFromTimestamp(current_time('timestamp'));
$queue->scheduled_at = $scheduled_at->addMinutes(5);
$queue->scheduled_at = $scheduled_at->addMinutes(
self::UNCONFIRMED_SUBSCRIBER_RESCHEDULE_TIMEOUT
);
$queue->save();
return false;
}
return true;
}
private function verifyWPSubscriber($subscriber_id, $newsletter) {
function verifyWPSubscriber($subscriber_id, $newsletter, $queue) {
// check if user has the proper role
$subscriber = Subscriber::findOne($subscriber_id);
if(!$subscriber || $subscriber->wp_user_id === null) {
$queue->delete();
return false;
}
$wp_user = (array) get_userdata($subscriber->wp_user_id);
if($newsletter->role !== \MailPoet\Newsletter\Scheduler\Scheduler::WORDPRESS_ALL_ROLES
&& !in_array($newsletter->role, $wp_user['roles'])) {
&& !in_array($newsletter->role, $wp_user['roles'])
) {
$queue->delete();
return false;
}
return true;
}
// TODO: function will be depreciated once new post notification logic is done
private function checkIfNewsletterChanged($newsletter) {
$previous_queue = SendingQueue::whereNull('status')
->where('newsletter_id', $newsletter->id)
->orderByDesc('id')
->findOne();
if ($previous_queue) return false;
$running_queue = SendingQueue::whereNull('status')
->where('newsletter_id', $newsletter->id)
->orderByDesc('id')
->findOne();
if ($running_queue) return false;
$last_run_queue = SendingQueue::where('status', 'completed')
->where('newsletter_id', $newsletter->id)
->orderByDesc('id')
->findOne();
if(!$last_run_queue) return true;
if((boolean) Setting::getValue('tracking.enabled')) {
// insert tracking code
add_filter('mailpoet_rendering_post_process', function ($template) {
return OpenTracking::process($template);
});
private function deleteQueueOrUpdateNextRunDate($queue, $newsletter) {
if($newsletter->intervalType === NewsletterScheduler::INTERVAL_IMMEDIATELY) {
$queue->delete();
} else {
$next_run_date = NewsletterScheduler::getNextRunDate($newsletter->schedule);
$queue->scheduled_at = $next_run_date;
$queue->save();
}
$renderer = new Renderer($newsletter->asArray());
$rendered_newsletter = $renderer->render();
$new_hash = md5($rendered_newsletter['text']);
$old_hash = $last_run_queue->newsletter_rendered_body_hash;
return $new_hash !== $old_hash;
}
private function getQueueNextRunDate($schedule) {
$schedule = Cron::factory($schedule);
return $schedule->getNextRunDate(current_time('mysql'))
->format('Y-m-d H:i:s');
return;
}
}

View File

@ -4,7 +4,7 @@ namespace MailPoet\Cron\Workers;
use MailPoet\Cron\CronHelper;
use MailPoet\Mailer\Mailer;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterLink;
use MailPoet\Models\NewsletterPost;
use MailPoet\Models\Setting;
use MailPoet\Models\StatisticsNewsletters;
use MailPoet\Models\Subscriber;
@ -20,9 +20,10 @@ class SendingQueue {
public $mta_config;
public $mta_log;
public $processing_method;
public $divider = '***MailPoet***';
private $timer;
const batch_size = 50;
const BATCH_SIZE = 50;
const DIVIDER = '***MailPoet***';
const STATUS_COMPLETED = 'completed';
function __construct($timer = false) {
$this->mta_config = $this->getMailerConfig();
@ -43,6 +44,12 @@ class SendingQueue {
}
$newsletter = $newsletter->asArray();
$newsletter['body'] = $this->getOrRenderNewsletterBody($queue, $newsletter);
if($newsletter['type'] === 'notification' &&
strpos($newsletter['body']['html'], 'data-post-id') === false
){
$queue->delete();
continue;
}
$queue->subscribers = (object) unserialize($queue->subscribers);
if(!isset($queue->subscribers->processed)) {
$queue->subscribers->processed = array();
@ -51,18 +58,18 @@ class SendingQueue {
$queue->subscribers->failed = array();
}
$mailer = $this->configureMailer($newsletter);
foreach(array_chunk($queue->subscribers->to_process, self::batch_size) as
foreach(array_chunk($queue->subscribers->to_process, self::BATCH_SIZE) as
$subscribers_ids) {
$subscribers = Subscriber::whereIn('id', $subscribers_ids)
->findArray();
if (count($subscribers_ids) !== count($subscribers)) {
if(count($subscribers_ids) !== count($subscribers)) {
$queue->subscribers->to_process = $this->recalculateSubscriberCount(
Helpers::arrayColumn($subscribers, 'id'),
$subscribers_ids,
$queue->subscribers->to_process
);
}
if (!count($queue->subscribers->to_process)) {
if(!count($queue->subscribers->to_process)) {
$this->updateQueue($queue);
continue;
}
@ -92,10 +99,9 @@ class SendingQueue {
return OpenTracking::process($template);
});
// render newsletter
list($rendered_newsletter, $queue->newsletter_rendered_body_hash) =
$this->renderNewsletter($newsletter);
// extract and replace links
$processed_newsletter = $this->processLinks(
$rendered_newsletter = $this->renderNewsletter($newsletter);
// process link shortcodes, extract and save links in the database
$processed_newsletter = $this->processLinksAndShortcodes(
$this->joinObject($rendered_newsletter),
$newsletter['id'],
$queue->id
@ -105,9 +111,12 @@ class SendingQueue {
}
else {
// render newsletter
list($newsletter['body'], $queue->newsletter_rendered_body_hash) =
$this->renderNewsletter($newsletter);
$newsletter['body'] = $this->renderNewsletter($newsletter);
}
$this->extractAndSaveNewsletterPosts(
$newsletter['id'],
$newsletter['body']['html']
);
$queue->newsletter_rendered_body = json_encode($newsletter['body']);
$queue->save();
} else {
@ -119,7 +128,7 @@ class SendingQueue {
function processBulkSubscribers($mailer, $newsletter, $subscribers, $queue) {
foreach($subscribers as $subscriber) {
$processed_newsletters[] =
$this->processNewsletter($newsletter, $subscriber, $queue);
$this->processNewsletterBeforeSending($newsletter, $subscriber, $queue);
if(!$queue->newsletter_rendered_subject) {
$queue->newsletter_rendered_subject = $processed_newsletters[0]['subject'];
}
@ -163,7 +172,7 @@ class SendingQueue {
function processIndividualSubscriber($mailer, $newsletter, $subscribers, $queue) {
foreach($subscribers as $subscriber) {
$this->checkSendingLimit();
$processed_newsletter = $this->processNewsletter($newsletter, $subscriber, $queue);
$processed_newsletter = $this->processNewsletterBeforeSending($newsletter, $subscriber, $queue);
if(!$queue->newsletter_rendered_subject) {
$queue->newsletter_rendered_subject = $processed_newsletter['subject'];
}
@ -197,26 +206,29 @@ class SendingQueue {
function renderNewsletter($newsletter) {
$renderer = new Renderer($newsletter);
$rendered_newsletter = $renderer->render();
$rendered_newsletter_hash = md5($rendered_newsletter['text']);
return array($rendered_newsletter, $rendered_newsletter_hash);
return $renderer->render();
}
function processLinks($text, $newsletter_id, $queue_id) {
list($text, $processed_links) = Links::replace($text);
foreach($processed_links as $link) {
// save extracted and processed links
$newsletter_link = NewsletterLink::create();
$newsletter_link->newsletter_id = $newsletter_id;
$newsletter_link->queue_id = $queue_id;
$newsletter_link->hash = $link['hash'];
$newsletter_link->url = $link['url'];
$newsletter_link->save();
}
return $text;
function processLinksAndShortcodes($content, $newsletter_id, $queue_id) {
// process only link shortcodes
$shortcodes = new Shortcodes($newsletter = false, $subscriber = false, $queue_id);
$content = $shortcodes->replace(
$content,
$categories = array('link')
);
// extract and save links and link shortcodes
list($content, $processed_links) =
Links::process(
$content,
$links = false,
$process_link_shortcodes = true,
$queue_id
);
Links::save($processed_links, $newsletter_id, $queue_id);
return $content;
}
function processNewsletter($newsletter, $subscriber = false, $queue) {
function processNewsletterBeforeSending($newsletter, $subscriber = false, $queue) {
$data_for_shortcodes = array(
$newsletter['subject'],
$newsletter['body']['html'],
@ -225,10 +237,11 @@ class SendingQueue {
$processed_newsletter = $this->replaceShortcodes(
$newsletter,
$subscriber,
$queue,
$this->joinObject($data_for_shortcodes)
);
if((boolean) Setting::getValue('tracking.enabled')) {
$processed_newsletter = $this->replaceLinks(
$processed_newsletter = Links::replaceSubscriberData(
$newsletter['id'],
$subscriber['id'],
$queue->id,
@ -242,18 +255,11 @@ class SendingQueue {
return $newsletter;
}
function replaceLinks($newsletter_id, $subscriber_id, $queue_id, $body) {
return str_replace(
'[mailpoet_data]',
sprintf('%s-%s-%s', $newsletter_id, $subscriber_id, $queue_id),
$body
);
}
function replaceShortcodes($newsletter, $subscriber, $body) {
function replaceShortcodes($newsletter, $subscriber, $queue, $body) {
$shortcodes = new Shortcodes(
$newsletter,
$subscriber
$subscriber,
$queue
);
return $shortcodes->replace($body);
}
@ -315,7 +321,7 @@ class SendingQueue {
$queue->count_processed + $queue->count_to_process;
if(!$queue->count_to_process) {
$queue->processed_at = current_time('mysql');
$queue->status = 'completed';
$queue->status = self::STATUS_COMPLETED;
}
$queue->subscribers = serialize((array) $queue->subscribers);
$queue->save();
@ -347,9 +353,9 @@ class SendingQueue {
}
function checkSendingLimit() {
$frequency_interval = (int) $this->mta_config['frequency']['interval'] * 60;
$frequency_limit = (int) $this->mta_config['frequency']['emails'];
$elapsed_time = time() - (int) $this->mta_log['started'];
$frequency_interval = (int)$this->mta_config['frequency']['interval'] * 60;
$frequency_limit = (int)$this->mta_config['frequency']['emails'];
$elapsed_time = time() - (int)$this->mta_log['started'];
if($this->mta_log['sent'] === $frequency_limit &&
$elapsed_time <= $frequency_interval
) {
@ -371,11 +377,22 @@ class SendingQueue {
return array_diff($subscribers_to_process, $subscibers_to_exclude);
}
function extractAndSaveNewsletterPosts($newletter_id, $content) {
preg_match_all('/data-post-id="(\d+)"/ism', $content, $posts);
$posts = $posts[1];
foreach($posts as $post) {
$newletter_post = NewsletterPost::create();
$newletter_post->newsletter_id = $newletter_id;
$newletter_post->post_id = $post;
$newletter_post->save();
}
}
private function joinObject($object = array()) {
return implode($this->divider, $object);
return implode(self::DIVIDER, $object);
}
private function splitObject($object = array()) {
return explode($this->divider, $object);
return explode(self::DIVIDER, $object);
}
}

View File

@ -109,6 +109,10 @@ class Date extends Base {
$month_names = static::getMonthNames();
$html = '';
// empty value label
$html .= '<option value="">'.__('Month').'</option>';
for($i = 1; $i < 13; $i++) {
$is_selected = ($i === $block['selected']) ? 'selected="selected"' : '';
$html .= '<option value="'.$i.'" '.$is_selected.'>';
@ -125,6 +129,7 @@ class Date extends Base {
'from' => (int)strftime('%Y') - 100,
'to' => (int)strftime('%Y')
);
// is default today
if(!empty($block['params']['is_default_today'])) {
$defaults['selected'] = (int)strftime('%Y');
@ -135,6 +140,9 @@ class Date extends Base {
$html = '';
// empty value label
$html .= '<option value="">'.__('Year').'</option>';
// return years as an array
for($i = (int)$block['to']; $i > (int)($block['from'] - 1); $i--) {
$is_selected = ($i === $block['selected']) ? 'selected="selected"' : '';
@ -158,6 +166,9 @@ class Date extends Base {
$html = '';
// empty value label
$html .= '<option value="">'.__('Day').'</option>';
// return days as an array
for($i = 1; $i < 32; $i++) {
$is_selected = ($i === $block['selected']) ? 'selected="selected"' : '';

View File

@ -13,9 +13,12 @@ class Select extends Base {
$html .= static::renderLabel($block);
$html .= '<select class="mailpoet_select" name="'.$field_name.'">';
if(isset($block['params']['label_within'])
&& $block['params']['label_within']) {
if(isset($block['params']['label_within']) && $block['params']['label_within']) {
$html .= '<option value="">'.static::getFieldLabel($block).'</option>';
} else {
if(empty($block['params']['required']) || !$block['params']['required']) {
$html .= '<option value="">-</option>';
}
}
$options = (!empty($block['params']['values'])

View File

@ -93,7 +93,7 @@ class Newsletter extends Model {
}
function getStatistics() {
if ($this->queue === false) {
if($this->queue === false) {
return false;
}
return SendingQueue::tableAlias('queues')

View File

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

View File

@ -28,8 +28,8 @@ class NewsletterTemplate extends Model {
static function createOrUpdate($data = array()) {
$template = false;
if(isset($data['id']) && (int) $data['id'] > 0) {
$template = self::findOne((int) $data['id']);
if(isset($data['id']) && (int)$data['id'] > 0) {
$template = self::findOne((int)$data['id']);
}
if($template === false) {

View File

@ -126,18 +126,19 @@ class Segment extends Model {
static function getSegmentsWithSubscriberCount() {
return self::selectMany(array(self::$_table.'.id', self::$_table.'.name'))
->select_expr(
'COUNT('.MP_SUBSCRIBER_SEGMENT_TABLE.'.subscriber_id)', 'subscribers'
->selectExpr(
self::$_table.'.*, COUNT(IF('.MP_SUBSCRIBER_SEGMENT_TABLE.'.status="subscribed",1,NULL)) `subscribers`'
)
->left_outer_join(
->leftOuterJoin(
MP_SUBSCRIBER_SEGMENT_TABLE,
array(self::$_table.'.id', '=', MP_SUBSCRIBER_SEGMENT_TABLE.'.segment_id'))
->left_outer_join(
->leftOuterJoin(
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')
->groupBy(self::$_table.'.id')
->groupBy(self::$_table.'.name')
->orderByAsc(self::$_table.'.name')
->where(self::$_table.'.type', 'default')
->whereNull(self::$_table.'.deleted_at')
->findArray();

View File

@ -1,7 +1,7 @@
<?php
namespace MailPoet\Models;
if (!defined('ABSPATH')) exit;
if(!defined('ABSPATH')) exit;
class Setting extends Model {
public static $_table = MP_SETTINGS_TABLE;

View File

@ -124,6 +124,10 @@ class Subscriber extends Model {
return false;
}
static function verifyToken($email, $token) {
return (self::generateToken($email) === $token);
}
static function subscribe($subscriber_data = array(), $segment_ids = array()) {
if(empty($subscriber_data) or empty($segment_ids)) {
return false;
@ -163,7 +167,10 @@ class Subscriber extends Model {
}
// welcome email
Scheduler::welcomeForSegmentSubscription($subscriber->id, $segment_ids);
Scheduler::scheduleSubscriberWelcomeNotification(
$subscriber->id,
$segment_ids
);
}
}
@ -188,11 +195,15 @@ class Subscriber extends Model {
'label' => __('All segments'),
'value' => ''
);
$subscribers_without_segment = self::filter('withoutSegments')->count();
$subscribers_without_segment_label = sprintf(
__('Subscribers without a segment (%s)'),
number_format($subscribers_without_segment)
);
$segment_list[] = array(
'label' => sprintf(
__('Subscribers without a segment (%d)'),
self::filter('withoutSegments')->count()
),
'label' => str_replace(' (0)', '', $subscribers_without_segment_label),
'value' => 'none'
);
@ -201,8 +212,9 @@ class Subscriber extends Model {
->filter('groupBy', $group)
->count();
$label = sprintf('%s (%s)', $segment->name, number_format($subscribers_count));
$segment_list[] = array(
'label' => sprintf('%s (%d)', $segment->name, $subscribers_count),
'label' => str_replace(' (0)', '', $label),
'value' => $segment->id()
);
}
@ -300,9 +312,10 @@ class Subscriber extends Model {
$customFields = CustomField::findArray();
foreach ($customFields as $customField) {
$orm = $orm->selectExpr(
'CASE WHEN ' .
'MAX(CASE WHEN ' .
MP_CUSTOM_FIELDS_TABLE . '.id=' . $customField['id'] . ' THEN ' .
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE . '.value END as "' . $customField['id'].'"');
MP_SUBSCRIBER_CUSTOM_FIELD_TABLE . '.value END) as "' . $customField['id'].'"'
);
}
$orm = $orm
->leftOuterJoin(
@ -526,12 +539,6 @@ class Subscriber extends Model {
return false;
}
static function bulkConfirmUnconfirmed($orm) {
$subscribers = $orm->findResultSet();
$subscribers->set('status', self::STATUS_SUBSCRIBED)->save();
return $subscribers->count();
}
static function bulkSendConfirmationEmail($orm) {
$subscribers = $orm
->where('status', self::STATUS_UNCONFIRMED)

View File

@ -2,13 +2,41 @@
namespace MailPoet\Newsletter;
use MailPoet\Newsletter\Editor\Transformer;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
class AutomatedLatestContent {
function getPosts($args) {
const DEFAULT_POSTS_PER_PAGE = 10;
function __construct($newsletter_id = false) {
$this->newsletter_id = $newsletter_id;
$this->_attachSentPostsFilter();
}
function __destruct() {
$this->_detachSentPostsFilter();
}
function filterOutSentPosts($where) {
$sentPostsQuery = 'SELECT ' . MP_NEWSLETTER_POSTS_TABLE . '.post_id FROM '
. MP_NEWSLETTER_POSTS_TABLE . ' WHERE '
. MP_NEWSLETTER_POSTS_TABLE . ".newsletter_id='" . $this->newsletter_id . "'";
$wherePostUnsent = 'ID NOT IN (' . $sentPostsQuery . ')';
if(!empty($where)) $wherePostUnsent = ' AND ' . $wherePostUnsent;
return $where . $wherePostUnsent;
}
function getPosts($args, $posts_to_exclude = array()) {
$posts_per_page = (!empty($args['amount']) && (int)$args['amount'] > 0)
? (int)$args['amount']
: self::DEFAULT_POSTS_PER_PAGE;
$parameters = array(
'posts_per_page' => (isset($args['amount'])) ? (int) $args['amount'] : 10,
'posts_per_page' => $posts_per_page,
'post_type' => (isset($args['contentType'])) ? $args['contentType'] : 'post',
'post_status' => (isset($args['postStatus'])) ? $args['postStatus'] : 'publish',
'orderby' => 'date',
@ -20,7 +48,17 @@ class AutomatedLatestContent {
if(isset($args['posts']) && is_array($args['posts'])) {
$parameters['post__in'] = $args['posts'];
}
if(!empty($posts_to_exclude)) {
$parameters['post__not_in'] = $posts_to_exclude;
}
$parameters['tax_query'] = $this->constructTaxonomiesQuery($args);
// This enables using posts query filters for get_posts, where by default
// it is disabled.
// However, it also enables other plugins and themes to hook in and alter
// the query.
$parameters['suppress_filters'] = false;
return get_posts($parameters);
}
@ -66,4 +104,16 @@ class AutomatedLatestContent {
}
return $taxonomies_query;
}
}
private function _attachSentPostsFilter() {
if($this->newsletter_id > 0) {
add_action('posts_where', array($this, 'filterOutSentPosts'));
}
}
private function _detachSentPostsFilter() {
if($this->newsletter_id > 0) {
remove_action('posts_where', array($this, 'filterOutSentPosts'));
}
}
}

View File

@ -11,14 +11,14 @@ class MetaInformationManager {
$position_field = $position . 'Text';
$text = array();
if ($args['showAuthor'] === $position_field) {
if($args['showAuthor'] === $position_field) {
$text[] = self::getPostAuthor(
$post->post_author,
$args['authorPrecededBy']
);
}
if ($args['showCategories'] === $position_field) {
if($args['showCategories'] === $position_field) {
$text[] = self::getPostCategories(
$post->ID,
$post->post_type,
@ -26,10 +26,10 @@ class MetaInformationManager {
);
}
if (!empty($text)) {
if(!empty($text)) {
$text = '<p>' . implode('<br />', $text) . '</p>';
if ($position === 'above') $content = $text . $content;
else if ($position === 'below') $content .= $text;
if($position === 'above') $content = $text . $content;
else if($position === 'below') $content .= $text;
}
}

View File

@ -8,9 +8,9 @@ class PostContentManager {
const MAX_EXCERPT_LENGTH = 60;
function getContent($post, $displayType) {
if ($displayType === 'titleOnly') {
if($displayType === 'titleOnly') {
return '';
} elseif ($displayType === 'excerpt') {
} elseif($displayType === 'excerpt') {
// get excerpt
if(!empty($post->post_excerpt)) {
return $post->post_excerpt;
@ -50,7 +50,7 @@ class PostContentManager {
private function generateExcerpt($content) {
// if excerpt is empty then try to find the "more" tag
$excerpts = explode('<!--more-->', $content);
if (count($excerpts) > 1) {
if(count($excerpts) > 1) {
// <!--more--> separator was present
return $excerpts[0];
} else {

View File

@ -17,7 +17,7 @@ class PostListTransformer {
$use_divider = $this->args['showDivider'] === 'true';
foreach ($posts as $index => $post) {
if ($use_divider && $index > 0) {
if($use_divider && $index > 0) {
$results[] = $this->args['divider'];
}

View File

@ -155,13 +155,13 @@ class PostTransformer {
private function getPostTitle($post) {
$title = $post->post_title;
if ($this->args['titleIsLink'] === 'true') {
if($this->args['titleIsLink'] === 'true') {
$title = '<a href="' . get_permalink($post->ID) . '">' . $title . '</a>';
}
if (in_array($this->args['titleFormat'], array('h1', 'h2', 'h3'))) {
if(in_array($this->args['titleFormat'], array('h1', 'h2', 'h3'))) {
$tag = $this->args['titleFormat'];
} elseif ($this->args['titleFormat'] === 'ul') {
} elseif($this->args['titleFormat'] === 'ul') {
$tag = 'li';
} else {
$tag = 'h1';

View File

@ -25,7 +25,7 @@ class StructureTransformer {
$top_ancestor = $this->findTopAncestor($item);
$offset = $top_ancestor->index();
if ($item->hasParent('a')) {
if($item->hasParent('a')) {
$item = $item->parent;
}
@ -46,8 +46,8 @@ class StructureTransformer {
*/
private function transformTagsToBlocks($root, $image_full_width) {
return array_map(function($item) use ($image_full_width) {
if ($item->tag === 'img' || $item->tag === 'a' && $item->query('img')) {
if ($item->tag === 'a') {
if($item->tag === 'img' || $item->tag === 'a' && $item->query('img')) {
if($item->tag === 'a') {
$link = $item->getAttribute('href');
$image = $item->children[0];
} else {
@ -87,11 +87,11 @@ class StructureTransformer {
$updated_structure = array();
$text_accumulator = '';
foreach ($structure as $item) {
if ($item['type'] === 'text') {
if($item['type'] === 'text') {
$text_accumulator .= $item['text'];
}
if ($item['type'] !== 'text') {
if (!empty($text_accumulator)) {
if($item['type'] !== 'text') {
if(!empty($text_accumulator)) {
$updated_structure[] = array(
'type' => 'text',
'text' => trim($text_accumulator),
@ -102,7 +102,7 @@ class StructureTransformer {
}
}
if (!empty($text_accumulator)) {
if(!empty($text_accumulator)) {
$updated_structure[] = array(
'type' => 'text',
'text' => trim($text_accumulator),

View File

@ -25,7 +25,7 @@ class TitleListTransformer {
$alignment = $this->args['titleAlignment'];
$alignment = (in_array($alignment, array('left', 'right', 'center'))) ? $alignment : 'left';
if ($this->args['titleIsLink']) {
if($this->args['titleIsLink']) {
$title = '<a href="' . get_permalink($post->ID) . '">' . $title . '</a>';
}

View File

@ -11,7 +11,7 @@ class Transformer {
function __construct($args) {
$title_list_only = $args['displayType'] === 'titleOnly' && $args['titleFormat'] === 'ul';
if ($title_list_only) $transformer = new TitleListTransformer($args);
if($title_list_only) $transformer = new TitleListTransformer($args);
else $transformer = new PostListTransformer($args);
$this->transformer = $transformer;
}

View File

@ -1,13 +1,16 @@
<?php
namespace MailPoet\Newsletter\Links;
use MailPoet\Models\NewsletterLink;
use MailPoet\Newsletter\Shortcodes\Shortcodes;
use MailPoet\Util\Security;
class Links {
static function extract($text) {
const DATA_TAG = '[mailpoet_data]';
static function extract($content) {
// adopted from WP's wp_extract_urls() function & modified to work on hrefs
# match href=' or href="
# match href=' or href="
$regex = '#(?:href.*?=.*?)(["\']?)('
# match http://
. '(?:([\w-]+:)?//?)'
@ -18,22 +21,24 @@ class Links {
. '(?:'
. '\([\w\d]+\)|'
. '(?:'
. '[^`!()\[\]{};:\'".,<>«»“”‘’\s]|'
. '[^`!()\[\]{}:;\'".,<>«»“”‘’\s]|'
. '(?:[:]\d+)?/?'
. ')+'
. ')'
. ')\\1#';
preg_match_all($regex, $text, $links);
$shortcodes = new Shortcodes();;
$shortcodes = $shortcodes->extract($text, $limit = array('subscription'));
// extract shortcodes with [link:*] format
$shortcodes = new Shortcodes();
$shortcodes = $shortcodes->extract($content, $categories = array('link'));
// extract links
preg_match_all($regex, $content, $links);
return array_merge(
array_unique($links[2]),
$shortcodes
);
}
static function replace($text, $links = false) {
$links = ($links) ? $links : self::extract($text);
static function process($content) {
$links = self::extract($content);
$processed_links = array();
foreach($links as $link) {
$hash = Security::generateRandomString(5);
@ -42,13 +47,53 @@ class Links {
'url' => $link
);
$encoded_link = sprintf(
'%s/?mailpoet&endpoint=track&action=click&data=%s',
'%s/?mailpoet&endpoint=track&action=click&data=%s-%s',
home_url(),
'[mailpoet_data]-'.$hash
self::DATA_TAG,
$hash
);
$link_regex = '/' . preg_quote($link, '/') . '/';
$text = preg_replace($link_regex, $encoded_link, $text);
$content = preg_replace($link_regex, $encoded_link, $content);
}
return array(
$content,
$processed_links
);
}
static function replaceSubscriberData(
$newsletter_id,
$subscriber_id,
$queue_id,
$content
) {
$regex = sprintf('/data=(%s(?:-\w+)?)/', preg_quote(self::DATA_TAG));
preg_match_all($regex, $content, $links);
foreach($links[1] as $link) {
$hash = null;
if(preg_match('/-/', $link)) {
list(, $hash) = explode('-', $link);
}
$data = array(
'newsletter' => $newsletter_id,
'subscriber' => $subscriber_id,
'queue' => $queue_id,
'hash' => $hash
);
$data = rtrim(base64_encode(serialize($data)), '=');
$content = str_replace($link, $data, $content);
}
return $content;
}
static function save($links, $newsletter_id, $queue_id) {
foreach($links as $link) {
$newsletter_link = NewsletterLink::create();
$newsletter_link->newsletter_id = $newsletter_id;
$newsletter_link->queue_id = $queue_id;
$newsletter_link->hash = $link['hash'];
$newsletter_link->url = $link['url'];
$newsletter_link->save();
}
return array($text, $processed_links);
}
}

View File

@ -1,12 +0,0 @@
<?php
namespace MailPoet\Newsletter\Renderer\Blocks;
class AutomatedLatestContent {
static function render($element, $column_count) {
$ALC = new \MailPoet\Newsletter\AutomatedLatestContent();
$posts = $ALC->getPosts($element);
$transformed_posts = array('blocks' => $ALC->transformPosts($element, $posts));
$renderer = new Renderer();
return $renderer->render($transformed_posts, $column_count);
}
}

View File

@ -43,8 +43,8 @@ class Button {
static function calculateWidth($element, $column_count) {
$column_width = ColumnsHelper::columnWidth($column_count);
$column_width = $column_width - (StylesHelper::$padding_width * 2);
$border_width = (int) $element['styles']['block']['borderWidth'];
$button_width = (int) $element['styles']['block']['width'];
$border_width = (int)$element['styles']['block']['borderWidth'];
$button_width = (int)$element['styles']['block']['width'];
$button_width = ($button_width > $column_width) ?
$column_width :
$button_width;

View File

@ -8,7 +8,7 @@ class Footer {
$element['text'] = preg_replace('/\n/', '<br />', $element['text']);
$element['text'] = preg_replace('/(<\/?p.*?>)/i', '', $element['text']);
$line_height = sprintf(
'%spx', StylesHelper::$line_height_multiplier * (int) $element['styles']['text']['fontSize']
'%spx', StylesHelper::$line_height_multiplier * (int)$element['styles']['text']['fontSize']
);
$DOM_parser = new \pQuery();
$DOM = $DOM_parser->parseStr($element['text']);

View File

@ -8,7 +8,7 @@ class Header {
$element['text'] = preg_replace('/\n/', '<br />', $element['text']);
$element['text'] = preg_replace('/(<\/?p.*?>)/i', '', $element['text']);
$line_height = sprintf(
'%spx', StylesHelper::$line_height_multiplier * (int) $element['styles']['text']['fontSize']
'%spx', StylesHelper::$line_height_multiplier * (int)$element['styles']['text']['fontSize']
);
$DOM_parser = new \pQuery();
$DOM = $DOM_parser->parseStr($element['text']);

View File

@ -6,8 +6,8 @@ use MailPoet\Newsletter\Renderer\StylesHelper;
class Image {
static function render($element, $column_count) {
$element['width'] = (int) $element['width'];
$element['height'] = (int) $element['height'];
$element['width'] = (int)$element['width'];
$element['height'] = (int)$element['height'];
$element = self::adjustImageDimensions($element, $column_count);
$image_template = '
<img style="max-width:' . $element['width'] . 'px;" src="' . $element['src'] . '"
@ -32,7 +32,7 @@ class Image {
if($element['width'] > $column_width) {
$ratio = $element['width'] / $column_width;
$element['width'] = $column_width;
$element['height'] = (int) ceil($element['height'] / $ratio);
$element['height'] = (int)ceil($element['height'] / $ratio);
}
// resize image if the image is padded and wider than padded column width
if($element['fullWidth'] === false &&
@ -40,7 +40,7 @@ class Image {
) {
$ratio = $element['width'] / ($column_width - $padded_width);
$element['width'] = $column_width - $padded_width;
$element['height'] = (int) ceil($element['height'] / $ratio);
$element['height'] = (int)ceil($element['height'] / $ratio);
}
return $element;
}

View File

@ -4,6 +4,15 @@ namespace MailPoet\Newsletter\Renderer\Blocks;
use MailPoet\Newsletter\Renderer\StylesHelper;
class Renderer {
public $newsletter;
public $posts;
function __construct(array $newsletter, $posts = false) {
$this->newsletter = $newsletter;
$this->posts = array();
$this->ALC = new \MailPoet\Newsletter\AutomatedLatestContent($this->newsletter['id']);
}
function render($data, $column_count) {
$block_content = '';
array_map(function($block) use (&$block_content, &$columns, $column_count) {
@ -20,8 +29,37 @@ class Renderer {
}
function createElementFromBlockType($block, $column_count) {
if($block['type'] === 'automatedLatestContent') {
$content = $this->processAutomatedLatestContent($block, $column_count);
return $content;
}
$block = StylesHelper::applyTextAlignment($block);
$block_class = __NAMESPACE__ . '\\' . ucfirst($block['type']);
return (class_exists($block_class)) ? $block_class::render($block, $column_count) : '';
if(!class_exists($block_class)) {
return '';
}
return $block_class::render($block, $column_count);
}
}
function processAutomatedLatestContent($args, $column_count) {
$posts_to_exclude = $this->getPosts();
$ALCPosts = $this->ALC->getPosts($args, $posts_to_exclude);
foreach($ALCPosts as $post) {
$posts_to_exclude[] = $post->ID;
}
$transformed_posts = array(
'blocks' => $this->ALC->transformPosts($args, $ALCPosts)
);
$this->setPosts($posts_to_exclude);
$rendered_posts = $this->render($transformed_posts, $column_count);
return $rendered_posts;
}
function getPosts() {
return $this->posts;
}
function setPosts($posts) {
return $this->posts = $posts;
}
}

View File

@ -8,7 +8,7 @@ class Social {
foreach($element['icons'] as $index => $icon) {
$icons_block .= '
<a href="' . $icon['link'] . '" style="text-decoration:none!important;">
<img src="' . $icon['image'] . '" width="' . (int) $icon['width'] . '" height="' . (int) $icon['height'] . '" style="width:' . $icon['width'] . ';height:' . $icon['width'] . ';-ms-interpolation-mode:bicubic;border:0;display:inline;outline:none;" alt="' . $icon['iconType'] . '">
<img src="' . $icon['image'] . '" width="' . (int)$icon['width'] . '" height="' . (int)$icon['height'] . '" style="width:' . $icon['width'] . ';height:' . $icon['width'] . ';-ms-interpolation-mode:bicubic;border:0;display:inline;outline:none;" alt="' . $icon['iconType'] . '">
</a>';
}
$template = '

View File

@ -3,7 +3,7 @@ namespace MailPoet\Newsletter\Renderer\Blocks;
class Spacer {
static function render($element) {
$height = (int) $element['styles']['block']['height'];
$height = (int)$element['styles']['block']['height'];
$background_color = $element['styles']['block']['backgroundColor'];
$template = '
<tr>

View File

@ -57,14 +57,7 @@ class Text {
</tr>
</tbody>'
);
$blockquote->parent->insertChild(
array(
'tag_name' => 'br',
'self_close' => true,
'attributes' => array()
),
$blockquote->index() + 1
);
$blockquote = self::insertLineBreak($blockquote);
}
return $DOM->__toString();
}
@ -75,8 +68,25 @@ class Text {
$paragraphs = $DOM->query('p');
if(!$paragraphs->count()) return $html;
foreach($paragraphs as $paragraph) {
// remove empty paragraphs
// process empty paragraphs
if(!trim($paragraph->html())) {
$next_element = ($paragraph->getNextSibling()) ?
trim($paragraph->getNextSibling()->text()) :
false;
$previous_element = ($paragraph->getPreviousSibling()) ?
trim($paragraph->getPreviousSibling()->text()) :
false;
$previous_element_tag = ($previous_element) ?
$paragraph->getPreviousSibling()->tag :
false;
// if previous or next paragraphs are empty OR previous paragraph
// is a heading, insert a break line
if(!$next_element ||
!$previous_element ||
(preg_match('/h\d+/', $previous_element_tag))
) {
$paragraph = self::insertLineBreak($paragraph);
}
$paragraph->remove();
continue;
}
@ -91,9 +101,13 @@ class Text {
$paragraph->cellpadding = 0;
$next_element = $paragraph->getNextSibling();
// unless this is the last element in column, add double line breaks
$line_breaks = ($next_element && !$next_element->getInnerText()) ? '<br /><br />' : '';
$line_breaks = ($next_element && !trim($next_element->text())) ?
'<br /><br />' :
'';
// if this element is followed by a list, add single line break
$line_breaks = ($next_element && preg_match('/<li>/i', $next_element->getInnerText())) ? '<br />' : $line_breaks;
$line_breaks = ($next_element && preg_match('/<li>/i', $next_element->text())) ?
'<br />' :
$line_breaks;
$paragraph->html('
<tr>
<td class="mailpoet_paragraph" style="word-break:break-word;word-wrap:break-word;' . $style . '">
@ -139,4 +153,16 @@ class Text {
static function removeLastLineBreak($html) {
return preg_replace('/(^)?(<br.*?\/?>)+$/i', '', $html);
}
static function insertLineBreak($element) {
$element->parent->insertChild(
array(
'tag_name' => 'br',
'self_close' => true,
'attributes' => array()
),
$element->index() + 1
);
return $element;
}
}

View File

@ -1,6 +1,8 @@
<?php
namespace MailPoet\Newsletter\Renderer\PostProcess;
use MailPoet\Newsletter\Links\Links;
class OpenTracking {
static function process($template) {
$DOM = new \pQuery();
@ -9,7 +11,7 @@ class OpenTracking {
$open_tracking_link = sprintf(
'<img alt="" class="" src="%s/%s"/>',
home_url(),
htmlentities('?mailpoet&endpoint=track&action=open&data=[mailpoet_data]')
esc_attr('?mailpoet&endpoint=track&action=open&data=' . Links::DATA_TAG)
);
$template->html($template->html() . $open_tracking_link);
return $DOM->__toString();

View File

@ -4,35 +4,31 @@ namespace MailPoet\Newsletter\Renderer;
if(!defined('ABSPATH')) exit;
class Renderer {
public $template = 'Template.html';
public $blocks_renderer;
public $columns_renderer;
public $DOM_parser;
public $CSS_inliner;
public $newsletter;
const NEWSLETTER_TEMPLATE = 'Template.html';
function __construct($newsletter) {
$this->blocks_renderer = new Blocks\Renderer();
function __construct(array $newsletter) {
$this->newsletter = $newsletter;
$this->blocks_renderer = new Blocks\Renderer($this->newsletter);
$this->columns_renderer = new Columns\Renderer();
$this->DOM_parser = new \pQuery();
$this->CSS_inliner = new \MailPoet\Util\CSS();
$this->newsletter = $newsletter;
$this->template = file_get_contents(dirname(__FILE__) . '/' . $this->template);
$this->template = file_get_contents(dirname(__FILE__) . '/' . self::NEWSLETTER_TEMPLATE);
}
function render() {
$newsletter_data = (is_array($this->newsletter['body'])) ?
$this->newsletter['body'] :
json_decode($this->newsletter['body'], true);
$newsletter_body = $this->renderBody($newsletter_data['content']);
$newsletter_styles = $this->renderStyles($newsletter_data['globalStyles']);
$newsletter_subject = $this->newsletter['subject'];
$newsletter_preheader = $this->newsletter['preheader'];
$newsletter = $this->newsletter;
$rendered_body = $this->renderBody($newsletter['body']['content']);
$rendered_styles = $this->renderStyles($newsletter['body']['globalStyles']);
$template = $this->injectContentIntoTemplate($this->template, array(
$newsletter_subject,
$newsletter_styles,
$newsletter_preheader,
$newsletter_body
$newsletter['subject'],
$rendered_styles,
$newsletter['preheader'],
$rendered_body
));
$template = $this->inlineCSSStyles($template);
$template = $this->postProcessTemplate($template);
@ -43,7 +39,7 @@ class Renderer {
}
function renderBody($content) {
$content = array_map(function($content_block) {
$rendered_content = array_map(function($content_block) {
$column_count = count($content_block['blocks']);
$column_data = $this->blocks_renderer->render(
$content_block,
@ -55,7 +51,7 @@ class Renderer {
$column_data
);
}, $content['blocks']);
return implode('', $content);
return implode('', $rendered_content);
}
function renderStyles($styles) {
@ -80,9 +76,9 @@ class Renderer {
return $css;
}
function injectContentIntoTemplate($template, $data) {
return preg_replace_callback('/{{\w+}}/', function($matches) use (&$data) {
return array_shift($data);
function injectContentIntoTemplate($template, $content) {
return preg_replace_callback('/{{\w+}}/', function($matches) use (&$content) {
return array_shift($content);
}, $template);
}

View File

@ -89,15 +89,15 @@ class StylesHelper {
}
static function applyHeadingMargin($style, $selector) {
if (!preg_match('/h[1-4]/i', $selector)) return $style;
$font_size = (int) $style['fontSize'];
if(!preg_match('/h[1-4]/i', $selector)) return $style;
$font_size = (int)$style['fontSize'];
$style['margin'] = sprintf('0 0 %spx 0', self::$heading_margin_multiplier * $font_size);
return $style;
}
static function applyLineHeight($style, $selector) {
if (!preg_match('/mailpoet_paragraph|h[1-4]/i', $selector)) return $style;
$font_size = (int) $style['fontSize'];
if(!preg_match('/mailpoet_paragraph|h[1-4]/i', $selector)) return $style;
$font_size = (int)$style['fontSize'];
$style['lineHeight'] = sprintf('%spx', self::$line_height_multiplier * $font_size);
return $style;
}

View File

@ -1,132 +1,192 @@
<?php
namespace MailPoet\Newsletter\Scheduler;
use Carbon\Carbon;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterOption;
use MailPoet\Models\NewsletterOptionField;
use MailPoet\Models\SendingQueue;
class Scheduler {
const SECONDS_IN_HOUR = 3600;
const LAST_WEEKDAY_FORMAT = 'L';
const WORDPRESS_ALL_ROLES = 'mailpoet_all';
static function postNotification($newsletter_id) {
$newsletter = Newsletter::filter('filterWithOptions')
->findOne($newsletter_id)
->asArray();
$interval_type = $newsletter['intervalType'];
$hour = (int) $newsletter['timeOfDay'] / self::SECONDS_IN_HOUR;
$week_day = $newsletter['weekDay'];
$month_day = $newsletter['monthDay'];
$nth_week_day = ($newsletter['nthWeekDay'] === self::LAST_WEEKDAY_FORMAT) ?
$newsletter['nthWeekDay'] :
'#' . $newsletter['nthWeekDay'];
switch($interval_type) {
case 'immediately':
$schedule = '* * * * *';
break;
case 'immediate':
case 'daily':
$schedule = sprintf('0 %s * * *', $hour);
break;
case 'weekly':
$schedule = sprintf('0 %s * * %s', $hour, $week_day);
break;
case 'monthly':
$schedule = sprintf('0 %s %s * *', $hour, $month_day);
break;
case 'nthWeekDay':
$schedule = sprintf('0 %s ? * %s%s', $hour, $week_day, $nth_week_day);
break;
}
$option_field = NewsletterOptionField::where('name', 'schedule')
->findOne()
->asArray();
$relation = NewsletterOption::where('newsletter_id', $newsletter_id)
->where('option_field_id', $option_field['id'])
->findOne();
if(!$relation) {
$relation = NewsletterOption::create();
$relation->newsletter_id = $newsletter['id'];
$relation->option_field_id = $option_field['id'];
}
$relation->value = $schedule;
$relation->save();
}
static function welcomeForSegmentSubscription($subscriber_id, array $segments) {
$newsletters = self::getWelcomeNewsletters();
if(!count($newsletters)) return;
foreach($newsletters as $newsletter) {
if($newsletter['event'] === 'segment' &&
in_array($newsletter['segment'], $segments)
) {
self::createSendingQueueEntry($newsletter, $subscriber_id);
}
}
}
static function welcomeForNewWPUser($subscriber_id, array $wp_user, $old_user_data) {
$newsletters = self::getWelcomeNewsletters();
if(!count($newsletters)) return;
foreach($newsletters as $newsletter) {
if($newsletter['event'] === 'user') {
if($old_user_data) {
// do not schedule welcome newsletter if roles have not changed
$old_role = (array) $old_user_data->roles;
$new_role = (array) $wp_user->roles;
if($newsletter['role'] === self::WORDPRESS_ALL_ROLES ||
!array_diff($old_role, $new_role)
) {
continue;
}
}
if($newsletter['role'] === self::WORDPRESS_ALL_ROLES ||
in_array($newsletter['role'], $wp_user['roles'])
) {
self::createSendingQueueEntry($newsletter, $subscriber_id);
}
}
}
}
private static function getWelcomeNewsletters() {
return Newsletter::where('type', 'welcome')
->whereNull('deleted_at')
->filter('filterWithOptions')
->findArray();
}
private static function createSendingQueueEntry($newsletter, $subscriber_id) {
$queue = SendingQueue::create();
$queue->newsletter_id = $newsletter['id'];
$queue->subscribers = serialize(
array(
'to_process' => array($subscriber_id)
)
);
$queue->count_total = $queue->count_to_process = 1;
$after_time_type = $newsletter['afterTimeType'];
$after_time_number = $newsletter['afterTimeNumber'];
$scheduled_at = null;
$current_time = Carbon::createFromTimestamp(current_time('timestamp'));
switch($after_time_type) {
case 'hours':
$scheduled_at = $current_time->addHours($after_time_number);
break;
case 'days':
$scheduled_at = $current_time->addDays($after_time_number);
break;
case 'weeks':
$scheduled_at = $current_time->addWeeks($after_time_number);
break;
default:
$scheduled_at = $current_time;
}
$queue->status = 'scheduled';
$queue->scheduled_at = $scheduled_at;
$queue->save();
}
}
<?php
namespace MailPoet\Newsletter\Scheduler;
use Carbon\Carbon;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterOption;
use MailPoet\Models\NewsletterOptionField;
use MailPoet\Models\NewsletterPost;
use MailPoet\Models\SendingQueue;
class Scheduler {
const SECONDS_IN_HOUR = 3600;
const LAST_WEEKDAY_FORMAT = 'L';
const WORDPRESS_ALL_ROLES = 'mailpoet_all';
const INTERVAL_IMMEDIATELY = 'immediately';
const INTERVAL_IMMEDIATE = 'immediate';
const INTERVAL_DAILY = 'daily';
const INTERVAL_WEEKLY = 'weekly';
const INTERVAL_MONTHLY = 'monthly';
const INTERVAL_NTHWEEKDAY = 'nthWeekDay';
const STATUS_SCHEDULED = 'scheduled';
static function processPostNotificationSchedule($newsletter_id) {
$newsletter = Newsletter::filter('filterWithOptions')
->findOne($newsletter_id);
if(!$newsletter) return;
$newsletter = $newsletter->asArray();
$interval_type = $newsletter['intervalType'];
$hour = (int)$newsletter['timeOfDay'] / self::SECONDS_IN_HOUR;
$week_day = $newsletter['weekDay'];
$month_day = $newsletter['monthDay'];
$nth_week_day = ($newsletter['nthWeekDay'] === self::LAST_WEEKDAY_FORMAT) ?
$newsletter['nthWeekDay'] :
'#' . $newsletter['nthWeekDay'];
switch($interval_type) {
case self::INTERVAL_IMMEDIATELY:
$schedule = '* * * * *';
break;
case self::INTERVAL_IMMEDIATE:
case self::INTERVAL_DAILY:
$schedule = sprintf('0 %s * * *', $hour);
break;
case self::INTERVAL_WEEKLY:
$schedule = sprintf('0 %s * * %s', $hour, $week_day);
break;
case self::INTERVAL_NTHWEEKDAY:
$schedule = sprintf('0 %s ? * %s%s', $hour, $week_day, $nth_week_day);
break;
case self::INTERVAL_MONTHLY:
$schedule = sprintf('0 %s %s * *', $hour, $month_day);
break;
}
$option_field = NewsletterOptionField::where('name', 'schedule')
->findOne()
->asArray();
$relation = NewsletterOption::where('newsletter_id', $newsletter_id)
->where('option_field_id', $option_field['id'])
->findOne();
if(!$relation) {
$relation = NewsletterOption::create();
$relation->newsletter_id = $newsletter['id'];
$relation->option_field_id = $option_field['id'];
}
$relation->value = $schedule;
$relation->save();
return Newsletter::filter('filterWithOptions')
->findOne($newsletter_id)->asArray();
}
static function schedulePostNotification($post_id) {
$newsletters = self::getNewsletters('notification');
if(!count($newsletters)) return;
foreach($newsletters as $newsletter) {
$post = NewsletterPost::where('newsletter_id', $newsletter['id'])
->where('post_id', $post_id)
->findOne();
if($post === false) {
$scheduled_notification = self::createPostNotificationQueue($newsletter);
}
}
}
/**
* Create a properly formatted timestamp for use in Scheduler from
* arbitrarily formatted timestamp strings.
*/
static function scheduleFromTimestamp($timestamp) {
return Carbon::parse($timestamp)->format('Y-m-d H:i:s');
}
static function scheduleSubscriberWelcomeNotification(
$subscriber_id,
array $segments
) {
$newsletters = self::getNewsletters('welcome');
if(!count($newsletters)) return;
foreach($newsletters as $newsletter) {
if($newsletter['event'] === 'segment' &&
in_array($newsletter['segment'], $segments)
) {
self::createWelcomeNotificationQueue($newsletter, $subscriber_id);
}
}
}
static function scheduleWPUserWelcomeNotification(
$subscriber_id,
array $wp_user,
$old_user_data
) {
$newsletters = self::getNewsletters('welcome');
if(!count($newsletters)) return;
foreach($newsletters as $newsletter) {
if($newsletter['event'] === 'user') {
if($old_user_data) {
// do not schedule welcome newsletter if roles have not changed
$old_role = (array) $old_user_data->roles;
$new_role = (array) $wp_user->roles;
if($newsletter['role'] === self::WORDPRESS_ALL_ROLES ||
!array_diff($old_role, $new_role)
) {
continue;
}
}
if($newsletter['role'] === self::WORDPRESS_ALL_ROLES ||
in_array($newsletter['role'], $wp_user['roles'])
) {
self::createWelcomeNotificationQueue($newsletter, $subscriber_id);
}
}
}
}
static function getNewsletters($type) {
return Newsletter::where('type', $type)
->whereNull('deleted_at')
->filter('filterWithOptions')
->findArray();
}
static function createWelcomeNotificationQueue($newsletter, $subscriber_id) {
$queue = SendingQueue::create();
$queue->newsletter_id = $newsletter['id'];
$queue->subscribers = serialize(
array(
'to_process' => array($subscriber_id)
)
);
$queue->count_total = $queue->count_to_process = 1;
$after_time_type = $newsletter['afterTimeType'];
$after_time_number = $newsletter['afterTimeNumber'];
$scheduled_at = null;
$current_time = Carbon::createFromTimestamp(current_time('timestamp'));
switch($after_time_type) {
case 'hours':
$scheduled_at = $current_time->addHours($after_time_number);
break;
case 'days':
$scheduled_at = $current_time->addDays($after_time_number);
break;
case 'weeks':
$scheduled_at = $current_time->addWeeks($after_time_number);
break;
default:
$scheduled_at = $current_time;
}
$queue->status = self::STATUS_SCHEDULED;
$queue->scheduled_at = $scheduled_at;
$queue->save();
}
static function createPostNotificationQueue($newsletter) {
$next_run_date = self::getNextRunDate($newsletter['schedule']);
// do not schedule duplicate queues for the same time
$existing_queue = SendingQueue::where('newsletter_id', $newsletter['id'])
->where('scheduled_at', $next_run_date)
->findOne();
if($existing_queue) return;
$queue = SendingQueue::create();
$queue->newsletter_id = $newsletter['id'];
$queue->status = 'scheduled';
$queue->scheduled_at = $next_run_date;
$queue->save();
return $queue;
}
static function getNextRunDate($schedule) {
$schedule = \Cron\CronExpression::factory($schedule);
return $schedule->getNextRunDate(current_time('mysql'))
->format('Y-m-d H:i:s');
}
}

View File

@ -0,0 +1,171 @@
<?php
namespace MailPoet\Newsletter\Shortcodes\Categories;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Statistics\Track\Unsubscribes;
use MailPoet\Subscription\Url as SubscriptionUrl;
class Link {
/*
{
text: '<%= __('Unsubscribe') %>',-
shortcode: 'subscription:unsubscribe',
},
{
text: '<%= __('Manage subscription') %>',
shortcode: 'subscription:manage',
},
{
text: '<%= __('View in browser link') %>',
shortcode: 'newsletter:view_in_browser',
}
*/
static function process($action,
$default_value = false,
$newsletter,
$subscriber,
$queue = false
) {
switch($action) {
case 'subscription_unsubscribe':
$action = 'subscription_unsubscribe_url';
$url = self::processUrl(
$action,
esc_attr(SubscriptionUrl::getUnsubscribeUrl($subscriber)),
$queue
);
return sprintf(
'<a target="_blank" href="%s">%s</a>',
$url,
__('Unsubscribe')
);
break;
case 'subscription_unsubscribe_url':
return self::processUrl(
$action,
SubscriptionUrl::getUnsubscribeUrl($subscriber),
$queue
);
break;
case 'subscription_manage':
$url = self::processUrl(
$action = 'subscription_manage_url',
esc_attr(SubscriptionUrl::getManageUrl($subscriber)),
$queue
);
return sprintf(
'<a target="_blank" href="%s">%s</a>',
$url,
__('Manage subscription')
);
break;
case 'subscription_manage_url':
return self::processUrl(
$action,
SubscriptionUrl::getManageUrl($subscriber),
$queue
);
break;
case 'newsletter_view_in_browser':
$action = 'view_in_browser_url';
$url = esc_attr(self::getViewInBrowserUrl($newsletter, $subscriber, $queue));
$url = self::processUrl($action, $url, $queue);
return sprintf(
'<a target="_blank" href="%s">%s</a>',
$url,
__('View in your browser')
);
break;
case 'newsletter_view_in_browser_url':
$url = self::getViewInBrowserUrl($newsletter, $subscriber, $queue);
return self::processUrl($action, $url, $queue);
break;
default:
$shortcode = self::getShortcode($action);
$url = apply_filters(
'mailpoet_newsletter_shortcode_link',
$shortcode,
$newsletter,
$subscriber,
$queue
);
return ($url !== $shortcode) ?
self::processUrl($action, $url, $queue) :
false;
break;
}
}
static function getViewInBrowserUrl(
$newsletter,
$subscriber = false,
$queue = false
) {
$data = array(
'newsletter' => (isset($newsletter['id'])) ?
$newsletter['id'] :
$newsletter,
'subscriber' => (isset($subscriber['id'])) ?
$subscriber['id'] :
$subscriber,
'subscriber_token' => (isset($subscriber['id'])) ?
Subscriber::generateToken($subscriber['email']) :
false,
'queue' => (isset($queue['id'])) ?
$queue['id'] :
$queue
);
$data = rtrim(base64_encode(serialize($data)), '=');
return home_url() . '/?mailpoet&endpoint=view_in_browser&data=' . $data;
}
static function processUrl($action, $url, $queue) {
return ($queue !== false && (boolean) Setting::getValue('tracking.enabled')) ?
self::getShortcode($action) :
$url;
}
static function processShortcodeAction(
$shortcode_action, $newsletter, $subscriber, $queue
) {
switch($shortcode_action) {
case 'subscription_unsubscribe_url':
// track unsubscribe event
if((boolean) Setting::getValue('tracking.enabled')) {
$unsubscribe = new Unsubscribes();
$unsubscribe->track($subscriber['id'], $queue['id'], $newsletter['id']);
}
$url = SubscriptionUrl::getUnsubscribeUrl($subscriber);
break;
case 'subscription_manage_url':
$url = SubscriptionUrl::getManageUrl($subscriber);
break;
case 'newsletter_view_in_browser_url':
$url = Link::getViewInBrowserUrl($newsletter, $subscriber, $queue);
break;
default:
$shortcode = self::getShortcode($shortcode_action);
$url = apply_filters(
'mailpoet_newsletter_shortcode_link',
$shortcode,
$newsletter,
$subscriber,
$queue
);
$url = ($url !== $shortcode_action) ? $url : false;
break;
}
return $url;
}
private static function getShortcode($action) {
return sprintf('[link:%s]', $action);
}
}

View File

@ -2,6 +2,7 @@
namespace MailPoet\Newsletter\Shortcodes\Categories;
use MailPoet\Models\SendingQueue;
use MailPoet\Newsletter\Shortcodes\ShortcodesHelper;
require_once( ABSPATH . "wp-includes/pluggable.php" );
@ -26,36 +27,33 @@ class Newsletter {
{
text: '<%= __('Issue number') %>',
shortcode: 'newsletter:number',
},
{
text: '<%= __('View in browser link') %>',
shortcode: 'newsletter:view_in_browser',
}
*/
static function process($action,
$default_value = false,
$newsletter, $subscriber = false, $text) {
if(is_object($newsletter)) {
$newsletter = $newsletter->asArray();
}
$newsletter,
$subscriber,
$queue = false,
$content
) {
switch($action) {
case 'subject':
return ($newsletter) ? $newsletter['subject'] : false;
break;
case 'total':
return substr_count($text, 'data-post-id');
return substr_count($content, 'data-post-id');
break;
case 'post_title':
preg_match_all('/data-post-id="(\w+)"/ism', $text, $posts);
preg_match_all('/data-post-id="(\d+)"/ism', $content, $posts);
$post_ids = array_unique($posts[1]);
$latest_post = self::getLatestWPPost($post_ids);
return ($latest_post) ? $latest_post['post_title'] : false;
break;
case 'number':
if ($newsletter['type'] !== 'notification') return false;
if($newsletter['type'] !== 'notification') return false;
$sent_newsletters =
SendingQueue::where('newsletter_id', $newsletter['id'])
->where('status', 'completed')
@ -63,14 +61,6 @@ class Newsletter {
return ++$sent_newsletters;
break;
case 'view_in_browser':
return '<a href="#TODO">'.__('View in your browser').'</a>';
break;
case 'view_in_browser_url':
return '#TODO';
break;
default:
return false;
break;

View File

@ -1,69 +0,0 @@
<?php
namespace MailPoet\Newsletter\Shortcodes\Categories;
use MailPoet\Models\Setting;
use MailPoet\Subscription\Url as SubscriptionUrl;
class Subscription {
/*
{
text: '<%= __('Unsubscribe') %>',-
shortcode: 'subscription:unsubscribe',
},
{
text: '<%= __('Manage subscriptions') %>',
shortcode: 'subscription:manage',
},
*/
static function process(
$action,
$default_value = false,
$newsletter = false,
$subscriber = false,
$text = false,
$shortcode
) {
switch($action) {
case 'unsubscribe':
return '<a target="_blank" href="'.
self::getShortcodeUrl(
$shortcode,
esc_attr(SubscriptionUrl::getUnsubscribeUrl($subscriber))
)
.'">'.__('Unsubscribe').'</a>';
break;
case 'unsubscribe_url':
return self::getShortcodeUrl(
$shortcode,
SubscriptionUrl::getUnsubscribeUrl($subscriber)
);
break;
case 'manage':
return '<a target="_blank" href="'.
self::getShortcodeUrl(
$shortcode,
esc_attr(SubscriptionUrl::getManageUrl($subscriber))
)
.'">'.__('Manage subscription').'</a>';
break;
case 'manage_url':
return self::getShortcodeUrl(
$shortcode,
SubscriptionUrl::getManageUrl($subscriber)
);
break;
default:
return false;
break;
}
}
static function getShortcodeUrl($shortcode, $url) {
return ((boolean) Setting::getValue('tracking.enabled')) ?
$shortcode :
$url;
}
}

View File

@ -28,10 +28,12 @@ class User {
shortcode: 'user:count',
}
*/
static function process($action, $default_value, $newsletter = false, $subscriber) {
if(is_object($subscriber)) {
$subscriber = $subscriber->asArray();
}
static function process(
$action,
$default_value,
$newsletter = false,
$subscriber
) {
switch($action) {
case 'firstname':
return ($subscriber) ? $subscriber['first_name'] : $default_value;

View File

@ -4,61 +4,86 @@ namespace MailPoet\Newsletter\Shortcodes;
class Shortcodes {
public $newsletter;
public $subscriber;
public $queue;
function __construct(
$newsletter = false,
$subscriber = false
$subscriber = false,
$queue = false
) {
$this->newsletter = $newsletter;
$this->subscriber = $subscriber;
$this->newsletter = (is_object($newsletter)) ?
$newsletter->asArray() :
$newsletter;
$this->subscriber = (is_object($subscriber)) ?
$subscriber->asArray() :
$subscriber;
$this->queue = (is_object($queue)) ?
$queue->asArray() :
$queue;
}
function extract($text, $limit = false) {
$limit = (is_array($limit)) ? implode('|', $limit) : false;
function extract($content, $categories= false) {
$categories = (is_array($categories)) ? implode('|', $categories) : false;
$regex = sprintf(
'/\[%s:.*?\]/ism',
($limit) ? '(?:' . $limit . ')' : '(?:\w+)'
($categories) ? '(?:' . $categories . ')' : '(?:\w+)'
);
preg_match_all($regex, $text, $shortcodes);
preg_match_all($regex, $content, $shortcodes);
return array_unique($shortcodes[0]);
}
function match($shortcode) {
preg_match(
'/\[(?P<type>\w+):(?P<action>\w+)(?:.*?default:(?P<default>.*?))?\]/',
'/\[(?P<category>\w+)?:(?P<action>\w+)(?:.*?\|.*?default:(?P<default>.*?))?\]/',
$shortcode,
$match
);
return $match;
}
function process($shortcodes, $text) {
function process($shortcodes, $content = false) {
$processed_shortcodes = array_map(
function($shortcode) use($text) {
function($shortcode) use($content) {
$shortcode_details = $this->match($shortcode);
$shortcode_type = ucfirst($shortcode_details['type']);
$shortcode_action = $shortcode_details['action'];
$shortcode_category = isset($shortcode_details['category']) ?
ucfirst($shortcode_details['category']) :
false;
$shortcode_action = isset($shortcode_details['action']) ?
$shortcode_details['action'] :
false;
$shortcode_class =
__NAMESPACE__ . '\\Categories\\' . $shortcode_type;
__NAMESPACE__ . '\\Categories\\' . $shortcode_category;
$shortcode_default_value = isset($shortcode_details['default'])
? $shortcode_details['default'] : false;
if(!class_exists($shortcode_class)) return false;
if(!class_exists($shortcode_class)) {
$custom_shortcode = apply_filters(
'mailpoet_newsletter_shortcode',
$shortcode,
$this->newsletter,
$this->subscriber,
$this->queue,
$content
);
return ($custom_shortcode === $shortcode) ?
false :
$custom_shortcode;
}
return $shortcode_class::process(
$shortcode_action,
$shortcode_default_value,
$this->newsletter,
$this->subscriber,
$text,
$shortcode
$this->queue,
$content
);
}, $shortcodes);
return $processed_shortcodes;
}
function replace($text) {
$shortcodes = $this->extract($text);
$processed_shortcodes = $this->process($shortcodes, $text);
function replace($content, $categories = false) {
$shortcodes = $this->extract($content, $categories);
$processed_shortcodes = $this->process($shortcodes, $content);
$shortcodes = array_intersect_key($shortcodes, $processed_shortcodes);
return str_replace($shortcodes, $processed_shortcodes, $text);
return str_replace($shortcodes, $processed_shortcodes, $content);
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace MailPoet\Newsletter\Viewer;
use MailPoet\Models\Newsletter;
use MailPoet\Models\SendingQueue;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\Links\Links;
use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Newsletter\Shortcodes\Shortcodes;
class ViewInBrowser {
public $data;
function __construct($data) {
$this->data = $data;
}
function view($data = false) {
$data = ($data) ? $data : $this->data;
$newsletter = ($data['newsletter'] !== false) ?
Newsletter::findOne($data['newsletter']) :
false;
if(!$newsletter) $this->abort();
$subscriber = ($data['subscriber'] !== false) ?
$this->verifySubscriber($data['subscriber'], $data['subscriber_token']) :
false;
$queue = ($data['queue'] !== false) ?
SendingQueue::findOne($data['queue']) :
false;
$rendered_newsletter =
$this->getAndRenderNewsletter($newsletter, $subscriber, $queue);
header('Content-Type: text/html; charset=utf-8');
echo $rendered_newsletter;
exit;
}
function verifySubscriber($subscriber_id, $subscriber_token) {
$subscriber = Subscriber::findOne($subscriber_id);
if(!$subscriber ||
!Subscriber::verifyToken($subscriber->email, $subscriber_token)
) {
return false;
}
return $subscriber;
}
function getAndRenderNewsletter($newsletter, $subscriber, $queue) {
if($queue) {
$newsletter_body = json_decode($queue->newsletter_rendered_body, true);
} else {
$renderer = new Renderer($newsletter->asArray());
$newsletter_body = $renderer->render();
}
$shortcodes = new Shortcodes(
$newsletter,
$subscriber,
$queue
);
$rendered_newsletter = $shortcodes->replace($newsletter_body['html']);
if($queue && (boolean) Setting::getValue('tracking.enabled')) {
$rendered_newsletter = Links::replaceSubscriberData(
$newsletter->id,
$subscriber->id,
$queue->id,
$rendered_newsletter
);
}
return $rendered_newsletter;
}
private function abort() {
header('HTTP/1.0 404 Not Found');
exit;
}
}

View File

@ -1,24 +1,23 @@
<?php
namespace MailPoet\Router;
use MailPoet\Config\Shortcodes;
use MailPoet\Listing;
use MailPoet\Mailer\API\MailPoet;
use MailPoet\Models\Newsletter;
use MailPoet\Models\Segment;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Models\NewsletterTemplate;
use MailPoet\Models\NewsletterSegment;
use MailPoet\Models\NewsletterOptionField;
use MailPoet\Models\NewsletterOption;
use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Models\SendingQueue;
use MailPoet\Newsletter\Scheduler\Scheduler;
use MailPoet\Newsletter\Shortcodes\Categories\Link;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
require_once(ABSPATH . 'wp-includes/pluggable.php');
class Newsletters {
function __construct() {
}
@ -134,19 +133,36 @@ class Newsletters {
return false;
}
function render($data = array()) {
function showPreview($data = array()) {
if(!isset($data['body'])) {
return false;
return array(
'result' => false,
'errors' => array(__('Newsletter data is missing.'))
);
}
$renderer = new Renderer($data);
$rendered_newsletter = $renderer->render();
$shortcodes = new \MailPoet\Newsletter\Shortcodes\Shortcodes($data);
$rendered_newsletter = $shortcodes->replace($rendered_newsletter['html']);
return array('rendered_body' => $rendered_newsletter);
$newsletter_id = (isset($data['id'])) ? (int)$data['id'] : 0;
$newsletter = Newsletter::findOne($newsletter_id);
if(!$newsletter) {
return array(
'result' => false,
'errors' => array(__('Newsletter could not be read.'))
);
}
$newsletter->body = $data['body'];
$newsletter->save();
$wp_user =wp_get_current_user();
$subscriber = Subscriber::where('email', $wp_user->data->user_email)
->findOne();
$subscriber = ($subscriber) ? $subscriber->asArray() : $subscriber;
$preview_url = Link::getViewInBrowserUrl($data, $subscriber);
return array(
'result' => true,
'data' => array('url' => $preview_url)
);
}
function sendPreview($data = array()) {
$id = (isset($data['id'])) ? (int) $data['id'] : 0;
$id = (isset($data['id'])) ? (int)$data['id'] : 0;
$newsletter = Newsletter::findOne($id);
if($newsletter === false) {
@ -268,7 +284,7 @@ class Newsletters {
isset($data['type']) &&
$data['type'] === 'notification'
) {
Scheduler::postNotification($newsletter->id);
Scheduler::processPostNotificationSchedule($newsletter->id);
}
return array(
'result' => true,

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