Compare commits

...

306 Commits

Author SHA1 Message Date
Tautvidas Sipavičius
2f25dc6a20 Bump up release version to 0.0.27 2016-05-06 14:46:12 +03:00
Tautvidas Sipavičius
fc38ee2f08 Merge pull request #466 from mailpoet/rendering_engine_update
Fixes empty paragraph spacing
2016-05-05 13:04:26 +03:00
Vlad
33bebc6629 - Replaces empty paragraphs with single space according to specific
condition
2016-05-04 19:57:10 -04:00
Tautvidas Sipavičius
14a8e02f99 Merge pull request #465 from mailpoet/browser_preview
Newsletter preview
2016-05-04 17:58:41 +03:00
Vlad
0bf6c87ec7 - Fixes shortcode category name for view in browser url
- Updates shortcode regex
2016-05-04 10:45:30 -04:00
Vlad
422fba2835 - Updates based on code review comments 2016-05-03 18:41:57 -04:00
Vlad
f36dbb78e6 - Updates unit test 2016-05-02 13:44:36 -04:00
Vlad
3213dd0d08 - Removes unused exception handlers 2016-05-02 13:19:10 -04:00
Vlad
3f2199fd63 - Updates link tracking to use the same Public API data format as other
endpoints
2016-05-02 13:15:48 -04:00
Vlad
a4477a9bd6 - Updates CONST values 2016-05-02 12:27:00 -04:00
Vlad
52790d7bd6 - Updates code comment 2016-05-02 12:24:56 -04:00
Vlad
0b9812210f - Updates CONST values 2016-05-02 11:30:37 -04:00
Vlad
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
Vlad
b38742ddc0 - Changes divider to CONST 2016-05-02 10:02:22 -04:00
Vlad
49e38549ec - Fixes URLs not being properly generated when tracking is enabled 2016-05-02 10:00:35 -04:00
Vlad
afcb0a0d7f - Upperases all constants 2016-05-02 09:47:54 -04:00
Vlad
d18f0e50b5 - Removes debug backtrace
- Removes uncommented queue save
2016-05-02 09:23:37 -04:00
Vlad
6868a07ead - Moves link saving logic into Links class 2016-05-01 21:55:24 -04:00
Vlad
cca76d0d97 - Adds custom shortcode processing logic
- Updates shortcode categories code
- Completely rewrites shortcodes unit tess
2016-05-01 14:07:06 -04:00
Vlad
70fa77d333 - Creates temporary folder if it doesn't exist 2016-04-30 22:38:55 -04:00
Vlad
412201d965 - Updates unit test 2016-04-30 22:38:39 -04:00
Vlad
045a92c7d6 - Standardizes variable names 2016-04-30 22:21:03 -04:00
Vlad
2ba2e3eca5 - Refactors sending queue worker 2016-04-30 22:19:59 -04:00
Vlad
90c294f60e - Updates editor/router to use the new browser preview class 2016-04-30 22:19:59 -04:00
Vlad
57b953dd14 - Rewrites shortcode processing class to work with other changes 2016-04-30 22:19:59 -04:00
Vlad
0f81a8db60 - Updates method to utilize constant value 2016-04-30 22:19:59 -04:00
Vlad
2d6971f8df - Refactors link tracking class 2016-04-30 22:19:59 -04:00
Vlad
0abe8b5371 - Implements view in browser 2016-04-30 22:19:59 -04:00
Vlad
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
Tautvidas Sipavičius
fa9d32c230 Bump up release version to 0.0.26 2016-04-29 21:09:41 +03:00
Tautvidas Sipavičius
1b3ceca7b2 Merge pull request #464 from mailpoet/undo_go_back
remove attempt at 'goback' after editing an entity
2016-04-29 21:07:10 +03:00
Jonathan Labreuille
9be326b45d remove attempt at 'goback' after editing an entity 2016-04-29 18:35:52 +02:00
Tautvidas Sipavičius
2d2e1298c4 Merge pull request #462 from mailpoet/segments_listing
React components update + bugfixes
2016-04-29 14:18:05 +03:00
Tautvidas Sipavičius
5dc3a4386e Merge pull request #463 from mailpoet/queue_processed_date_fix
Updates queue processed date to account for time offset
2016-04-29 12:16:51 +03:00
Vlad
b617dde266 - Updates queue processed date to account for time offset 2016-04-28 12:42:16 -04:00
Jonathan Labreuille
962e91f9dc fixed merge fuck 2016-04-28 17:15:07 +02:00
Jonathan Labreuille
4047b41a7f Updated all React components to their latest version
- updated code due to deprecated warnings (mostly router stuff & input default value)
- set default sender based on settings when creating new newsletter
- fixed erroneous UTC offset when displaying dates (PHP takes care of it)
2016-04-28 17:02:24 +02:00
mrcasual
59199140bf Merge pull request #448 from mailpoet/twig_template_cache
modified temp path/url for cache/export folders
2016-04-28 10:18:53 -04:00
Tautvidas Sipavičius
1079c0beae Merge pull request #458 from mailpoet/click_rate_update
fixes click rate
2016-04-28 17:01:05 +03:00
Vlad
1694baab7d Merge branch 'click_rate_update' of mailpoet:mailpoet/mailpoet into click_rate_update 2016-04-28 09:37:04 -04:00
Vlad
335ac9c778 - Updates statistics to display only 1 click event from a given subscriber
Fixes #417 (7)
2016-04-28 09:34:59 -04:00
Vlad
65efd234f9 - Fixes duplicate status for different newsletters
- Updates statistics not to display NaN values
2016-04-28 09:25:09 -04:00
Tautvidas Sipavičius
f726d943db Merge pull request #460 from mailpoet/text_rendering_fix
Text rendering fix
2016-04-28 12:55:08 +03:00
Tautvidas Sipavičius
2d1e950097 Merge pull request #459 from mailpoet/scheduled_status
Updates newsletter listing status
2016-04-28 12:52:46 +03:00
Vlad
ca2f16970b - Fixes and issue with lists not retaining links and other HTML content 2016-04-27 21:22:39 -04:00
Vlad
4a123f8fe9 - Displays proper status for scheduled newsletters 2016-04-27 20:59:04 -04:00
Vlad
7b224328e1 - Updates statistics to display only 1 click event from a given subscriber
Fixes #417 (7)
2016-04-27 20:55:19 -04:00
Vlad
699dfa19f0 - Removes leftover console command 2016-04-27 20:14:06 -04:00
Vlad
06d56fe19d - Adds "scheduled for" status for notifications
- Adds post notification/welcome activation message
- Implements schedule update when editing newsletter
Implements #405 (3 & 7)
2016-04-27 20:08:45 -04:00
Tautvidas Sipavičius
1b7ac62b5c Merge pull request #453 from mailpoet/queue_statistics
Send statistics
2016-04-27 21:28:42 +03:00
Vlad
ae358ce13e - Updates statistics SQL query
- Fixes tests + expands coverage
- Removes leftover console command
2016-04-27 21:26:47 +03:00
Vlad
18f35b5e91 - Displays statistics in newsletter listing 2016-04-27 21:26:47 +03:00
Vlad
67ca305b7f - Adds withStatistics method to the Newsletter model
- Updates Newsletter router to return statistics
- Returns only completed/running queues
2016-04-27 21:26:47 +03:00
Tautvidas Sipavičius
cb0908fc70 Merge pull request #454 from mailpoet/post_title_shortcode_fix
Updates newsletter post title shortcode
2016-04-27 21:24:34 +03:00
Tautvidas Sipavičius
42df59076d Merge pull request #457 from mailpoet/forms_listing
Forms listing
2016-04-27 15:15:53 +03:00
Tautvidas Sipavičius
ff8be3bdc6 Merge pull request #456 from mailpoet/manage_subscription_custom_pages
don't enforce our content on custom subscription pages
2016-04-27 15:03:27 +03:00
Jonathan Labreuille
f7b6dcf409 - Remove bulk actions from forms
- form name is now a link to the edit form page
- added "row-title" class so that the name is "bigger"
2016-04-27 13:31:30 +02:00
Tautvidas Sipavičius
940f3848dd Merge pull request #452 from mailpoet/form_stats
Form subscription stats
2016-04-27 14:14:42 +03:00
Jonathan Labreuille
e847ad2df2 don't enforce our content on custom subscription pages 2016-04-27 11:13:36 +02:00
Vlad
2459a103fd - Ignores sticky posts when fetching latest post title
- Updates unit test
2016-04-26 19:53:23 -04:00
Jonathan Labreuille
0dd3f2178e updated form stats to record individual subscription 2016-04-26 18:12:05 +02:00
Jonathan Labreuille
de873eca71 Unit test for StatisticsForms model
- improved incrementation of subscriptions count so that we don't need to fetch the record after an update
2016-04-26 15:38:42 +02:00
Jonathan Labreuille
ef461da77f Statistics for Form Subscriptions
- added statistics_forms table
- added corresponding model to record stats
- record stats whenever someone subscribes via a form
2016-04-26 15:16:37 +02:00
Tautvidas Sipavičius
caf6dcddfa Merge pull request #451 from mailpoet/react_select_fix
Subscribers bulk actions with an extra select
2016-04-26 14:00:32 +03:00
Jonathan Labreuille
9684c88651 refactor 2016-04-26 12:43:48 +02:00
Jonathan Labreuille
256bca8ed9 Fix selection.jsx by adding a check before trying to destroy the select2 instance 2016-04-26 12:19:39 +02:00
Tautvidas Sipavičius
8d56e8582f Merge pull request #450 from mailpoet/unsubscribe_tracking
Implements tracking of unsubscribe events
2016-04-25 21:32:10 +03:00
Tautvidas Sipavičius
56959f2f49 Terminate execution after redirecting a click 2016-04-25 20:33:17 +03:00
Vlad
bbc7de6898 - Removes commit leftovers 2016-04-25 12:02:56 -04:00
Tautvidas Sipavičius
0df246da15 Merge pull request #447 from mailpoet/tracking_fix
Fixes click tracking that would end request when calling open tracking
2016-04-25 18:37:15 +03:00
Tautvidas Sipavičius
757e18355d Merge pull request #446 from mailpoet/welcome_email_fix
welcome email fix
2016-04-25 18:36:57 +03:00
Vlad
4b29b04bd1 - Implements tracking of unsubscribe events 2016-04-25 11:06:01 -04:00
Vlad
6cb94bc413 - Updates open rate tracking class constructor 2016-04-25 09:43:39 -04:00
Vlad
f624c891ab - Capitalizes CONST values 2016-04-25 09:42:02 -04:00
Jonathan Labreuille
b83abf0ac5 modified temp path/url 2016-04-25 14:26:27 +02:00
Vlad
ef1b0036e5 - Fixes click tracking that would end request when calling open tracking 2016-04-22 20:30:16 -04:00
Vlad
5efa9f65c6 - Fixes an issue with welcome email not being sent to WP user with any
role
2016-04-22 18:51:51 -04:00
Tautvidas Sipavičius
a8e3dd424e Bump up release version to 0.0.25 2016-04-22 23:25:28 +03:00
Tautvidas Sipavičius
26628ba156 Merge pull request #445 from mailpoet/sending_fix
Fixes check for changed newsletter content
2016-04-22 23:22:30 +03:00
Tautvidas Sipavičius
78cabde9e1 Select an existing segment as initial value for Welcome segment select 2016-04-22 16:08:45 -04:00
Vlad
1ec0372c2d - Fixes check for changed newsletter content 2016-04-22 16:08:45 -04:00
Tautvidas Sipavičius
31e2d5e771 Merge pull request #444 from mailpoet/fix_router
fixed Router when data represents a single variable (not an array)
2016-04-22 18:05:38 +03:00
Jonathan Labreuille
d2dbf86a9c fixed Router when data represents a single variable (not an array) 2016-04-22 16:59:28 +02:00
Tautvidas Sipavičius
1c39d39078 Merge pull request #442 from mailpoet/sending_subscriber_recalculation
Dynamically updates queue's subscriber count
2016-04-22 17:03:09 +03:00
mrcasual
6d0795abba Merge pull request #438 from mailpoet/non_ajax_subscription
Non ajax subscription
2016-04-22 09:51:47 -04:00
Vlad
5f45c6cc74 - Extracts subscriber recalculation into a spearate method
- Updates subscriber recalculation logic
2016-04-22 09:47:53 -04:00
Jonathan Labreuille
d4d806e247 Merge pull request #443 from mailpoet/editor_rendering
Editor rendering update
2016-04-22 15:38:54 +02:00
Tautvidas Sipavičius
758a545eb6 Merge pull request #441 from mailpoet/shortcodes_fix
Updates the newsletter shortcode
2016-04-22 13:45:15 +03:00
Tautvidas Sipavičius
578088d2e5 Add fallback fonts for all editor fonts 2016-04-22 12:38:59 +03:00
Tautvidas Sipavičius
e17dba2b07 Add blockquote bottom margin 2016-04-22 12:30:57 +03:00
Vlad
7e5cf533f0 Fixes class name 2016-04-22 12:30:57 +03:00
Vlad
f7c656aed5 Fixes blockquote content duplication 2016-04-22 12:30:57 +03:00
Vlad
2e42305710 Updates line breaks in footer/header to match the editor 2016-04-22 12:30:57 +03:00
Tautvidas Sipavičius
b3e310652e Remove bottom margin from paragraphs 2016-04-22 12:30:57 +03:00
Tautvidas Sipavičius
6737158130 - Change paragraph bottom margin to equal line-height
- Remove horizontal blockquote margin to match Renderer;
- Removed bottom padding from last text elements in column;
- Removed bottom padding from full width images;
2016-04-22 12:30:57 +03:00
Tautvidas Sipavičius
906558a772 Apply paragraph styles to LI tags 2016-04-22 12:30:57 +03:00
Tautvidas Sipavičius
d559483c7b - Add bottom margin to headings
- Fix top and bottom margin for UL tags
- Add bottom margin to LI tags
2016-04-22 12:30:57 +03:00
Tautvidas Sipavičius
bec3e02285 Remove vertical button padding, following renderer 2016-04-22 12:30:57 +03:00
Tautvidas Sipavičius
9d6d72dd8c Use fallback font stack for Lucida font 2016-04-22 12:30:57 +03:00
Tautvidas Sipavičius
3b76f838d1 Merge pull request #440 from mailpoet/sending_fix
Adds condition that prevents duplicate and/or stuck post notifications
2016-04-22 11:32:14 +03:00
Vlad
909ad86e33 - Recalculates the number of processed/to process subscribers if the
original count does not the current count from the database
2016-04-21 21:36:37 -04:00
Vlad
dd8f58e35e - Fixes incorrect newsletter issue number display
- Updates newsletter 'last post title' logic
- Updates shortcode extraction regex to allow limiting extraction of only
  specific categories
- Updates unit tests
- Closes #380
2016-04-21 20:04:02 -04:00
Vlad
4b2061fcfa - Adds condition that prevents duplicate and/or stuck post notifications 2016-04-21 18:20:13 -04:00
Tautvidas Sipavičius
8e6ca502b3 Merge pull request #439 from mailpoet/sending_fix
Sending fix
2016-04-21 19:05:49 +03:00
Vlad
ef85834db5 - Removes debugging function 2016-04-21 11:32:29 -04:00
Vlad
2bdcd0eb42 - Prevents duplicate newsletters from being sent 2016-04-21 10:45:00 -04:00
Vlad
3b90be4122 - Fixes scheduling of notifications 2016-04-21 10:44:43 -04:00
Jonathan Labreuille
375e70d84b success messages in both context when subscribing to a form 2016-04-21 12:39:14 +02:00
Jonathan Labreuille
42d586610e form subscription only using Router now 2016-04-20 16:59:18 +02:00
Tautvidas Sipavičius
e4a5438512 Merge pull request #435 from mailpoet/open_tracking
Implements open tracking
2016-04-20 14:32:20 +03:00
Vlad
11b22fa63a - Enables newsletter open tracking when links are clicked 2016-04-19 21:01:27 -04:00
Vlad
45b933d635 - Implements open tracking 2016-04-19 21:01:27 -04:00
Vlad
15cf087d40 - Implements post-processing filter during rendering 2016-04-19 21:01:27 -04:00
Tautvidas Sipavičius
ed09c3e5d4 Merge pull request #427 from mailpoet/batch_sending_fix
Adds missing rendered subject to the queue during bulk processing
2016-04-18 18:27:24 +03:00
Tautvidas Sipavičius
285a556f21 Merge pull request #426 from mailpoet/subscriber_selection_update
Updates subscriber selection logic
2016-04-18 17:41:03 +03:00
Vlad
dc1ef2af47 - Removes check for signup confirmation when selecting subscribers 2016-04-18 17:33:08 +03:00
Tautvidas Sipavičius
6f17f0d2d1 Merge branch 'subscriber_selection_update' 2016-04-18 17:28:53 +03:00
Vlad
91076580ef - Enables check for subscribers who has "subscribed" status during
newsletter sending
- Enforces "signup_confirmation" option when selecting subscribers for
  newsletter sending
- Updates unit tests
2016-04-18 17:28:37 +03:00
Tautvidas Sipavičius
d4abaa7150 Merge branch 'link_tracking_update' 2016-04-18 16:57:56 +03:00
Vlad
d2bc0fd24a - Removes check for signup confirmation when selecting subscribers 2016-04-18 08:45:38 -04:00
Vlad
1f26079b5f - Removes unused properties from Links class and converts methods to
static
2016-04-16 16:48:35 -04:00
Vlad
87a1211a55 - Adds missing rendered subject to the queue during bulk processing 2016-04-15 20:06:35 -04:00
Vlad
0ca0a7d029 - Enables check for subscribers who has "subscribed" status during
newsletter sending
- Enforces "signup_confirmation" option when selecting subscribers for
  newsletter sending
- Updates unit tests
2016-04-15 20:02:44 -04:00
Tautvidas Sipavičius
cb3d49f200 Bump up release version to 0.0.24 2016-04-15 21:27:07 +03:00
Jonathan Labreuille
c0da428c27 Merge pull request #425 from mailpoet/subscription_pages
Subscription pages
2016-04-15 19:07:20 +02:00
Jonathan Labreuille
435cd9b777 fixed unsubscribe action + updated public api decodeData to prevent warning 2016-04-15 18:59:20 +02:00
Vlad
bfde34eb8d - Rebases master
- Updates PubliAPI's logic dealing with request data
2016-04-15 12:10:56 -04:00
Jonathan Labreuille
a4c1b24c35 fixed unit tests 2016-04-15 11:56:33 -04:00
Jonathan Labreuille
2cbd2d54f3 Subscription pages 2016-04-15 11:56:33 -04:00
Tautvidas Sipavičius
378f6d803a Merge pull request #422 from mailpoet/link_tracking
Implements links tracking
2016-04-15 18:47:14 +03:00
Vlad
af4d29ebe6 - Updates codes based on Taut's comments 2016-04-15 11:36:57 -04:00
Vlad
599661e028 - Updates code based on Taut's comments 2016-04-14 20:35:56 -04:00
Vlad
84294b7ee6 - Adds subsription tracking option to Settings->Advanced
- Updates sending queue worker to not replace links if tracking is
  disabled
Closes #417
2016-04-14 19:37:27 -04:00
Vlad
a3e6eb5bba - Implementsi tracking clicks & redirecting URLs 2016-04-14 19:37:27 -04:00
Vlad
67359980e9 - Renames statistics table to the new "statistics_entity" format
- Adds new clicks statistics model
2016-04-14 19:37:27 -04:00
Vlad
f1b955d74a - Implements link extraction, processing, replacement and saving 2016-04-14 19:37:27 -04:00
Tautvidas Sipavičius
809be415c5 Merge pull request #423 from mailpoet/text_block_fix
Fixes 'Can't use method return value in write context' error
2016-04-14 12:32:31 +03:00
Vlad
457a4d1bba - Fixes 'Can't use method return value in write context' error 2016-04-13 22:46:59 -04:00
Tautvidas Sipavičius
1e16912763 Merge pull request #420 from mailpoet/sending_queue_router_fix
Prevents newsletters from being queued when mailer is not configured
2016-04-12 16:02:18 +03:00
Vlad
e2c9971c99 - Prevents newsletters from being queued when mailer is not configured
Closes #342
2016-04-12 08:58:21 -04:00
Jonathan Labreuille
c1d31ca400 Merge pull request #414 from mailpoet/sending_last_step
Sending options in last newsletter creation step
2016-04-12 13:14:34 +02:00
Tautvidas Sipavičius
a011c3aade Fix standard newsletters to not send when segments are missing 2016-04-12 14:11:26 +03:00
Tautvidas Sipavičius
e35e97cdbf Fix Select2 integration in Form component 2016-04-12 14:04:01 +03:00
Tautvidas Sipavičius
49a59d35a1 Add number of subscribers to segment selection 2016-04-12 14:04:01 +03:00
Tautvidas Sipavičius
9a46640c15 Allow selecting only published segments, fix "Save" translation 2016-04-12 14:04:01 +03:00
Tautvidas Sipavičius
678a0b3835 Add form loading state, remove errors and params 2016-04-12 14:04:01 +03:00
Tautvidas Sipavičius
0c008325c4 - Fix unmounting select2 component
- Fix subjects of welcome and notification newsletters
- Remove debugging statements
2016-04-12 14:04:01 +03:00
Tautvidas Sipavičius
ad5441487b Show different fields based on newsletter type, fix saving across
multiple endpoints
2016-04-12 14:04:01 +03:00
Tautvidas Sipavičius
104620a40a Add react component support to Forms, make newsletter scheduling
components reusable
2016-04-12 14:04:01 +03:00
mrcasual
c1a3ba67f5 Merge pull request #418 from mailpoet/fix_initializer
fix cron issue
2016-04-11 09:41:08 -04:00
Jonathan Labreuille
c42bbf3dc4 fix cron issue 2016-04-11 15:35:47 +02:00
Tautvidas Sipavičius
7d34274fbf Merge pull request #416 from mailpoet/rendering_engine_update
Sets text-align to left when it's not center|justify|right
2016-04-11 13:51:42 +03:00
Vlad
f930b3303b - Sets text-align to left when it's not center|justify|right 2016-04-08 20:37:00 -04:00
Tautvidas Sipavičius
a25ea3ddf6 Bump up release version to 0.0.23 2016-04-09 02:08:05 +03:00
Tautvidas Sipavičius
021349caee Merge pull request #415 from mailpoet/public_api_update
Updates query parameters for public API
2016-04-09 02:01:20 +03:00
Vlad
89f2958d23 - Updates cron to use new public API query parameters 2016-04-08 18:53:57 -04:00
Vlad
8c8435766e - Updates query parameters for public API 2016-04-08 18:40:45 -04:00
Tautvidas Sipavičius
40dd1bbb3b Merge pull request #413 from mailpoet/rendering_engine_update
Rendering engine update
2016-04-08 22:27:48 +03:00
Vlad
ba40437eb9 - Enables dynamic line-height based on font-size 2016-04-08 15:19:14 -04:00
Vlad
813db1ae33 - Sets LI bottom margin to 10px 2016-04-08 08:45:09 -04:00
Vlad
9588397e4e - Moves all styles logic to the StylesHelper class
- Sets margin-bottom on h1-4 to be 0.3*font-size
2016-04-07 19:39:09 -04:00
Vlad
ecf83ca419 - Disables color compression when tidying CSS 2016-04-07 19:39:09 -04:00
Vlad
9cc494f0fa - Removes image height HTML attribute and sets "height:auto" style 2016-04-07 19:39:09 -04:00
Vlad
dd4b7e4d1c - Adds lines heights to LIs 2016-04-07 19:39:09 -04:00
Vlad
738b2f6c17 - Adds dynamic line breaks to paragraphs when the elment is followed by
list
2016-04-07 19:39:09 -04:00
Vlad
33289342d3 - Implements dynamic margins on headings 2016-04-07 19:39:09 -04:00
Vlad
a00f1efcfe - Updates unit test due to button style change 2016-04-07 19:38:15 -04:00
Vlad
b89897e6d4 - Adds line breaks after headings IF they are followed by paragraph
- Adds line breaks after blocksquotes
- Updates margin on lists
- Adds line breaks after paragraphs IF they are not the last element
2016-04-07 19:38:15 -04:00
Vlad
c539837896 - Makes button's stroke width = border width 2016-04-07 19:38:15 -04:00
Vlad
27edf5f71d - Updates list styles 2016-04-07 19:38:15 -04:00
Vlad
32cc5644f9 - Removes height:auto from images 2016-04-07 19:38:15 -04:00
Vlad
8a664aa7f1 - Set left text alignment on all elements when alignment is otherwise
absent
2016-04-07 19:38:15 -04:00
Vlad
7e5e8a4282 - Updates button padding 2016-04-07 19:38:15 -04:00
Tautvidas Sipavičius
70d5d609e2 Merge pull request #412 from mailpoet/scheduled_sending
Fixes an issue with welcome emails not being sent to confirmed subscribers
2016-04-07 12:09:30 +03:00
Tautvidas Sipavičius
19160c99e1 Merge pull request #411 from mailpoet/send_with_review
Send with review
2016-04-06 18:23:50 +03:00
Tautvidas Sipavičius
99b2a7457e Merge pull request #410 from mailpoet/newsletter_send_preview_fix
Fixes an issue with shortcodes not rendering when sending newsletter
2016-04-06 18:05:39 +03:00
Jonathan Labreuille
945d7edc70 Send with review
- updated MailPoet logo
- added SPF
- hide Dkim for beta
- added warning in case the number of emails/sec is too high
2016-04-06 16:57:10 +02:00
Vlad
6a97badfed - Fixes an issue with welcome emails not being seint to confirmed
subscribers
2016-04-05 14:34:22 -04:00
Vlad
5ec8e4ed52 - Fixes an issue with shortcodes not rendering when sending newsletter
preview
2016-04-05 09:54:04 -04:00
Tautvidas Sipavičius
1569b5f80a Merge pull request #409 from mailpoet/scheduled_sending
Fixed an issue with newsletter schedule not being saved
2016-04-05 12:10:34 +03:00
Vlad
e62ecc5036 - Fixes duplicate detection check 2016-04-04 21:09:15 -04:00
Vlad
b0150e184b - Fixed an issue with newsletter schedule not being saved 2016-04-04 19:41:18 -04:00
Tautvidas Sipavičius
8b96854f39 Bump up release version to 0.0.22 2016-04-01 14:57:11 +03:00
Tautvidas Sipavičius
28d8600078 Merge pull request #408 from mailpoet/rendering_engine_update
Rendering engine update
2016-04-01 12:37:11 +03:00
Vlad
4f30158722 - Updates button height calculation
- Removes background color when it's set to "transparent"
- Removes second line break from paragraphs
- Introduces other changes based on Becks's testing
- Updates unit tests
2016-03-31 19:19:58 -04:00
Tautvidas Sipavičius
4486f9c5b0 Merge pull request #407 from mailpoet/scheduled_sending
Prevents welcome emails to be sent to unconfirmed subsribers
2016-03-31 14:53:41 +03:00
Tautvidas Sipavičius
8515dcf29b Merge pull request #406 from mailpoet/rendering_engine_update
Rendering engine update
2016-03-31 14:05:18 +03:00
Vlad
d16eb87782 - Prevents welcome emails to be sent to unconfirmed subscribers
- Closes #384
2016-03-30 20:59:55 -04:00
Vlad
3c77e5d25e - Removes dynamic line-height based on column width
- Updates logic behind removing last element bottom padding
- Properly sets font size for H1-4 tags, header, footer and text
2016-03-30 20:09:06 -04:00
Vlad
aa1a2a0da9 - Updates button padding 2016-03-30 20:09:06 -04:00
Tautvidas Sipavičius
7c9029b227 Bump up release version to 0.0.21 2016-03-25 13:10:37 +02:00
mrcasual
1823bf606a Merge pull request #403 from mailpoet/signup_confirmation_review
Signup confirmation review
2016-03-24 14:43:36 -04:00
Tautvidas Sipavičius
525b7fdd65 Merge pull request #402 from mailpoet/scheduled_sending
Scheduled sending
2016-03-24 16:10:56 +02:00
Tautvidas Sipavičius
11031d2b56 Remove unused cron translations, fix start and stop button translations 2016-03-24 16:10:39 +02:00
Vlad
4d45635d03 - Implements "in any role" for WP user welcome e-mails 2016-03-24 10:02:04 -04:00
Vlad
b81764402b - Updates hooks used to schedule welcome email
- Simplifies cron status message
- Implements newsletter change detection between scheduled queues
- Implements detection for subscribers who unsubscribed from list
- Implements detection for WP users who changed roles
- Updates scheduler worker logic
- Various fixes
2016-03-24 10:02:04 -04:00
Vlad
947e1150d8 - Removes cron worker hooks as the execution timer can't be persisted
between them
2016-03-24 10:00:52 -04:00
Jonathan Labreuille
97f0e512af populate from/reply_to for signup confirmation
- added loading screen to reinstall process
2016-03-24 14:22:37 +01:00
Jonathan Labreuille
f15374de43 don't replace title on custom mailpoet pages 2016-03-24 14:22:37 +01:00
Jonathan Labreuille
f082c065d1 Confirmation email + Subscription pages
- form as an iframe: increased marginY
- fixed issue with page titles (old themes using wp_title hook)
- redirect on reinstall instead of showing a message (to avoid being able to re-save old settings)
2016-03-24 14:22:37 +01:00
Jonathan Labreuille
72a9951125 Merge pull request #400 from mailpoet/i18n
Javascript translations
2016-03-24 13:11:51 +01:00
Tautvidas Sipavičius
a82d9a63d4 Add missing unsubscribedOn translation on subscribers form 2016-03-24 14:09:35 +02:00
Tautvidas Sipavičius
a7e9979781 Merge pull request #398 from mailpoet/rendering_engine_update
Updates rendering engine based on Becs's comments
2016-03-24 13:18:06 +02:00
mrcasual
cebd1ee7ae Merge pull request #399 from mailpoet/editor_rendering
Adapts editor rendering to Becs' feedback
2016-03-23 18:21:00 -04:00
Vlad
5faa467306 - Updates merge issue with template
- Updates button width/height based on border radius
2016-03-23 18:18:21 -04:00
Tautvidas Sipavičius
53eb9cd2ae Translate JS code in listings and forms 2016-03-23 18:59:01 +02:00
Tautvidas Sipavičius
7f6eed6d66 Translate cron, forms, newsletters, segments and subscribers 2016-03-23 14:52:06 +02:00
Tautvidas Sipavičius
74f3fa65cd Add translatiosn for newsletter creation and listings 2016-03-23 14:52:06 +02:00
Tautvidas Sipavičius
8723aa4e4e Remove obsolete test file 2016-03-23 14:52:06 +02:00
Tautvidas Sipavičius
ccab8b4cf3 Add MailPoet.I18n for basic translation handling, removed MailPoetI18n 2016-03-23 14:52:06 +02:00
Tautvidas Sipavičius
4c4a4ab31d Reduce max button width to 288px, max button border to 10px 2016-03-23 14:09:19 +02:00
Tautvidas Sipavičius
45df02b0ec Merge pull request #395 from mailpoet/many_improvements
Many improvements
2016-03-23 13:38:40 +02:00
Tautvidas Sipavičius
dd7067e590 Merge pull request #397 from mailpoet/scheduled_sending
Updates scheduling logic to work with the new normalized DB time
2016-03-23 11:51:58 +02:00
Tautvidas Sipavičius
1219b5ba49 Merge pull request #396 from mailpoet/timezone_fix
Normalizes time difference between WP and database
2016-03-23 11:45:42 +02:00
Vlad
01496ac813 Merge branch 'scheduled_sending' of mailpoet:mailpoet/mailpoet into scheduled_sending 2016-03-22 20:55:42 -04:00
Vlad
3b3ccc18ce - Updates scheduling logic to work with the new normalized DB time 2016-03-22 20:43:16 -04:00
Vlad
3dce951e66 - Updates rendering engine based on Becs's comments
- Prevents removing of side padding
- Enables font size on output
- Sets line-height in footer
- Adds image height:auto
- Adds mobile styles for buttons
- Updates style sheet
2016-03-22 20:21:39 -04:00
Vlad
db1dc172aa - Updates scheduling logic to work with the new normalized DB time 2016-03-22 18:26:37 -04:00
Vlad
26c5cc1e43 - Normalizes time difference between WP and database 2016-03-22 13:05:41 -04:00
Jonathan Labreuille
f91bfbf473 handle form as iframe 2016-03-22 17:25:25 +01:00
Jonathan Labreuille
3dae0ef13f increased page input in listings 2016-03-22 17:25:25 +01:00
Jonathan Labreuille
3281ac390e Improved segment selection errors in form editor
- improved error display to make it more obvious (added border on select2 on error)
2016-03-22 17:25:25 +01:00
Jonathan Labreuille
d731a6b432 toggle segment selection validation on form editor 2016-03-22 17:25:25 +01:00
Jonathan Labreuille
bb869e8ae8 Fixed setWindowTitle for WP version < 4.4
- fixed variable name Env::temp_url (instead of temp_URL)
- updated cache folder to be in the temp folder (uploads) instead of views (within plugin)
2016-03-22 17:25:25 +01:00
Tautvidas Sipavičius
7331e5cabd Merge pull request #392 from mailpoet/shortcodes_update
Updates shortcodes logic
2016-03-21 17:58:38 +02:00
Vlad
ba05ca35af - Implements shortcodes rendering in subject line 2016-03-21 11:45:08 -04:00
Vlad
91e7bf6336 Merge branch 'shortcodes_update' of mailpoet:mailpoet/mailpoet into shortcodes_update 2016-03-21 10:12:10 -04:00
Tautvidas Sipavičius
ff58067d55 Change array_column to Helpers::arrayColumn method 2016-03-21 16:07:20 +02:00
Tautvidas Sipavičius
2ba6bb339e Fix newsletter options format for newsletters 2016-03-21 15:50:48 +02:00
Vlad
a47afdd313 - Fixes queue worker issue 2016-03-21 09:09:43 -04:00
Vlad
608b559ee1 - Minor adjustment to the shortcodes logic 2016-03-21 09:09:43 -04:00
Vlad
181ed45d0b - Updates shortcodes logic
- Implements [newsletter:total] and [newsletter:number] shortcodes
- Implements shortcode replacement in subject line
- Updates unit tests
Issue #380
2016-03-21 09:09:43 -04:00
Tautvidas Sipavičius
29fac8d052 Merge pull request #394 from mailpoet/scheduled_sending
Updates scheduled sending
2016-03-21 15:06:30 +02:00
Vlad
a3d7d53eea - Fixes queue worker issue 2016-03-21 08:58:51 -04:00
Vlad
3f6caf5fa4 - Implements scheduler worker for welcome and post notifications
- Updates sending queue worker to save rendered newsletter body
- Updates sending queue router to schedule post notification newsletters
2016-03-20 22:01:01 -04:00
Vlad
42c4139ba5 - Minor adjustment to the shortcodes logic 2016-03-20 11:37:59 -04:00
Vlad
ad31b143d2 - Updates scheduler worker to process queued newsletters 2016-03-19 12:29:10 -04:00
Vlad
6ec15bec22 - Updates shortcodes logic
- Implements [newsletter:total] and [newsletter:number] shortcodes
- Implements shortcode replacement in subject line
- Updates unit tests
Issue #380
2016-03-19 11:19:22 -04:00
Tautvidas Sipavičius
71d8fb0d93 Bump up version to 0.0.20 2016-03-18 19:40:11 +02:00
Tautvidas Sipavičius
2f293da7a3 Merge pull request #391 from mailpoet/scheduled_sending
Scheduled sending
2016-03-18 18:49:47 +02:00
Vlad
17b56f0160 - Fixes scheduling issues
- Fixes unit test
- Updates as per code review comments
2016-03-18 12:11:38 -04:00
Vlad
bb9fce7f82 - Implements post notification scheduling 2016-03-18 11:15:31 -04:00
Tautvidas Sipavičius
ad4f1f8326 Merge pull request #390 from mailpoet/newsletter_footer_links
Newsletter footer links
2016-03-18 16:10:17 +02:00
Jonathan Labreuille
8ece62c9a6 fix tests 2016-03-18 14:58:33 +01:00
Jonathan Labreuille
a9b9e9c631 Updated tests in order to fix WP related issues 2016-03-18 14:30:59 +01:00
Vlad
6e289b6a8f - Implements welcome e-mail scheduling 2016-03-17 11:22:29 -04:00
Jonathan Labreuille
20ced8b099 replace mailpoet_title 'hack' by checking the post type 2016-03-17 15:48:41 +01:00
Jonathan Labreuille
f38b632707 properly handle custom subscriptions pages 2016-03-17 15:48:41 +01:00
Jonathan Labreuille
8ce0595342 turned static values into constants 2016-03-17 15:48:06 +01:00
Jonathan Labreuille
f11de2f1ad Updated shortcodes for unsubscribe/manage/browser links
- fixed all issues in #387 except the custom mailpoet pages
2016-03-17 15:48:06 +01:00
Jonathan Labreuille
e28451d410 removed edit page in settings + manage subscription update 2016-03-17 15:45:05 +01:00
Jonathan Labreuille
72882aaf2b fixed shortcodes replacement for "global:" in newsletters
- extracted "get subscription pages urls" from models\Subscriber
- added unit tests for subscription\url class (not working because of WP/Codeception issue)
2016-03-17 15:45:05 +01:00
Jonathan Labreuille
fdf9dd0fa3 Merge pull request #386 from mailpoet/editor_refactor
Editor refactor
2016-03-17 15:19:04 +01:00
Jonathan Labreuille
97dd0abea2 fixed makepot task
- added proper grunt module (grunt-cli)
- updated translations extract code to remove warnings when no translations are found
2016-03-17 15:08:38 +01:00
Tautvidas Sipavičius
a099174226 Revert "Extract text labels from React code for translation in twig views"
This reverts commit 9aef6850c2.

Conflicts:
	views/newsletters.html
2016-03-17 15:24:07 +02:00
Tautvidas Sipavičius
a054acc6e6 Merge pull request #389 from mailpoet/import_xss_update
Updates import to santize user input
2016-03-17 12:02:58 +02:00
Vlad
74254d7e2a - Updates import to santize user input 2016-03-15 13:06:21 -04:00
Vlad
1795964c69 - Updates composer dependencies 2016-03-15 12:10:09 -04:00
Tautvidas Sipavičius
0118b2472a Fix loading of makepot task for pot translation file generation 2016-03-10 19:24:35 +02:00
Tautvidas Sipavičius
5fe03f0dee Add event selection explanation to welcome email type 2016-03-10 17:49:06 +02:00
Tautvidas Sipavičius
9aef6850c2 Extract text labels from React code for translation in twig views 2016-03-10 17:08:40 +02:00
Tautvidas Sipavičius
f688a69f8b Fix translations to be injected at config time 2016-03-07 17:52:14 +02:00
Tautvidas Sipavičius
b2682fa0b7 Remove console.log statements from newsletter editor 2016-03-07 16:57:20 +02:00
Tautvidas Sipavičius
18b15c5440 Restructure JS loading to not duplicate JS asset loading 2016-03-07 16:15:26 +02:00
Tautvidas Sipavičius
c3416977bb Replace editor translations with Twig localize function 2016-03-07 15:42:12 +02:00
Tautvidas Sipavičius
88a00bc38c Rename editor form.html to editor.html 2016-03-07 14:42:12 +02:00
Tautvidas Sipavičius
a1441dfde6 Bump up version to 0.0.19 2016-03-04 18:43:22 +02:00
mrcasual
8e7336d352 Merge pull request #373 from mailpoet/editor_rendering
Editor rendering (Part 2)
2016-03-04 11:11:42 -05:00
mrcasual
e0e2933cdf Merge pull request #379 from mailpoet/scheduled_sending
Fix mistyped method name
2016-03-04 11:06:40 -05:00
Tautvidas Sipavičius
c93ec629ea Fix mistyped method name 2016-03-04 18:03:02 +02:00
Tautvidas Sipavičius
d613df6558 Merge pull request #378 from mailpoet/scheduled_sending
Prepares codebase for future scheduled sending
2016-03-04 17:58:59 +02:00
Vlad
01c9096543 - Rewrites hooks to not use closures 2016-03-04 10:47:55 -05:00
Tautvidas Sipavičius
f120e839dd Merge pull request #375 from mailpoet/export_large_dataset_fix
Export update
2016-03-04 14:12:20 +02:00
Tautvidas Sipavičius
f0ab592c04 Merge pull request #376 from mailpoet/rendering_serverside_update
Removes padding from the last column element
2016-03-04 13:30:31 +02:00
Tautvidas Sipavičius
1a269d28b3 Merge pull request #374 from mailpoet/edit_subscription
edit subscription form + save
2016-03-04 12:54:56 +02:00
Jonathan Labreuille
8d4a666bf0 added logged out handling of subscriber save request 2016-03-04 11:52:06 +01:00
Jonathan Labreuille
4a96e483a6 edit profile rendering (missing segments list) + fallback for Url::redirectBack() 2016-03-04 11:20:17 +01:00
Vlad
263a66407f - Removes mailpoet_padded class from the last element in a column 2016-03-03 19:13:50 -05:00
Vlad
c4b728f4e1 - Updates Daemon to execute workers via WP's action hook
- Bootstraps Scheduler class
2016-03-03 15:42:10 -05:00
Tautvidas Sipavičius
9970ad7fb6 Merge pull request #369 from mailpoet/manage_subscription
Manage subscription (part 2)
2016-03-03 15:33:56 -05:00
Vlad
eb380499d9 - Rewrites export to support large datasets
- Updates unit tests
- Fixes an issue with export UI not displaying subscribers without segment
2016-03-03 14:33:10 -05:00
Jonathan Labreuille
4b528549f5 edit subscription form + save 2016-03-03 15:57:42 +01:00
Tautvidas Sipavičius
a903017bc2 Update "Please include unsubscribe link" message 2016-03-03 15:48:59 +02:00
Tautvidas Sipavičius
afd25e9174 Remove old and unused editor template 2016-03-03 15:39:46 +02:00
Tautvidas Sipavičius
b1bbf1b3bc Add default email for newsletter preview, disallow empty email 2016-03-03 12:59:49 +02:00
Tautvidas Sipavičius
9e84e8df93 Show loading icon when loading newsletter preview 2016-03-02 16:57:04 +02:00
Tautvidas Sipavičius
df775b5a07 Set font-style for paragraph and headings 2016-03-02 16:57:04 +02:00
Tautvidas Sipavičius
c9c22b9b52 Switch 'Comic Sans' to 'Comic Sans MS' 2016-03-02 16:57:04 +02:00
Tautvidas Sipavičius
93d20688ea Prevent heading line height and font weight from being overridden 2016-03-02 16:57:04 +02:00
Tautvidas Sipavičius
9ebcddfa2a Update button defaults 2016-03-02 16:57:04 +02:00
Tautvidas Sipavičius
85995bc8a6 Merge pull request #369 from mailpoet/manage_subscription
Manage subscription (part 2)
2016-03-02 16:42:12 +02:00
Jonathan Labreuille
a8f2959bc6 added Subscriber::generateToken() 2016-03-02 15:07:37 +01:00
Jonathan Labreuille
36242bd580 Fixed sending confirmation email to new subscribers (first time)
- fixed newsletters listing issue with queue
2016-03-02 13:11:06 +01:00
Jonathan Labreuille
1e7dbc8449 refactor pages 2016-03-01 18:27:31 +01:00
Jonathan Labreuille
12c159c627 bugfix + refactoring 2016-03-01 16:23:15 +01:00
Jonathan Labreuille
82ed7e51c5 Replaced "contains" by "indexOf" (chrome issue)
- added public ajax routing (not checking permissions)
- exception handling in form subscription
2016-03-01 13:18:36 +01:00
Tautvidas Sipavičius
0b3aa0d12a Merge pull request #370 from mailpoet/import_batch_update
Import batch size increase
2016-03-01 12:10:46 +02:00
Vlad
4d788f69aa - Updates batch size to 2000 2016-02-29 11:41:17 -05:00
Jonathan Labreuille
c721843c12 extracted subscription pages code from router 2016-02-29 13:34:17 +01:00
Jonathan Labreuille
d2be407ccb rendering of edit subscription form 2016-02-29 13:34:17 +01:00
Jonathan Labreuille
e6337216cf improved demo view of subscription pages 2016-02-29 13:34:17 +01:00
Jonathan Labreuille
f1c396f0b0 basic implementation of confirm/edit/unsubscribe pages 2016-02-29 13:34:17 +01:00
Jonathan Labreuille
3622bc9fcb fixed sending issue of confirmation email 2016-02-29 13:34:17 +01:00
Jonathan Labreuille
14fe333678 Send confirmation email + page 2016-02-29 13:34:17 +01:00
Jonathan Labreuille
cf6466197a Fixed Subscribers' bulk actions when filtering by a segment
- filter by segment is now affected by the selected group (all, trash,...)
- updated relationship methods between subscribers & segments (to account for subsegment status)
2016-02-29 13:34:17 +01:00
Jonathan Labreuille
f56bee76f2 MailPoet.Date to handle localized dates and times 2016-02-29 13:34:17 +01:00
229 changed files with 10471 additions and 6023 deletions

View File

@@ -89,14 +89,14 @@ class RoboFile extends \Robo\Tasks {
}
function makepot() {
$this->_exec('grunt makepot'.
$this->_exec('./node_modules/.bin/grunt makepot'.
' --gruntfile '.__DIR__.'/tasks/makepot/makepot.js'.
' --base_path '.__DIR__
);
}
function pushpot() {
$this->_exec('grunt pushpot'.
$this->_exec('./node_modules/.bin/grunt pushpot'.
' --gruntfile '.__DIR__.'/tasks/makepot/makepot.js'.
' --base_path '.__DIR__
);
@@ -123,7 +123,7 @@ class RoboFile extends \Robo\Tasks {
$this->compileJs();
$this->_exec(join(' ', array(
'./node_modules/mocha/bin/mocha',
'./node_modules/.bin/mocha',
'-r tests/javascript/mochaTestHelper.js',
'tests/javascript/testBundles/**/*.js'
)));

View File

@@ -563,3 +563,6 @@ handle_icon = '../img/handle.png'
.CodeMirror
border: 1px solid #eee
/* Settings */
#mailpoet_form_segments.parsley-error + span .select2-selection
border: 1px solid #b94a48

View File

@@ -1,4 +1,5 @@
$block-hover-highlight-color = $primary-active-color
$block-text-line-height = $text-line-height
.mailpoet_block
box-sizing: border-box
@@ -30,7 +31,34 @@ $block-hover-highlight-color = $primary-active-color
.mailpoet_content
position: relative
line-height: 1.61803398875
line-height: $block-text-line-height
p, h1, h2, h3, h4, h5, h6
line-height: $block-text-line-height
padding: 0
margin: 0
font-style: normal
font-weight: normal
h1, h2, h3, h4, h5, h6
margin-bottom: 0.3em
p
line-height: 1.61803398875
margin-top: 0
margin-bottom: 0
font-weight: normal
ul
padding: 0
margin-top: 10px
margin-bottom: 10px
li
margin-top: 0
font-weight: normal
margin-bottom: 10px
blockquote
margin: 0 0 $block-text-line-height
padding-left: 10px
border-left: 2px #565656 solid

View File

@@ -1,9 +1,4 @@
$button-vertical-padding = 13px
.mailpoet_button_block
padding-top: $button-vertical-padding
padding-bottom: $button-vertical-padding
overflow: hidden
.mailpoet_editor_button

View File

@@ -6,5 +6,5 @@
.mailpoet_content
padding: 10px 20px
p
margin: 0
& > *:last-child
margin-bottom: 0

View File

@@ -6,5 +6,5 @@
.mailpoet_content
padding: 10px 20px
p
margin: 0
& > *:last-child
margin-bottom: 0

View File

@@ -1,4 +1,4 @@
$social-block-vertical-padding = 11px
$social-block-vertical-padding = 0px
$social-icon-width = 32px
$active-social-icon-set-border-color = #adadad

View File

@@ -9,14 +9,5 @@
padding-left: 20px
padding-right: 20px
h1, h2, h3, h4, h5, h6
padding: 0
margin: 0
p
margin-top: 0
blockquote
margin: 1em
padding-left: 1em
border-left: 2px #565656 solid
& > *:last-child
margin-bottom: 0

View File

@@ -24,3 +24,5 @@ $error-text-color = #d54e21
// Dimensions
$newsletter-width = 660px
$text-line-height = 1.6em

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -48,28 +48,22 @@ define(
})
.done(function(response) {
if(!response.result) {
MailPoet.Notice.error(MailPoetI18n.daemonControlError);
MailPoet.Notice.error(MailPoet.I18n.t('daemonControlError'));
}
}.bind(this));
},
render: function() {
if(this.state.status === 'loading') {
return(<div>Loading daemon status...</div>);
return(<div>{MailPoet.I18n.t('loadingDaemonStatus')}</div>);
}
switch(this.state.status) {
case 'started':
return(
<div>
Cron daemon is running.
{MailPoet.I18n.t('cronDaemonIsRunning')}
<br/>
<br/>
It was started
<strong> {this.state.timeSinceStart} </strong> and last executed
<strong> {this.state.timeSinceUpdate} </strong> for a total of
<strong> {this.state.counter} </strong> times (once every 30 seconds, unless it was interrupted and restarted).
<br />
<br />
<a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'stop')}>Stop</a>
<a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'stop')}>{MailPoet.I18n.t('stop')}</a>
</div>
);
break;
@@ -77,17 +71,17 @@ define(
case 'stopping':
return(
<div>
Daemon is {this.state.status}
{MailPoet.I18n.t('cronDaemonState').replace('%$1s', this.state.status)}
</div>
);
break;
case 'stopped':
return(
<div>
Daemon is {this.state.status}
{MailPoet.I18n.t('cronDaemonState').replace('%$1s', this.state.status)}
<br />
<br />
<a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'start')}>Start</a>
<a href="#" className="button-primary" onClick={this.controlCron.bind(null, 'start')}>{MailPoet.I18n.t('start')}</a>
</div>
);
break;
@@ -103,4 +97,4 @@ define(
container
);
}
});
});

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

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

View File

@@ -61,6 +61,10 @@ function(
case 'date':
field = (<FormFieldDate {...data} />);
break;
case 'reactComponent':
field = (<data.field.component {...data} />);
break;
}
if(inline === true) {
@@ -121,4 +125,4 @@ function(
});
return FormField;
});
});

View File

@@ -13,12 +13,22 @@ function(
getInitialState: function() {
return {
items: [],
initialized: false
}
select2: false
};
},
componentWillMount: function() {
this.loadCachedItems();
},
allowMultipleValues: function() {
return (this.props.field.multiple === true);
},
isSelect2Initialized: function() {
return (this.state.select2 === true);
},
componentDidMount: function() {
this.loadCachedItems();
this.setupSelect2();
if(this.allowMultipleValues()) {
this.setupSelect2();
}
},
componentDidUpdate: function(prevProps, prevState) {
if(
@@ -30,12 +40,18 @@ function(
.trigger('change');
}
},
componentWillUnmount: function() {
if(this.allowMultipleValues()) {
this.destroySelect2();
}
},
destroySelect2: function() {
if(this.isSelect2Initialized()) {
jQuery('#'+this.refs.select.id).select2('destroy');
}
},
setupSelect2: function() {
if(
!this.props.field.multiple
|| this.state.initialized === true
|| this.refs.select === undefined
) {
if(this.isSelect2Initialized()) {
return;
}
@@ -67,12 +83,7 @@ function(
select2.on('change', this.handleChange);
select2.select2(
'val',
this.getSelectedValues()
);
this.setState({ initialized: true });
this.setState({ select2: true });
},
getSelectedValues: function() {
if(this.props.field['selected'] !== undefined) {
@@ -161,4 +172,4 @@ function(
});
return Selection;
});
});

View File

@@ -7,7 +7,10 @@ function(
var FormFieldText = React.createClass({
render: function() {
var value = this.props.item[this.props.field.name];
if(!value) { value = null; }
if(value === undefined) {
value = this.props.field.defaultValue || '';
}
return (
<input
type="text"
@@ -21,7 +24,6 @@ function(
id={ 'field_'+this.props.field.name }
value={ value }
placeholder={ this.props.field.placeholder }
defaultValue={ this.props.field.defaultValue }
onChange={ this.props.onValueChange }
{...this.props.field.validation}
/>
@@ -30,4 +32,4 @@ function(
});
return FormFieldText;
});
});

View File

@@ -13,10 +13,16 @@ define(
Router,
FormField
) {
var Form = React.createClass({
mixins: [
Router.History
],
contextTypes: {
router: React.PropTypes.object.isRequired
},
getDefaultProps: function() {
return {
params: {},
};
},
getInitialState: function() {
return {
loading: false,
@@ -24,6 +30,12 @@ define(
item: {}
};
},
getValues: function() {
return this.props.item ? this.props.item : this.state.item;
},
getErrors: function() {
return this.props.errors ? this.props.errors : this.state.errors;
},
componentDidMount: function() {
if(this.props.params.id !== undefined) {
if(this.isMounted()) {
@@ -37,7 +49,9 @@ define(
loading: false,
item: {}
});
this.refs.form.reset();
if (props.item === undefined) {
this.refs.form.reset();
}
} else {
this.loadItem(props.params.id);
}
@@ -55,7 +69,7 @@ define(
loading: false,
item: {}
}, function() {
this.history.pushState(null, '/new');
this.context.router.push('/new');
}.bind(this));
} else {
this.setState({
@@ -105,7 +119,7 @@ define(
if(this.props.onSuccess !== undefined) {
this.props.onSuccess();
} else {
this.history.pushState(null, '/')
this.context.router.push('/');
}
if(this.props.params.id !== undefined) {
@@ -123,19 +137,23 @@ define(
}.bind(this));
},
handleValueChange: function(e) {
var item = this.state.item,
field = e.target.name;
if (this.props.onChange) {
return this.props.onChange(e);
} else {
var item = this.state.item,
field = e.target.name;
item[field] = e.target.value;
item[field] = e.target.value;
this.setState({
item: item
});
return true;
this.setState({
item: item
});
return true;
}
},
render: function() {
if(this.state.errors !== undefined) {
var errors = this.state.errors.map(function(error, index) {
if(this.getErrors() !== undefined) {
var errors = this.getErrors().map(function(error, index) {
return (
<p key={ 'error-'+index } className="mailpoet_error">
{ error }
@@ -146,14 +164,14 @@ define(
var formClasses = classNames(
'mailpoet_form',
{ 'mailpoet_form_loading': this.state.loading }
{ 'mailpoet_form_loading': this.state.loading || this.props.loading }
);
var fields = this.props.fields.map(function(field, i) {
return (
<FormField
field={ field }
item={ this.state.item }
item={ this.getValues() }
onValueChange={ this.handleValueChange }
key={ 'field-'+i } />
);
@@ -167,7 +185,7 @@ define(
<input
className="button button-primary"
type="submit"
value="Save"
value={MailPoet.I18n.t('save')}
disabled={this.state.loading} />
);
}
@@ -199,4 +217,4 @@ define(
return Form;
}
);
);

View File

@@ -402,11 +402,30 @@ var WysijaForm = {
}
});
// hide list selection if a list widget has been dragged into the editor
$('mailpoet_settings_segment_selection')[
(($$('#' + WysijaForm.options.editor + ' [wysija_id="segments"]').length > 0) === true)
? 'hide' : 'show'
]();
var hasSegmentSelection = WysijaForm.hasSegmentSelection();
if(hasSegmentSelection) {
$('mailpoet_form_segments').writeAttribute('required', false).disable();
$('mailpoet_settings_segment_selection').hide();
} else {
$('mailpoet_form_segments').writeAttribute('required', true).enable();
$('mailpoet_settings_segment_selection').show();
}
},
hasSegmentSelection: function() {
return ($$('#' + WysijaForm.options.editor + ' [wysija_id="segments"]').length > 0);
},
isSegmentSelectionValid: function() {
var segment_selection = $$('#' + WysijaForm.options.editor + ' [wysija_id="segments"]')[0];
if(segment_selection !== undefined) {
var block = WysijaForm.get(segment_selection).block.getData();
return (
(block.params.values !== undefined)
&&
(block.params.values.length > 0)
);
}
return false;
},
setBlockPositions: function(event, target) {
// release dragging lock

View File

@@ -1,10 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, Route, IndexRoute } from 'react-router'
import { Router, Route, IndexRoute, Link, useRouterHistory } from 'react-router'
import { createHashHistory } from 'history'
import FormList from 'forms/list.jsx'
import createHashHistory from 'history/lib/createHashHistory'
let history = createHashHistory({ queryKey: false })
const history = useRouterHistory(createHashHistory)({ queryKey: false });
const App = React.createClass({
render() {
@@ -12,7 +12,7 @@ const App = React.createClass({
}
});
let container = document.getElementById('forms_container');
const container = document.getElementById('forms_container');
if(container) {
ReactDOM.render((

View File

@@ -8,17 +8,17 @@ import MailPoet from 'mailpoet'
const columns = [
{
name: 'name',
label: 'Name',
label: MailPoet.I18n.t('formName'),
sortable: true
},
{
name: 'segments',
label: 'Lists',
label: MailPoet.I18n.t('segments'),
sortable: false
},
{
name: 'created_at',
label: 'Created on',
label: MailPoet.I18n.t('createdOn'),
sortable: true
}
];
@@ -30,11 +30,11 @@ const messages = {
if(count === 1) {
message = (
'1 form was moved to the trash.'
MailPoet.I18n.t('oneFormTrashed')
);
} else {
message = (
'%$1d forms were moved to the trash.'
MailPoet.I18n.t('multipleFormsTrashed')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
@@ -45,11 +45,11 @@ const messages = {
if(count === 1) {
message = (
'1 form was permanently deleted.'
MailPoet.I18n.t('oneFormDeleted')
);
} else {
message = (
'%$1d forms were permanently deleted.'
MailPoet.I18n.t('multipleFormsDeleted')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
@@ -60,11 +60,11 @@ const messages = {
if(count === 1) {
message = (
'1 form has been restored from the trash.'
MailPoet.I18n.t('oneFormRestored')
);
} else {
message = (
'%$1d forms have been restored from the trash.'
MailPoet.I18n.t('multipleFormsRestored')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
@@ -74,16 +74,16 @@ const messages = {
const item_actions = [
{
name: 'edit',
label: 'Edit',
label: MailPoet.I18n.t('edit'),
link: function(item) {
return (
<a href={ `admin.php?page=mailpoet-form-editor&id=${item.id}` }>Edit</a>
<a href={ `admin.php?page=mailpoet-form-editor&id=${item.id}` }>{MailPoet.I18n.t('edit')}</a>
);
}
},
{
name: 'duplicate_form',
label: 'Duplicate',
label: MailPoet.I18n.t('duplicate'),
onClick: function(item, refresh) {
return MailPoet.Ajax.post({
endpoint: 'forms',
@@ -91,7 +91,7 @@ const item_actions = [
data: item.id
}).done(function(response) {
MailPoet.Notice.success(
('Form "%$1s" has been duplicated.').replace('%$1s', response.name)
(MailPoet.I18n.t('formDuplicated')).replace('%$1s', response.name)
);
refresh();
});
@@ -102,14 +102,6 @@ const item_actions = [
}
];
const bulk_actions = [
{
name: 'trash',
label: 'Trash',
onSuccess: messages.onTrash
}
];
const FormList = React.createClass({
createForm() {
MailPoet.Ajax.post({
@@ -138,15 +130,18 @@ const FormList = React.createClass({
<div>
<td className={ row_classes }>
<strong>
<a>{ form.name }</a>
<a
className="row-title"
href={ `admin.php?page=mailpoet-form-editor&id=${form.id}` }
>{ form.name }</a>
</strong>
{ actions }
</td>
<td className="column-format" data-colname="Lists">
<td className="column-format" data-colname={MailPoet.I18n.t('segments')}>
{ segments }
</td>
<td className="column-date" data-colname="Created on">
<abbr>{ form.created_at }</abbr>
<td className="column-date" data-colname={MailPoet.I18n.t('createdOn')}>
<abbr>{ MailPoet.Date.full(form.created_at) }</abbr>
</td>
</div>
);
@@ -155,11 +150,11 @@ const FormList = React.createClass({
return (
<div>
<h2 className="title">
Forms <a
{MailPoet.I18n.t('pageTitle')} <a
className="add-new-h2"
href="javascript:;"
onClick={ this.createForm }
>New</a>
>{MailPoet.I18n.t('new')}</a>
</h2>
<Listing
@@ -171,7 +166,6 @@ const FormList = React.createClass({
endpoint="forms"
onRenderItem={ this.renderItem }
columns={ columns }
bulk_actions={ bulk_actions }
item_actions={ item_actions }
/>
</div>
@@ -179,4 +173,4 @@ const FormList = React.createClass({
}
});
module.exports = FormList;
module.exports = FormList;

View File

@@ -140,5 +140,20 @@ define('handlebars_helpers', ['handlebars'], function(Handlebars) {
return parseInt(string, 10);
});
Handlebars.registerHelper('fontWithFallback', function(font) {
switch(font) {
case 'Arial': return new Handlebars.SafeString("Arial, 'Helvetica Neue', Helvetica, sans-serif");
case 'Comic Sans MS': return new Handlebars.SafeString("'Comic Sans MS', 'Marker Felt-Thin', Arial, sans-serif");
case 'Courier New': return new Handlebars.SafeString("'Courier New', Courier, 'Lucida Sans Typewriter', 'Lucida Typewriter', monospace");
case 'Georgia': return new Handlebars.SafeString("Georgia, Times, 'Times New Roman', serif");
case 'Lucida': return new Handlebars.SafeString("'Lucida Sans Unicode', 'Lucida Grande', sans-serif");
case 'Tahoma': return new Handlebars.SafeString("Tahoma, Verdana, Segoe, sans-serif");
case 'Times New Roman': return new Handlebars.SafeString("'Times New Roman', Times, Baskerville, Georgia, serif");
case 'Trebuchet MS': return new Handlebars.SafeString("'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif");
case 'Verdana': return new Handlebars.SafeString("Verdana, Geneva, sans-serif");
default: return font;
}
});
window.Handlebars = Handlebars;
});

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

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

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

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

View File

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

View File

@@ -1,10 +1,12 @@
define([
'react',
'jquery'
'jquery',
'mailpoet'
],
function(
React,
jQuery
jQuery,
MailPoet
) {
var ListingFilters = React.createClass({
handleFilterAction: function() {
@@ -69,7 +71,7 @@ function(
id="post-query-submit"
onClick={ this.handleFilterAction }
type="submit"
defaultValue="Filter"
defaultValue={MailPoet.I18n.t('filter')}
className="button" />
);
}
@@ -80,7 +82,7 @@ function(
<input
onClick={ this.handleEmptyTrash }
type="submit"
value="Empty Trash"
value={MailPoet.I18n.t('emptyTrash')}
className="button"
/>
);

View File

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

View File

@@ -98,7 +98,7 @@ define(
null,
this.props.item.id
) }>
Trash
{MailPoet.I18n.t('trash')}
</a>
</span>
);
@@ -145,7 +145,7 @@ define(
} else {
item_actions = (
<span className="edit">
<Link to={ `/edit/${ this.props.item.id }` }>Edit</Link>
<Link to={ `/edit/${ this.props.item.id }` }>{MailPoet.I18n.t('edit')}</Link>
</span>
);
}
@@ -161,7 +161,7 @@ define(
null,
this.props.item.id
)}
>Restore</a>
>{MailPoet.I18n.t('restore')}</a>
</span>
{ ' | ' }
<span className="delete">
@@ -172,13 +172,13 @@ define(
null,
this.props.item.id
)}
>Delete permanently</a>
>{MailPoet.I18n.t('deletePermanently')}</a>
</span>
</div>
<button
onClick={ this.handleToggleItem.bind(null, this.props.item.id) }
className="toggle-row" type="button">
<span className="screen-reader-text">Show more details</span>
<span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span>
</button>
</div>
);
@@ -191,7 +191,7 @@ define(
<button
onClick={ this.handleToggleItem.bind(null, this.props.item.id) }
className="toggle-row" type="button">
<span className="screen-reader-text">Show more details</span>
<span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span>
</button>
</div>
);
@@ -223,8 +223,8 @@ define(
className="colspanchange">
{
(this.props.loading === true)
? MailPoetI18n.loadingItems
: MailPoetI18n.noItemsFound
? MailPoet.I18n.t('loadingItems')
: MailPoet.I18n.t('noItemsFound')
}
</td>
</tr>
@@ -250,8 +250,8 @@ define(
}>
{
(this.props.selection !== 'all')
? MailPoetI18n.selectAllLabel
: MailPoetI18n.selectedAllLabel.replace(
? MailPoet.I18n.t('selectAllLabel')
: MailPoet.I18n.t('selectedAllLabel').replace(
'%d',
this.props.count
)
@@ -261,8 +261,8 @@ define(
onClick={ this.props.onSelectAll }
href="javascript:;">{
(this.props.selection !== 'all')
? MailPoetI18n.selectAllLink
: MailPoetI18n.clearSelection
? MailPoet.I18n.t('selectAllLink')
: MailPoet.I18n.t('clearSelection')
}</a>
</td>
</tr>
@@ -295,9 +295,9 @@ define(
});
var Listing = React.createClass({
mixins: [
Router.History
],
contextTypes: {
router: React.PropTypes.object.isRequired
},
getInitialState: function() {
return {
loading: false,
@@ -359,9 +359,12 @@ define(
}
})
}
// default overrides
if(this.props.limit !== undefined) {
state.limit = Math.abs(~~this.props.limit);
}
this.setState(state, function() {
this.getItems();
}.bind(this));
@@ -398,7 +401,7 @@ define(
if(this.props.location) {
if(this.props.location.pathname !== params) {
this.history.pushState(null, `${params}`)
this.context.router.push(`${params}`);
}
}
},
@@ -519,7 +522,7 @@ define(
group: 'trash'
}, function(response) {
MailPoet.Notice.success(
MailPoetI18n.permanentlyDeleted.replace('%d', response)
MailPoet.I18n.t('permanentlyDeleted').replace('%d', response)
);
});
},
@@ -678,12 +681,12 @@ define(
bulk_actions = [
{
name: 'restore',
label: 'Restore',
label: MailPoet.I18n.t('restore'),
onSuccess: this.props.messages.onRestore
},
{
name: 'delete',
label: 'Delete permanently',
label: MailPoet.I18n.t('deletePermanently'),
onSuccess: this.props.messages.onDelete
}
];
@@ -753,8 +756,8 @@ define(
onSort={ this.handleSort }
onSelectItems={ this.handleSelectItems }
selection={ this.state.selection }
sort_by={ this.state.sort_by }
sort_order={ this.state.sort_order }
sort_by={ sort_by }
sort_order={ sort_order }
columns={ this.props.columns }
is_selectable={ bulk_actions.length > 0 } />
</thead>
@@ -783,8 +786,8 @@ define(
onSort={ this.handleSort }
onSelectItems={ this.handleSelectItems }
selection={ this.state.selection }
sort_by={ this.state.sort_by }
sort_order={ this.state.sort_order }
sort_by={ sort_by }
sort_order={ sort_order }
columns={ this.props.columns }
is_selectable={ bulk_actions.length > 0 } />
</tfoot>

View File

@@ -1,4 +1,12 @@
define(['react', 'classnames'], function(React, classNames) {
define([
'react',
'classnames',
'mailpoet'
], function(
React,
classNames,
MailPoet
) {
var ListingPages = React.createClass({
getInitialState: function() {
@@ -72,7 +80,7 @@ define(['react', 'classnames'], function(React, classNames) {
<a href="javascript:;"
onClick={ this.setPreviousPage }
className="prev-page">
<span className="screen-reader-text">Previous page</span>
<span className="screen-reader-text">{MailPoet.I18n.t('previousPage')}</span>
<span aria-hidden="true"></span>
</a>
);
@@ -83,7 +91,7 @@ define(['react', 'classnames'], function(React, classNames) {
<a href="javascript:;"
onClick={ this.setFirstPage }
className="first-page">
<span className="screen-reader-text">First page</span>
<span className="screen-reader-text">{MailPoet.I18n.t('firstPage')}</span>
<span aria-hidden="true">«</span>
</a>
);
@@ -94,7 +102,7 @@ define(['react', 'classnames'], function(React, classNames) {
<a href="javascript:;"
onClick={ this.setNextPage }
className="next-page">
<span className="screen-reader-text">Next page</span>
<span className="screen-reader-text">{MailPoet.I18n.t('nextPage')}</span>
<span aria-hidden="true"></span>
</a>
);
@@ -105,7 +113,7 @@ define(['react', 'classnames'], function(React, classNames) {
<a href="javascript:;"
onClick={ this.setLastPage }
className="last-page">
<span className="screen-reader-text">Last page</span>
<span className="screen-reader-text">{MailPoet.I18n.t('lastPage')}</span>
<span aria-hidden="true">»</span>
</a>
);
@@ -125,20 +133,20 @@ define(['react', 'classnames'], function(React, classNames) {
<span className="paging-input">
<label
className="screen-reader-text"
htmlFor="current-page-selector">Current Page</label>
htmlFor="current-page-selector">{MailPoet.I18n.t('currentPage')}</label>
<input
type="text"
onChange={ this.handleChangeManualPage }
onKeyUp={ this.handleSetManualPage }
onBlur={ this.handleBlurManualPage }
aria-describedby="table-paging"
size="1"
size="2"
ref="page"
value={ pageValue }
name="paged"
id="current-page-selector"
className="current-page" />
&nbsp;of&nbsp;
&nbsp;{MailPoet.I18n.t('pageOutOf')}&nbsp;
<span className="total-pages">
{Math.ceil(this.props.count / this.props.limit)}
</span>
@@ -158,7 +166,9 @@ define(['react', 'classnames'], function(React, classNames) {
return (
<div className={ classes }>
<span className="displaying-num">{ this.props.count } items</span>
<span className="displaying-num">{
MailPoet.I18n.t('numberOfItems').replace('%$1d', this.props.count)
}</span>
{ pagination }
</div>
);
@@ -167,4 +177,4 @@ define(['react', 'classnames'], function(React, classNames) {
});
return ListingPages;
});
});

View File

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

View File

@@ -12,9 +12,19 @@ define([
'newsletter_editor/blocks/button',
'newsletter_editor/blocks/divider',
'newsletter_editor/components/communication',
'mailpoet',
'underscore',
'jquery'
], function(App, BaseBlock, ButtonBlock, DividerBlock, CommunicationComponent, _, jQuery) {
], function(
App,
BaseBlock,
ButtonBlock,
DividerBlock,
CommunicationComponent,
MailPoet,
_,
jQuery
) {
"use strict";
@@ -73,7 +83,7 @@ define([
that.get('_container').get('blocks').reset(content, {parse: true});
that.trigger('postsChanged');
}).fail(function(error) {
console.log('ALC fetchPosts error', arguments);
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts'));
});
},
/**

View File

@@ -16,7 +16,7 @@ define([
defaults: function() {
return this._getDefaults({
type: 'footer',
text: '<a href="[unsubscribeUrl]">Unsubscribe</a> | <a href="[manageSubscriptionUrl]">Manage subscription</a><br /><b>Add your postal address here!</b>',
text: '<a href="[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',
@@ -79,7 +79,7 @@ define([
},
mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(),
mailpoet_custom_fields_window_title: App.getConfig().get('translations.customFieldsWindowTitle'),
mailpoet_custom_fields_window_title: MailPoet.I18n.t('customFieldsWindowTitle'),
});
},
});

View File

@@ -16,7 +16,7 @@ define([
defaults: function() {
return this._getDefaults({
type: 'header',
text: 'Display problems? <a href="[viewInBrowserUrl]">View it in your browser</a>',
text: 'Display problems? <a href="[link:newsletter_view_in_browser_url]">View it in your browser</a>',
styles: {
block: {
backgroundColor: 'transparent',
@@ -79,7 +79,7 @@ define([
},
mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(),
mailpoet_custom_fields_window_title: App.getConfig().get('translations.customFieldsWindowTitle'),
mailpoet_custom_fields_window_title: MailPoet.I18n.t('customFieldsWindowTitle'),
});
},
});

View File

@@ -101,7 +101,7 @@ define([
that.get('_selectedPosts').reset(); // Empty out the collection
that.trigger('change:_availablePosts');
}).fail(function() {
console.log('Posts fetchPosts error', arguments);
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchAvailablePosts'));
});
},
_refreshTransformedPosts: function() {
@@ -118,7 +118,7 @@ define([
CommunicationComponent.getTransformedPosts(data).done(function(posts) {
that.get('_transformedPosts').get('blocks').reset(posts, {parse: true});
}).fail(function() {
console.log('Posts _refreshTransformedPosts error', arguments);
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts'));
});
},
_insertSelectedPosts: function() {
@@ -134,7 +134,7 @@ define([
CommunicationComponent.getTransformedPosts(data).done(function(posts) {
collection.add(posts, { at: index });
}).fail(function() {
console.log('Posts fetchPosts error', arguments);
MailPoet.Notice.error(MailPoet.I18n.t('failedToFetchRenderedPosts'));
});
},
});

View File

@@ -77,7 +77,7 @@ define([
},
mailpoet_custom_fields: App.getConfig().get('customFields').toJSON(),
mailpoet_custom_fields_window_title: App.getConfig().get('translations.customFieldsWindowTitle'),
mailpoet_custom_fields_window_title: MailPoet.I18n.t('customFieldsWindowTitle'),
});
}
},

View File

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

View File

@@ -191,7 +191,7 @@ define([
if (templateName === '') {
MailPoet.Notice.error(
App.getConfig().get('translations.templateNameMissing'),
MailPoet.I18n.t('templateNameMissing'),
{
positionAfter: that.$el,
scroll: true,
@@ -199,30 +199,27 @@ define([
);
} else if (templateDescription === '') {
MailPoet.Notice.error(
App.getConfig().get('translations.templateDescriptionMissing'),
MailPoet.I18n.t('templateDescriptionMissing'),
{
positionAfter: that.$el,
scroll: true,
}
);
} else {
console.log('Saving template with ', templateName, templateDescription);
Module.saveTemplate({
name: templateName,
description: templateDescription,
}).done(function() {
console.log('Template saved', arguments);
MailPoet.Notice.success(
App.getConfig().get('translations.templateSaved'),
MailPoet.I18n.t('templateSaved'),
{
positionAfter: that.$el,
scroll: true,
}
);
}).fail(function() {
console.log('Template save failed', arguments);
MailPoet.Notice.error(
App.getConfig().get('translations.templateSaveFailed'),
MailPoet.I18n.t('templateSaveFailed'),
{
positionAfter: that.$el,
scroll: true,
@@ -247,7 +244,7 @@ define([
if (templateName === '') {
MailPoet.Notice.error(
App.getConfig().get('translations.templateNameMissing'),
MailPoet.I18n.t('templateNameMissing'),
{
positionAfter: that.$el,
scroll: true,
@@ -255,14 +252,13 @@ define([
);
} else if (templateDescription === '') {
MailPoet.Notice.error(
App.getConfig().get('translations.templateDescriptionMissing'),
MailPoet.I18n.t('templateDescriptionMissing'),
{
positionAfter: that.$el,
scroll: true,
}
);
} else {
console.log('Exporting template with ', templateName, templateDescription);
Module.exportTemplate({
name: templateName,
description: templateDescription,
@@ -278,7 +274,6 @@ define([
next: function() {
this.hideOptionContents();
if(!this.$('.mailpoet_save_next').hasClass('button-disabled')) {
console.log('Next');
window.location.href = App.getConfig().get('urls.send');
}
},
@@ -289,8 +284,8 @@ define([
}
if (App.getConfig().get('validation.validateUnsubscribeLinkPresent') &&
JSON.stringify(jsonObject).indexOf("[unsubscribeUrl]") < 0) {
this.showValidationError(App.getConfig().get('translations.unsubscribeLinkMissing'));
JSON.stringify(jsonObject).indexOf("[link:subscription_unsubscribe_url]") < 0) {
this.showValidationError(MailPoet.I18n.t('unsubscribeLinkMissing'));
return;
}

View File

@@ -1,13 +1,24 @@
define([
'newsletter_editor/App',
'newsletter_editor/components/communication',
'mailpoet',
'backbone',
'backbone.marionette',
'backbone.supermodel',
'underscore',
'jquery',
'sticky-kit'
], function(App, CommunicationComponent, Backbone, Marionette, SuperModel, _, jQuery, StickyKit) {
], function(
App,
CommunicationComponent,
MailPoet,
Backbone,
Marionette,
SuperModel,
_,
jQuery,
StickyKit
) {
"use strict";
@@ -230,33 +241,51 @@ define([
json.body = JSON.stringify(json.body);
}
MailPoet.Modal.loading(true);
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'render',
action: 'showPreview',
data: json,
}).done(function(response){
console.log('Should open a new window');
window.open('data:text/html;charset=utf-8,' + encodeURIComponent(response.rendered_body), '_blank');
MailPoet.Modal.loading(false);
if (response.result === true) {
window.open(response.data.url, '_blank')
}
MailPoet.Notice.error(response.errors);
}).fail(function(error) {
console.log('Preview error', json);
alert('Something went wrong, check console');
MailPoet.Modal.loading(false);
MailPoet.Notice.error(
MailPoet.I18n.t('newsletterPreviewFailed')
);
});
},
sendPreview: function() {
// testing sending method
console.log('trying to send a preview');
// get form data
var $emailField = this.$('#mailpoet_preview_to_email');
var data = {
subscriber: this.$('#mailpoet_preview_to_email').val(),
subscriber: $emailField.val(),
id: App.getNewsletter().get('id'),
};
if (data.subscriber.length <= 0) {
MailPoet.Notice.error(
MailPoet.I18n.t('newsletterPreviewEmailMissing'),
{
positionAfter: $emailField,
scroll: true,
}
);
return false;
}
// send test email
MailPoet.Modal.loading(true);
CommunicationComponent.previewNewsletter(data).done(function(response) {
if(response.result !== undefined && response.result === true) {
MailPoet.Notice.success(App.getConfig().get('translations.newsletterPreviewSent'), { scroll: true });
MailPoet.Notice.success(MailPoet.I18n.t('newsletterPreviewSent'), { scroll: true });
} else {
if (_.isArray(response.errors)) {
response.errors.map(function(error) {
@@ -264,7 +293,7 @@ define([
});
} else {
MailPoet.Notice.error(
App.getConfig().get('translations.newsletterPreviewFailedToSend'),
MailPoet.I18n.t('newsletterPreviewFailedToSend'),
{
scroll: true,
static: true,

View File

@@ -2,39 +2,38 @@ define(
[
'react',
'react-router',
'classnames'
'classnames',
'mailpoet'
],
function(
React,
Router,
classNames
classNames,
MailPoet
) {
var Link = Router.Link;
var Breadcrumb = React.createClass({
mixins: [
Router.History
],
getInitialState: function() {
return {
step: null,
steps: [
{
name: 'type',
label: 'Select type',
label: MailPoet.I18n.t('selectType'),
link: '/new'
},
{
name: 'template',
label: 'Template'
label: MailPoet.I18n.t('template')
},
{
name: 'editor',
label: 'Designer'
label: MailPoet.I18n.t('designer')
},
{
name: 'send',
label: 'Send'
label: MailPoet.I18n.t('send')
}
]
};
@@ -73,4 +72,4 @@ define(
return Breadcrumb;
}
);
);

View File

@@ -4,39 +4,45 @@ define(
'react-router',
'listing/listing.jsx',
'classnames',
'jquery'
'jquery',
'mailpoet'
],
function(
React,
Router,
Listing,
classNames,
jQuery
jQuery,
MailPoet
) {
var Link = Router.Link;
var columns = [
{
name: 'subject',
label: 'Subject',
label: MailPoet.I18n.t('subject'),
sortable: true
},
{
name: 'status',
label: 'Status'
label: MailPoet.I18n.t('status')
},
{
name: 'segments',
label: 'Lists'
label: MailPoet.I18n.t('lists')
},
{
name: 'statistics',
label: MailPoet.I18n.t('statistics')
},
{
name: 'created_at',
label: 'Created on',
label: MailPoet.I18n.t('createdOn'),
sortable: true
},
{
name: 'updated_at',
label: 'Last modified on',
label: MailPoet.I18n.t('lastModifiedOn'),
sortable: true
}
];
@@ -48,11 +54,11 @@ define(
if(count === 1) {
message = (
'1 newsletter was moved to the trash.'
MailPoet.I18n.t('oneNewsletterTrashed')
);
} else {
message = (
'%$1d newsletters were moved to the trash.'
MailPoet.I18n.t('multipleNewslettersTrashed')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
@@ -63,11 +69,11 @@ define(
if(count === 1) {
message = (
'1 newsletter was permanently deleted.'
MailPoet.I18n.t('oneNewsletterDeleted')
);
} else {
message = (
'%$1d newsletters were permanently deleted.'
MailPoet.I18n.t('multipleNewslettersDeleted')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
@@ -78,11 +84,11 @@ define(
if(count === 1) {
message = (
'1 newsletter has been restored from the trash.'
MailPoet.I18n.t('oneNewsletterRestored')
);
} else {
message = (
'%$1d newsletters have been restored from the trash.'
MailPoet.I18n.t('multipleNewslettersRestored')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
@@ -92,7 +98,7 @@ define(
var bulk_actions = [
{
name: 'trash',
label: 'Trash',
label: MailPoet.I18n.t('trash'),
onSuccess: messages.onTrash
}
];
@@ -103,7 +109,7 @@ define(
link: function(item) {
return (
<a href={ `?page=mailpoet-newsletter-editor&id=${ item.id }` }>
Edit
{MailPoet.I18n.t('edit')}
</a>
);
}
@@ -135,11 +141,16 @@ define(
});
},
renderStatus: function(item) {
if(item.queue === null) {
if(!item.queue) {
return (
<span>Not sent yet.</span>
<span>{MailPoet.I18n.t('notSentYet')}</span>
);
} else {
if (item.queue.status === 'scheduled') {
return (
<span>{MailPoet.I18n.t('scheduledFor')} { item.queue.scheduled_at } </span>
)
}
var progressClasses = classNames(
'mailpoet_progress',
{ 'mailpoet_progress_complete': item.queue.status === 'completed'}
@@ -155,9 +166,11 @@ define(
if(item.queue.status === 'completed') {
label = (
<span>
Sent to {
item.queue.count_processed - item.queue.count_failed
} out of { item.queue.count_total }.
{
MailPoet.I18n.t('newsletterQueueCompleted')
.replace("%$1d", item.queue.count_processed - item.queue.count_failed)
.replace("%$2d", item.queue.count_total)
}
</span>
);
} else {
@@ -171,14 +184,14 @@ define(
style={{ display: (item.queue.status === 'paused') ? 'inline-block': 'none' }}
href="javascript:;"
onClick={ this.resumeSending.bind(null, item) }
>Resume</a>
>{MailPoet.I18n.t('resume')}</a>
<a
id={ 'pause_'+item.id }
className="button mailpoet_pause"
style={{ display: (item.queue.status === null) ? 'inline-block': 'none' }}
href="javascript:;"
onClick={ this.pauseSending.bind(null, item) }
>Pause</a>
>{MailPoet.I18n.t('pause')}</a>
</span>
);
}
@@ -201,6 +214,31 @@ define(
);
}
},
renderStatistics: function(item) {
if(!item.statistics || !item.queue || item.queue.count_processed == 0 || item.queue.status === 'scheduled') {
return (
<span>
{MailPoet.I18n.t('notSentYet')}
</span>
);
}
var percentage_clicked = Math.round(
(item.statistics.clicked * 100) / (item.queue.count_processed)
);
var percentage_opened = Math.round(
(item.statistics.opened * 100) / (item.queue.count_processed)
);
var percentage_unsubscribed = Math.round(
(item.statistics.unsubscribed * 100) / (item.queue.count_processed)
);
return (
<span>
{ percentage_opened }%, { percentage_clicked }%, { percentage_unsubscribed }%
</span>
);
},
renderItem: function(newsletter, actions) {
var rowClasses = classNames(
'manage-column',
@@ -208,12 +246,17 @@ define(
'has-row-actions'
);
var segments = mailpoet_segments.filter(function(segment) {
return (jQuery.inArray(segment.id, newsletter.segments) !== -1);
}).map(function(segment) {
return segment.name;
var segments = newsletter.segments.map(function(segment) {
return segment.name
}).join(', ');
var statistics_column =
(!mailpoet_settings.tracking || !mailpoet_settings.tracking.enabled) ?
false :
<td className="column {statistics_class}" data-colname="Statistics">
{ this.renderStatistics(newsletter) }
</td>;
return (
<div>
<td className={ rowClasses }>
@@ -222,26 +265,30 @@ define(
</strong>
{ actions }
</td>
<td className="column" data-colname="Lists">
<td className="column" data-colname="Status">
{ this.renderStatus(newsletter) }
</td>
<td className="column" data-colname="Lists">
{ segments }
</td>
{ statistics_column }
<td className="column-date" data-colname="Subscribed on">
<abbr>{ newsletter.created_at }</abbr>
<abbr>{ MailPoet.Date.full(newsletter.created_at) }</abbr>
</td>
<td className="column-date" data-colname="Last modified on">
<abbr>{ newsletter.updated_at }</abbr>
<abbr>{ MailPoet.Date.full(newsletter.updated_at) }</abbr>
</td>
</div>
);
},
render: function() {
if (!mailpoet_settings.tracking || !mailpoet_settings.tracking.enabled) {
columns = _.without(columns, _.findWhere(columns, {name: 'statistics'}));
}
return (
<div>
<h2 className="title">
Newsletters <Link className="add-new-h2" to="/new">New</Link>
{MailPoet.I18n.t('pageTitle')} <Link className="add-new-h2" to="/new">{MailPoet.I18n.t('new')}</Link>
</h2>
<Listing
@@ -260,4 +307,4 @@ define(
return NewsletterList;
}
);
);

View File

@@ -1,16 +1,16 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, Route, IndexRoute, Link } from 'react-router'
import { Router, Route, IndexRoute, Link, useRouterHistory } from 'react-router'
import { createHashHistory } from 'history'
import NewsletterList from 'newsletters/list.jsx'
import NewsletterTypes from 'newsletters/types.jsx'
import NewsletterTemplates from 'newsletters/templates.jsx'
import NewsletterSend from 'newsletters/send.jsx'
import NewsletterStandard from 'newsletters/types/standard.jsx'
import NewsletterWelcome from 'newsletters/types/welcome.jsx'
import NewsletterNotification from 'newsletters/types/notification.jsx'
import createHashHistory from 'history/lib/createHashHistory'
import NewsletterWelcome from 'newsletters/types/welcome/welcome.jsx'
import NewsletterNotification from 'newsletters/types/notification/notification.jsx'
let history = createHashHistory({ queryKey: false })
const history = useRouterHistory(createHashHistory)({ queryKey: false });
const App = React.createClass({
render() {
@@ -18,7 +18,7 @@ const App = React.createClass({
}
});
let container = document.getElementById('newsletters_container');
const container = document.getElementById('newsletters_container');
if(container) {
ReactDOM.render((

View File

@@ -2,190 +2,194 @@ define(
[
'react',
'react-router',
'underscore',
'mailpoet',
'form/form.jsx',
'form/fields/selection.jsx',
'newsletters/send/standard.jsx',
'newsletters/send/notification.jsx',
'newsletters/send/welcome.jsx',
'newsletters/breadcrumb.jsx'
],
function(
React,
Router,
_,
MailPoet,
Form,
Selection,
StandardNewsletterFields,
NotificationNewsletterFields,
WelcomeNewsletterFields,
Breadcrumb
) {
var settings = window.mailpoet_settings || {};
var fields = [
{
name: 'subject',
label: 'Subject line',
tip: "Be creative! It's the first thing your subscribers see."+
"Tempt them to open your email.",
type: 'text',
validation: {
'data-parsley-required': true,
'data-parsley-required-message': 'You need to specify a subject.'
}
},
{
name: 'segments',
label: 'Segments',
tip: "The subscriber segment that will be used for this campaign.",
type: 'selection',
placeholder: "Select a segment",
id: "mailpoet_segments",
endpoint: "segments",
multiple: true,
filter: function(segment) {
return !!(!segment.deleted_at);
},
validation: {
'data-parsley-required': true,
'data-parsley-required-message': 'You need to select a segment.'
}
},
{
name: 'sender',
label: 'Sender',
tip: "Name & email of yourself or your company.",
fields: [
{
name: 'sender_name',
type: 'text',
placeholder: 'John Doe',
defaultValue: (settings.sender !== undefined) ? settings.sender.name : '',
validation: {
'data-parsley-required': true
}
},
{
name: 'sender_address',
type: 'text',
placeholder: 'john.doe@email.com',
defaultValue: (settings.sender !== undefined) ? settings.sender.address : '',
validation: {
'data-parsley-required': true,
'data-parsley-type': 'email'
}
}
]
},
{
name: 'reply-to',
label: 'Reply-to',
tip: 'When the subscribers hit "reply" this is who will receive their '+
'email.',
inline: true,
fields: [
{
name: 'reply_to_name',
type: 'text',
placeholder: 'John Doe',
defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.name : '',
},
{
name: 'reply_to_address',
type: 'text',
placeholder: 'john.doe@email.com',
defaultValue: (settings.reply_to !== undefined) ? settings.reply_to.address : ''
},
]
}
];
var messages = {
onUpdate: function() {
MailPoet.Notice.success('Newsletter successfully updated!');
},
onCreate: function() {
MailPoet.Notice.success('Newsletter successfully added!');
}
};
var NewsletterSend = React.createClass({
mixins: [
Router.History
],
componentDidMount: function() {
jQuery('#mailpoet_newsletter').parsley();
contextTypes: {
router: React.PropTypes.object.isRequired
},
getInitialState: function() {
return {
fields: [],
item: {},
loading: false,
};
},
getFieldsByNewsletter: function(newsletter) {
switch(newsletter.type) {
case 'notification': return NotificationNewsletterFields;
case 'welcome': return WelcomeNewsletterFields;
default: return StandardNewsletterFields;
}
},
isAutomatedNewsletter: function() {
return this.state.item.type !== 'standard';
},
isValid: function() {
return jQuery('#mailpoet_newsletter').parsley().isValid();
},
handleSend: function() {
componentDidMount: function() {
if(this.isMounted()) {
this.loadItem(this.props.params.id);
}
jQuery('#mailpoet_newsletter').parsley();
},
componentWillReceiveProps: function(props) {
this.loadItem(props.params.id);
},
loadItem: function(id) {
this.setState({ loading: true });
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'get',
data: id
}).done((response) => {
if(response === false) {
this.setState({
loading: false,
item: {},
}, function() {
this.context.router.push('/new');
}.bind(this));
} else {
this.setState({
loading: false,
item: response,
fields: this.getFieldsByNewsletter(response),
});
}
});
},
handleSend: function(e) {
e.preventDefault();
if(!this.isValid()) {
jQuery('#mailpoet_newsletter').parsley().validate();
} else {
this.setState({ loading: true });
MailPoet.Ajax.post({
endpoint: 'sendingQueue',
action: 'add',
data: {
newsletter_id: this.props.params.id,
segments: jQuery('#mailpoet_segments').val(),
sender: {
'name': jQuery('#mailpoet_newsletter [name="sender_name"]').val(),
'address': jQuery('#mailpoet_newsletter [name="sender_address"]').val()
},
reply_to: {
'name': jQuery('#mailpoet_newsletter [name="reply_to_name"]').val(),
'address': jQuery('#mailpoet_newsletter [name="reply_to_address"]').val()
}
endpoint: 'newsletters',
action: 'save',
data: this.state.item,
}).then((response) => {
if (response.result === true) {
return MailPoet.Ajax.post({
endpoint: 'sendingQueue',
action: 'add',
data: _.extend({}, this.state.item, {
newsletter_id: this.props.params.id,
}),
});
} else {
return response;
}
}).done(function(response) {
}).done((response) => {
this.setState({ loading: false });
if(response.result === true) {
this.history.pushState(null, '/');
MailPoet.Notice.success(
'The newsletter is being sent...'
);
this.context.router.push('/');
MailPoet.Notice.success(response.data.message);
} else {
if(response.errors) {
MailPoet.Notice.error(response.errors);
} else {
MailPoet.Notice.error(
'An error occurred while trying to send. '+
'<a href="?page=mailpoet-settings">Check your settings.</a>'
MailPoet.I18n.t('newsletterSendingError').replace("%$1s", '?page=mailpoet-settings')
);
}
}
}.bind(this));
});
}
return false;
},
handleSave: function(e) {
e.preventDefault();
this.setState({ loading: true });
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'save',
data: this.state.item,
}).done((response) => {
this.setState({ loading: false });
if(response.result === true) {
this.context.router.push('/');
MailPoet.Notice.success(
MailPoet.I18n.t('newsletterUpdated')
);
} else {
if(response.errors) {
MailPoet.Notice.error(response.errors);
}
}
});
},
handleFormChange: function(e) {
var item = this.state.item,
field = e.target.name;
item[field] = e.target.value;
this.setState({
item: item
});
return true;
},
render: function() {
return (
<div>
<h1>Final step: last details</h1>
<h1>{MailPoet.I18n.t('finalNewsletterStep')}</h1>
<Breadcrumb step="send" />
<Form
id="mailpoet_newsletter"
endpoint="newsletters"
fields={ fields }
params={ this.props.params }
messages={ messages }
isValid={ this.isValid }
fields={ this.state.fields }
item={ this.state.item }
loading={ this.state.loading }
onChange={this.handleFormChange}
onSubmit={this.handleSave}
>
<p className="submit">
<input
className="button button-primary"
type="button"
onClick={ this.handleSend }
value="Send" />
value={
this.isAutomatedNewsletter()
? MailPoet.I18n.t('activate')
: MailPoet.I18n.t('send')} />
&nbsp;
<input
className="button button-secondary"
type="submit"
value="Save as draft and close" />
&nbsp;or simply&nbsp;
value={MailPoet.I18n.t('saveDraftAndClose')} />
&nbsp;{MailPoet.I18n.t('orSimply')}&nbsp;
<a
href={
'?page=mailpoet-newsletter-editor&id='+this.props.params.id
}>
go back to design
{MailPoet.I18n.t('goBackToDesign')}
</a>.
</p>
</Form>
@@ -196,4 +200,4 @@ define(
return NewsletterSend;
}
);
);

View File

@@ -0,0 +1,100 @@
define(
[
'mailpoet',
'newsletters/types/notification/scheduling.jsx'
],
function(
MailPoet,
Scheduling
) {
var settings = window.mailpoet_settings || {};
var fields = [
{
name: 'subject',
label: MailPoet.I18n.t('subjectLine'),
tip: MailPoet.I18n.t('postNotificationSubjectLineTip'),
type: 'text',
validation: {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('emptySubjectLineError')
}
},
{
name: 'options',
label: MailPoet.I18n.t('selectPeriodicity'),
type: 'reactComponent',
component: Scheduling,
},
{
name: 'segments',
label: MailPoet.I18n.t('segments'),
tip: MailPoet.I18n.t('segmentsTip'),
type: 'selection',
placeholder: MailPoet.I18n.t('selectSegmentPlaceholder'),
id: "mailpoet_segments",
endpoint: "segments",
multiple: true,
filter: function(segment) {
return !!(!segment.deleted_at);
},
getLabel: function(segment) {
var name = segment.name;
if (segment.subscribers > 0) {
name += ' (%$1s)'.replace('%$1s', segment.subscribers);
}
return name;
},
validation: {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('noSegmentsSelectedError')
}
},
{
name: 'sender',
label: MailPoet.I18n.t('sender'),
tip: MailPoet.I18n.t('senderTip'),
fields: [
{
name: 'sender_name',
type: 'text',
placeholder: MailPoet.I18n.t('senderNamePlaceholder'),
validation: {
'data-parsley-required': true
}
},
{
name: 'sender_address',
type: 'text',
placeholder: MailPoet.I18n.t('senderAddressPlaceholder'),
validation: {
'data-parsley-required': true,
'data-parsley-type': 'email'
}
}
]
},
{
name: 'reply-to',
label: MailPoet.I18n.t('replyTo'),
tip: MailPoet.I18n.t('replyToTip'),
inline: true,
fields: [
{
name: 'reply_to_name',
type: 'text',
placeholder: MailPoet.I18n.t('replyToNamePlaceholder')
},
{
name: 'reply_to_address',
type: 'text',
placeholder: MailPoet.I18n.t('replyToAddressPlaceholder')
}
]
}
];
return fields;
}
);

View File

@@ -0,0 +1,92 @@
define(
[
'mailpoet'
],
function(
MailPoet
) {
var settings = window.mailpoet_settings || {};
var fields = [
{
name: 'subject',
label: MailPoet.I18n.t('subjectLine'),
tip: MailPoet.I18n.t('subjectLineTip'),
type: 'text',
validation: {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('emptySubjectLineError')
}
},
{
name: 'segments',
label: MailPoet.I18n.t('segments'),
tip: MailPoet.I18n.t('segmentsTip'),
type: 'selection',
placeholder: MailPoet.I18n.t('selectSegmentPlaceholder'),
id: "mailpoet_segments",
endpoint: "segments",
multiple: true,
filter: function(segment) {
return !!(!segment.deleted_at);
},
getLabel: function(segment) {
var name = segment.name;
if (segment.subscribers > 0) {
name += ' (%$1s)'.replace('%$1s', segment.subscribers);
}
return name;
},
validation: {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('noSegmentsSelectedError')
}
},
{
name: 'sender',
label: MailPoet.I18n.t('sender'),
tip: MailPoet.I18n.t('senderTip'),
fields: [
{
name: 'sender_name',
type: 'text',
placeholder: MailPoet.I18n.t('senderNamePlaceholder'),
validation: {
'data-parsley-required': true
}
},
{
name: 'sender_address',
type: 'text',
placeholder: MailPoet.I18n.t('senderAddressPlaceholder'),
validation: {
'data-parsley-required': true,
'data-parsley-type': 'email'
}
}
]
},
{
name: 'reply-to',
label: MailPoet.I18n.t('replyTo'),
tip: MailPoet.I18n.t('replyToTip'),
inline: true,
fields: [
{
name: 'reply_to_name',
type: 'text',
placeholder: MailPoet.I18n.t('replyToNamePlaceholder')
},
{
name: 'reply_to_address',
type: 'text',
placeholder: MailPoet.I18n.t('replyToAddressPlaceholder')
}
]
}
];
return fields;
}
);

View File

@@ -0,0 +1,77 @@
define(
[
'mailpoet',
'newsletters/types/welcome/scheduling.jsx'
],
function(
MailPoet,
Scheduling
) {
var settings = window.mailpoet_settings || {};
var fields = [
{
name: 'subject',
label: MailPoet.I18n.t('subjectLine'),
tip: MailPoet.I18n.t('subjectLineTip'),
type: 'text',
validation: {
'data-parsley-required': true,
'data-parsley-required-message': MailPoet.I18n.t('emptySubjectLineError')
}
},
{
name: 'options',
label: MailPoet.I18n.t('sendWelcomeEmailWhen'),
type: 'reactComponent',
component: Scheduling,
},
{
name: 'sender',
label: MailPoet.I18n.t('sender'),
tip: MailPoet.I18n.t('senderTip'),
fields: [
{
name: 'sender_name',
type: 'text',
placeholder: MailPoet.I18n.t('senderNamePlaceholder'),
validation: {
'data-parsley-required': true
}
},
{
name: 'sender_address',
type: 'text',
placeholder: MailPoet.I18n.t('senderAddressPlaceholder'),
validation: {
'data-parsley-required': true,
'data-parsley-type': 'email'
}
}
]
},
{
name: 'reply-to',
label: MailPoet.I18n.t('replyTo'),
tip: MailPoet.I18n.t('replyToTip'),
inline: true,
fields: [
{
name: 'reply_to_name',
type: 'text',
placeholder: MailPoet.I18n.t('replyToNamePlaceholder')
},
{
name: 'reply_to_address',
type: 'text',
placeholder: MailPoet.I18n.t('replyToAddressPlaceholder')
}
]
}
];
return fields;
}
);

View File

@@ -54,7 +54,7 @@ define(
try {
saveTemplate(JSON.parse(e.target.result));
} catch (err) {
MailPoet.Notice.error('This template file appears to be malformed. Please try another one.');
MailPoet.Notice.error(MailPoet.I18n.t('templateFileMalformedError'));
}
}.bind(this);
@@ -63,15 +63,15 @@ define(
render: function() {
return (
<div>
<h2>Import a template</h2>
<h2>{MailPoet.I18n.t('importTemplateTitle')}</h2>
<form onSubmit={this.handleSubmit}>
<input type="file" placeholder="Select a .json file to upload" ref="templateFile" />
<input type="file" placeholder={MailPoet.I18n.t('selectJsonFileToUpload')} ref="templateFile" />
<p className="submit">
<input
className="button button-primary"
type="submit"
value="Upload" />
value={MailPoet.I18n.t('upload')} />
</p>
</form>
</div>
@@ -80,9 +80,6 @@ define(
});
var NewsletterTemplates = React.createClass({
mixins: [
Router.History
],
getInitialState: function() {
return {
loading: false,
@@ -108,9 +105,9 @@ define(
response = [
{
name:
"MailPoet's Guide",
MailPoet.I18n.t('mailpoetGuideTemplateTitle'),
description:
"This is the standard template that comes with MailPoet.",
MailPoet.I18n.t('mailpoetGuideTemplateDescription'),
readonly: "1"
}
]
@@ -155,7 +152,9 @@ define(
this.setState({ loading: true });
if(
window.confirm(
'You are about to delete the template named "'+ template.name +'"'
(
MailPoet.I18n.t('confirmTemplateDeletion')
).replace("%$1s", template.name)
)
) {
MailPoet.Ajax.post({
@@ -187,7 +186,7 @@ define(
href="javascript:;"
onClick={ this.handleDeleteTemplate.bind(null, template) }
>
Delete
{MailPoet.I18n.t('delete')}
</a>
</div>
), thumbnail = '';
@@ -218,7 +217,7 @@ define(
className="button button-primary"
onClick={ this.handleSelectTemplate.bind(null, template) }
>
Select
{MailPoet.I18n.t('select')}
</a>
&nbsp;
<a
@@ -226,7 +225,7 @@ define(
className="button button-secondary"
onClick={ this.handlePreviewTemplate.bind(null, template) }
>
Preview
{MailPoet.I18n.t('preview')}
</a>
</div>
{ (template.readonly === "1") ? false : deleteLink }
@@ -242,7 +241,7 @@ define(
return (
<div>
<h1>Select a template</h1>
<h1>{MailPoet.I18n.t('selectTemplateTitle')}</h1>
<Breadcrumb step="template" />

View File

@@ -12,12 +12,12 @@ define(
Breadcrumb
) {
var NewsletterTypes = React.createClass({
mixins: [
Router.History
],
contextTypes: {
router: React.PropTypes.object.isRequired
},
setupNewsletter: function(type) {
if(type !== undefined) {
this.history.pushState(null, `/new/${type}`);
this.context.router.push(`/new/${type}`);
}
},
createNewsletter: function(type) {
@@ -26,11 +26,11 @@ define(
action: 'create',
data: {
type: type,
subject: 'Draft newsletter',
subject: MailPoet.I18n.t('draftNewsletterTitle'),
}
}).done(function(response) {
if(response.result && response.newsletter.id) {
this.history.pushState(null, `/template/${response.newsletter.id}`);
this.context.router.push(`/template/${response.newsletter.id}`);
} else {
if(response.errors.length > 0) {
response.errors.map(function(error) {
@@ -43,7 +43,7 @@ define(
render: function() {
return (
<div>
<h1>Pick a type of campaign</h1>
<h1>{MailPoet.I18n.t('pickCampaignType')}</h1>
<Breadcrumb step="type" />
@@ -52,10 +52,9 @@ define(
<div className="mailpoet_thumbnail"></div>
<div className="mailpoet_description">
<h3>Newsletter</h3>
<h3>{MailPoet.I18n.t('regularNewsletterTypeTitle')}</h3>
<p>
Send a newsletter with images, buttons, dividers,
and social bookmarks. Or a simple email.
{MailPoet.I18n.t('regularNewsletterTypeDescription')}
</p>
</div>
@@ -64,7 +63,7 @@ define(
className="button button-primary"
onClick={ this.createNewsletter.bind(null, 'standard') }
>
Create
{MailPoet.I18n.t('create')}
</a>
</div>
</li>
@@ -73,9 +72,9 @@ define(
<div className="mailpoet_thumbnail"></div>
<div className="mailpoet_description">
<h3>Welcome email</h3>
<h3>{MailPoet.I18n.t('welcomeNewsletterTypeTitle')}</h3>
<p>
Send an email for new users.
{MailPoet.I18n.t('welcomeNewsletterTypeDescription')}
</p>
</div>
@@ -84,7 +83,7 @@ define(
className="button button-primary"
onClick={ this.setupNewsletter.bind(null, 'welcome') }
>
Set up
{MailPoet.I18n.t('setUp')}
</a>
</div>
</li>
@@ -93,9 +92,9 @@ define(
<div className="mailpoet_thumbnail"></div>
<div className="mailpoet_description">
<h3>Post notifications</h3>
<h3>{MailPoet.I18n.t('postNotificationNewsletterTypeTitle')}</h3>
<p>
Automatically send posts immediately, daily, weekly or monthly. Filter by categories, if you like.
{MailPoet.I18n.t('postNotificationsNewsletterTypeDescription')}
</p>
</div>
@@ -104,7 +103,7 @@ define(
className="button button-primary"
onClick={ this.setupNewsletter.bind(null, 'notification') }
>
Set up
{MailPoet.I18n.t('setUp')}
</a>
</div>
</li>

View File

@@ -1,227 +0,0 @@
define(
[
'underscore',
'react',
'react-router',
'mailpoet',
'form/form.jsx',
'form/fields/select.jsx',
'form/fields/selection.jsx',
'form/fields/text.jsx',
'newsletters/breadcrumb.jsx'
],
function(
_,
React,
Router,
MailPoet,
Form,
Select,
Selection,
Text,
Breadcrumb
) {
var intervalField = {
name: 'interval',
values: {
'daily': 'Once a day at...',
'weekly': 'Weekly on...',
'monthly': 'Monthly on the...',
'nthWeekDay': 'Monthly every...',
'immediately': 'Immediately.',
},
};
var SECONDS_IN_DAY = 86400;
var TIME_STEP_SECONDS = 3600; // Default: 3600
var numberOfTimeSteps = SECONDS_IN_DAY / TIME_STEP_SECONDS;
var timeOfDayValues = _.object(_.map(
_.times(numberOfTimeSteps, function(step) { return step * TIME_STEP_SECONDS; }),
function(seconds) {
var date = new Date(null);
date.setSeconds(seconds);
var timeLabel = date.toISOString().substr(11, 5);
return [seconds, timeLabel];
}
));
var timeOfDayField = {
name: 'timeOfDay',
values: timeOfDayValues,
};
var weekDayField = {
name: 'weekDay',
values: {
0: 'Monday',
1: 'Tuesday',
2: 'Wednesday',
3: 'Thursday',
4: 'Friday',
5: 'Saturday',
6: 'Sunday',
},
};
var NUMBER_OF_DAYS_IN_MONTH = 28; // 28 for compatibility with MP2
var monthDayField = {
name: 'monthDay',
values: _.object(_.map(
_.times(NUMBER_OF_DAYS_IN_MONTH, function(day) { return day; }),
function(day) {
var suffixes = {
0: 'st',
1: 'nd',
2: 'rd'
};
var suffix = suffixes[day] || 'th';
return [day, (day + 1).toString() + suffix];
},
)),
};
var nthWeekDayField = {
name: 'nthWeekDay',
values: {
'0': '1st',
'1': '2nd',
'2': '3rd',
'3': 'last',
},
};
var NewsletterWelcome = React.createClass({
mixins: [
Router.History
],
getInitialState: function() {
return {
intervalType: 'immediate', // 'immediate'|'daily'|'weekly'|'monthly'
timeOfDay: 0,
weekDay: 0,
monthDay: 0,
nthWeekDay: 0,
};
},
handleIntervalChange: function(event) {
this.setState({
intervalType: event.target.value,
});
},
handleTimeOfDayChange: function(event) {
this.setState({
timeOfDay: event.target.value,
});
},
handleWeekDayChange: function(event) {
this.setState({
weekDay: event.target.value,
});
},
handleMonthDayChange: function(event) {
this.setState({
monthDay: event.target.value,
});
},
handleNthWeekDayChange: function(event) {
this.setState({
nthWeekDay: event.target.value,
});
},
handleNext: function() {
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'create',
data: {
type: 'notification',
options: this.state,
},
}).done(function(response) {
if(response.result && response.newsletter.id) {
this.showTemplateSelection(response.newsletter.id);
} else {
if(response.errors.length > 0) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
}
}.bind(this));
},
showTemplateSelection: function(newsletterId) {
this.history.pushState(null, `/template/${newsletterId}`);
},
render: function() {
var timeOfDaySelection,
weekDaySelection,
monthDaySelection,
nthWeekDaySelection;
if (this.state.intervalType !== 'immediately') {
timeOfDaySelection = (
<Select
field={timeOfDayField}
item={this.state}
onValueChange={this.handleTimeOfDayChange} />
);
}
if (this.state.intervalType === 'weekly'
|| this.state.intervalType === 'nthWeekDay') {
weekDaySelection = (
<Select
field={weekDayField}
item={this.state}
onValueChange={this.handleWeekDayChange} />
);
}
if (this.state.intervalType === 'monthly') {
monthDaySelection = (
<Select
field={monthDayField}
item={this.state}
onValueChange={this.handleMonthDayChange} />
);
}
if (this.state.intervalType === 'nthWeekDay') {
nthWeekDaySelection = (
<Select
field={nthWeekDayField}
item={this.state}
onValueChange={this.handleNthWeekDayChange} />
);
}
return (
<div>
<h1>Post notifications</h1>
<Breadcrumb step="type" />
<Select
field={intervalField}
item={this.state}
onValueChange={this.handleIntervalChange} />
{nthWeekDaySelection}
{monthDaySelection}
{weekDaySelection}
{timeOfDaySelection}
<p className="submit">
<input
className="button button-primary"
type="button"
onClick={ this.handleNext }
value="Next" />
</p>
</div>
);
},
});
return NewsletterWelcome;
}
);

View File

@@ -0,0 +1,96 @@
define(
[
'underscore',
'react',
'react-router',
'mailpoet',
'newsletters/types/notification/scheduling.jsx',
'newsletters/breadcrumb.jsx'
],
function(
_,
React,
Router,
MailPoet,
Scheduling,
Breadcrumb
) {
var field = {
name: 'options',
label: 'Periodicity',
type: 'reactComponent',
component: Scheduling,
};
var NewsletterNotification = React.createClass({
contextTypes: {
router: React.PropTypes.object.isRequired
},
getInitialState: function() {
return {
options: {
intervalType: 'daily',
timeOfDay: 0,
weekDay: 1,
monthDay: 0,
nthWeekDay: 1,
}
};
},
handleValueChange: function(event) {
var state = this.state;
state[event.target.name] = event.target.value;
this.setState(state);
},
handleNext: function() {
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'create',
data: _.extend({}, this.state, {
type: 'notification',
subject: MailPoet.I18n.t('draftNewsletterTitle'),
}),
}).done(function(response) {
if(response.result && response.newsletter.id) {
this.showTemplateSelection(response.newsletter.id);
} else {
if(response.errors.length > 0) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
}
}.bind(this));
},
showTemplateSelection: function(newsletterId) {
this.context.router.push(`/template/${newsletterId}`);
},
render: function() {
return (
<div>
<h1>{MailPoet.I18n.t('postNotificationNewsletterTypeTitle')}</h1>
<Breadcrumb step="type" />
<h3>{MailPoet.I18n.t('selectPeriodicity')}</h3>
<Scheduling
item={this.state}
field={field}
onValueChange={this.handleValueChange} />
<p className="submit">
<input
className="button button-primary"
type="button"
onClick={ this.handleNext }
value={MailPoet.I18n.t('next')} />
</p>
</div>
);
},
});
return NewsletterNotification;
}
);

View File

@@ -0,0 +1,200 @@
define(
[
'underscore',
'react',
'react-router',
'mailpoet',
'form/fields/select.jsx'
],
function(
_,
React,
Router,
MailPoet,
Select
) {
var intervalField = {
name: 'intervalType',
values: {
'daily': MailPoet.I18n.t('daily'),
'weekly': MailPoet.I18n.t('weekly'),
'monthly': MailPoet.I18n.t('monthly'),
'nthWeekDay': MailPoet.I18n.t('monthlyEvery'),
'immediately': MailPoet.I18n.t('immediately'),
},
};
var SECONDS_IN_DAY = 86400;
var TIME_STEP_SECONDS = 3600; // Default: 3600
var numberOfTimeSteps = SECONDS_IN_DAY / TIME_STEP_SECONDS;
var timeOfDayValues = _.object(_.map(
_.times(numberOfTimeSteps, function(step) { return step * TIME_STEP_SECONDS; }),
function(seconds) {
var date = new Date(null);
date.setSeconds(seconds);
var timeLabel = date.toISOString().substr(11, 5);
return [seconds, timeLabel];
}
));
var timeOfDayField = {
name: 'timeOfDay',
values: timeOfDayValues,
};
var weekDayField = {
name: 'weekDay',
values: {
0: MailPoet.I18n.t('sunday'),
1: MailPoet.I18n.t('monday'),
2: MailPoet.I18n.t('tuesday'),
3: MailPoet.I18n.t('wednesday'),
4: MailPoet.I18n.t('thursday'),
5: MailPoet.I18n.t('friday'),
6: MailPoet.I18n.t('saturday')
},
};
var NUMBER_OF_DAYS_IN_MONTH = 28; // 28 for compatibility with MP2
var monthDayField = {
name: 'monthDay',
values: _.object(_.map(
_.times(NUMBER_OF_DAYS_IN_MONTH, function(day) { return day; }),
function(day) {
var labels = {
0: MailPoet.I18n.t('first'),
1: MailPoet.I18n.t('second'),
2: MailPoet.I18n.t('third')
},
label;
if (labels[day] !== undefined) {
label = labels[day];
} else {
label = MailPoet.I18n.t('nth').replace("%$1d", day + 1);
}
return [day, label];
},
)),
};
var nthWeekDayField = {
name: 'nthWeekDay',
values: {
'1': MailPoet.I18n.t('first'),
'2': MailPoet.I18n.t('second'),
'3': MailPoet.I18n.t('third'),
'L': MailPoet.I18n.t('last'),
},
};
var NotificationScheduling = React.createClass({
_getCurrentValue: function() {
return this.props.item[this.props.field.name] || {};
},
handleValueChange: function(name, value) {
var oldValue = this._getCurrentValue(),
newValue = {};
newValue[name] = value;
return this.props.onValueChange({
target: {
name: this.props.field.name,
value: _.extend({}, oldValue, newValue)
}
});
},
handleIntervalChange: function(event) {
return this.handleValueChange(
'intervalType',
event.target.value
);
},
handleTimeOfDayChange: function(event) {
return this.handleValueChange(
'timeOfDay',
event.target.value
);
},
handleWeekDayChange: function(event) {
return this.handleValueChange(
'weekDay',
event.target.value
);
},
handleMonthDayChange: function(event) {
return this.handleValueChange(
'monthDay',
event.target.value
);
},
handleNthWeekDayChange: function(event) {
return this.handleValueChange(
'nthWeekDay',
event.target.value
);
},
render: function() {
var value = this._getCurrentValue(),
timeOfDaySelection,
weekDaySelection,
monthDaySelection,
nthWeekDaySelection;
if (value.intervalType !== 'immediately') {
timeOfDaySelection = (
<Select
field={timeOfDayField}
item={this._getCurrentValue()}
onValueChange={this.handleTimeOfDayChange} />
);
}
if (value.intervalType === 'weekly'
|| value.intervalType === 'nthWeekDay') {
weekDaySelection = (
<Select
field={weekDayField}
item={this._getCurrentValue()}
onValueChange={this.handleWeekDayChange} />
);
}
if (value.intervalType === 'monthly') {
monthDaySelection = (
<Select
field={monthDayField}
item={this._getCurrentValue()}
onValueChange={this.handleMonthDayChange} />
);
}
if (value.intervalType === 'nthWeekDay') {
nthWeekDaySelection = (
<Select
field={nthWeekDayField}
item={this._getCurrentValue()}
onValueChange={this.handleNthWeekDayChange} />
);
}
return (
<div>
<Select
field={intervalField}
item={this._getCurrentValue()}
onValueChange={this.handleIntervalChange} />
{nthWeekDaySelection}
{monthDaySelection}
{weekDaySelection}
{timeOfDaySelection}
</div>
);
},
});
return NotificationScheduling;
}
);

View File

@@ -3,25 +3,21 @@ define(
'react',
'react-router',
'mailpoet',
'form/form.jsx',
'form/fields/selection.jsx',
'newsletters/breadcrumb.jsx'
],
function(
React,
Router,
MailPoet,
Form,
Selection,
Breadcrumb
) {
var NewsletterStandard = React.createClass({
mixins: [
Router.History
],
contextTypes: {
router: React.PropTypes.object.isRequired
},
showTemplateSelection: function(newsletterId) {
this.history.pushState(null, `/template/${newsletterId}`);
this.context.router.push(`/template/${newsletterId}`);
},
componentDidMount: function() {
// No options for this type, create a newsletter upon mounting
@@ -32,7 +28,6 @@ define(
type: 'standard',
}
}).done(function(response) {
console.log(response);
if(response.result && response.newsletter.id) {
this.showTemplateSelection(response.newsletter.id);
} else {
@@ -47,7 +42,7 @@ define(
render: function() {
return (
<div>
<h1>Newsletter</h1>
<h1>{MailPoet.I18n.t('regularNewsletterTypeTitle')}</h1>
<Breadcrumb step="type" />
</div>
);

View File

@@ -4,9 +4,7 @@ define(
'react',
'react-router',
'mailpoet',
'form/form.jsx',
'form/fields/select.jsx',
'form/fields/selection.jsx',
'form/fields/text.jsx',
'newsletters/breadcrumb.jsx'
],
@@ -15,9 +13,7 @@ define(
React,
Router,
MailPoet,
Form,
Select,
Selection,
Text,
Breadcrumb
) {
@@ -28,15 +24,19 @@ define(
var events = {
name: 'event',
values: {
'segment': 'When someone subscribes to the list...',
'user': 'When a new Wordrpess user is added to your site...',
'segment': MailPoet.I18n.t('onSubscriptionToList'),
'user': MailPoet.I18n.t('onWordpressUserRegistration'),
}
};
var availableSegmentValues = _.object(_.map(
availableSegments,
function(segment) {
return [segment.id, segment.name];
var name = segment.name;
if (segment.subscribers > 0) {
name += ' (%$1d)'.replace('%$1d', segment.subscribers);
}
return [segment.id, name];
}
));
var segmentField = {
@@ -57,50 +57,61 @@ define(
var afterTimeTypeField = {
name: 'afterTimeType',
values: {
'immediate': 'immediately',
'hours': 'hour(s) after',
'days': 'day(s) after',
'weeks': 'week(s) after',
'immediate': MailPoet.I18n.t('delayImmediately'),
'hours': MailPoet.I18n.t('delayHoursAfter'),
'days': MailPoet.I18n.t('delayDaysAfter'),
'weeks': MailPoet.I18n.t('delayWeeksAfter'),
}
};
var NewsletterWelcome = React.createClass({
mixins: [
Router.History
],
getInitialState: function() {
return {
event: 'segment',
segment: 1,
role: 'subscriber',
afterTimeNumber: 1,
afterTimeType: 'immediate',
};
var WelcomeScheduling = React.createClass({
contextTypes: {
router: React.PropTypes.object.isRequired
},
_getCurrentValue: function() {
return this.props.item[this.props.field.name] || {};
},
handleValueChange: function(name, value) {
var oldValue = this._getCurrentValue(),
newValue = {};
newValue[name] = value;
return this.props.onValueChange({
target: {
name: this.props.field.name,
value: _.extend({}, oldValue, newValue)
}
});
},
handleEventChange: function(event) {
this.setState({
event: event.target.value,
});
return this.handleValueChange(
'event',
event.target.value
);
},
handleSegmentChange: function(event) {
this.setState({
segment: event.target.value,
});
return this.handleValueChange(
'segment',
event.target.value
);
},
handleRoleChange: function(event) {
this.setState({
role: event.target.value,
});
return this.handleValueChange(
'role',
event.target.value
);
},
handleAfterTimeNumberChange: function(event) {
this.setState({
afterTimeNumber: event.target.value,
});
return this.handleValueChange(
'afterTimeNumber',
event.target.value
);
},
handleAfterTimeTypeChange: function(event) {
this.setState({
afterTimeType: event.target.value,
});
return this.handleValueChange(
'afterTimeType',
event.target.value
);
},
handleNext: function() {
MailPoet.Ajax.post({
@@ -123,41 +134,41 @@ define(
}.bind(this));
},
showTemplateSelection: function(newsletterId) {
this.history.pushState(null, `/template/${newsletterId}`);
this.context.router.push(`/template/${newsletterId}`);
},
render: function() {
var roleSegmentSelection, timeNumber;
if (this.state.event === 'user') {
var value = this._getCurrentValue(),
roleSegmentSelection, timeNumber;
if (value.event === 'user') {
roleSegmentSelection = (
<Select
field={roleField}
item={this.state}
item={this._getCurrentValue()}
onValueChange={this.handleRoleChange} />
);
} else {
roleSegmentSelection = (
<Select
field={segmentField}
item={this.state}
item={this._getCurrentValue()}
onValueChange={this.handleSegmentChange} />
);
}
if (this.state.afterTimeType !== 'immediate') {
if (value.afterTimeType !== 'immediate') {
timeNumber = (
<Text
field={afterTimeNumberField}
item={this.state}
item={this._getCurrentValue()}
onValueChange={this.handleAfterTimeNumberChange} />
);
}
return (
<div>
<h1>Welcome email</h1>
<Breadcrumb step="type" />
<Select
field={events}
item={this.state}
item={this._getCurrentValue()}
onValueChange={this.handleEventChange} />
{roleSegmentSelection}
@@ -166,21 +177,14 @@ define(
<Select
field={afterTimeTypeField}
item={this.state}
item={this._getCurrentValue()}
onValueChange={this.handleAfterTimeTypeChange}/>
<p className="submit">
<input
className="button button-primary"
type="button"
onClick={ this.handleNext }
value="Next" />
</p>
</div>
);
},
});
return NewsletterWelcome;
return WelcomeScheduling;
}
);

View File

@@ -0,0 +1,103 @@
define(
[
'underscore',
'react',
'react-router',
'mailpoet',
'newsletters/types/welcome/scheduling.jsx',
'newsletters/breadcrumb.jsx'
],
function(
_,
React,
Router,
MailPoet,
Scheduling,
Breadcrumb
) {
var field = {
name: 'options',
label: 'Event',
type: 'reactComponent',
component: Scheduling,
};
var availableSegments = window.mailpoet_segments || {},
defaultSegment = 1;
if (_.size(availableSegments) > 0) {
defaultSegment = _.first(availableSegments).id;
}
var NewsletterWelcome = React.createClass({
contextTypes: {
router: React.PropTypes.object.isRequired
},
getInitialState: function() {
return {
options: {
event: 'segment',
segment: defaultSegment,
role: 'subscriber',
afterTimeNumber: 1,
afterTimeType: 'immediate',
}
};
},
handleValueChange: function(event) {
var state = this.state;
state[event.target.name] = event.target.value;
this.setState(state);
},
handleNext: function() {
MailPoet.Ajax.post({
endpoint: 'newsletters',
action: 'create',
data: _.extend({}, this.state, {
type: 'welcome',
subject: MailPoet.I18n.t('draftNewsletterTitle'),
}),
}).done(function(response) {
if(response.result && response.newsletter.id) {
this.showTemplateSelection(response.newsletter.id);
} else {
if(response.errors.length > 0) {
response.errors.map(function(error) {
MailPoet.Notice.error(error);
});
}
}
}.bind(this));
},
showTemplateSelection: function(newsletterId) {
this.context.router.push(`/template/${newsletterId}`);
},
render: function() {
return (
<div>
<h1>{MailPoet.I18n.t('welcomeNewsletterTypeTitle')}</h1>
<Breadcrumb step="type" />
<h3>{MailPoet.I18n.t('selectEventToSendWelcomeEmail')}</h3>
<Scheduling
item={this.state}
field={field}
onValueChange={this.handleValueChange} />
<p className="submit">
<input
className="button button-primary"
type="button"
onClick={ this.handleNext }
value={MailPoet.I18n.t('next')} />
</p>
</div>
);
},
});
return NewsletterWelcome;
}
);

View File

@@ -20,13 +20,18 @@ function(
$('form.mailpoet_form').each(function() {
var form = $(this);
form.parsley().on('form:submit', function(parsley) {
var data = form.serializeObject() || {};
form.parsley().on('form:validated', function(parsley) {
// clear messages
form.find('.mailpoet_message').html('');
form.find('.mailpoet_message > p').hide();
// resize iframe
if(window.frameElement !== null) {
MailPoet.Iframe.autoSize(window.frameElement);
}
});
form.parsley().on('form:submit', function(parsley) {
var data = form.serializeObject() || {};
// check if we're on the same domain
if(isSameDomain(MailPoetForm.ajax_url) === false) {
// non ajax post request
@@ -40,27 +45,18 @@ function(
action: 'subscribe',
data: data
}).done(function(response) {
if(response.result !== true) {
// errors
$.each(response.errors, function(index, error) {
form
.find('.mailpoet_message')
.append('<p class="mailpoet_validate_error">'+
error+
'</p>');
});
if(response.result === false) {
form.find('.mailpoet_validate_error').html(
response.errors.join('<br />')
).show();
} else {
// successfully subscribed
if(response.page !== undefined) {
// go to page
window.location.href = response.page;
} else if(response.message !== undefined) {
} else {
// display success message
form
.find('.mailpoet_message')
.html('<p class="mailpoet_validate_success">'+
response.message+
'</p>');
form.find('.mailpoet_validate_success').show();
}
// reset form
@@ -68,6 +64,11 @@ function(
// reset validation
parsley.reset();
}
// resize iframe
if(window.frameElement !== null) {
MailPoet.Iframe.autoSize(window.frameElement);
}
});
}
return false;

View File

@@ -1,13 +1,11 @@
define(
[
'react',
'react-router',
'mailpoet',
'form/form.jsx'
],
function(
React,
Router,
MailPoet,
Form
) {
@@ -15,34 +13,32 @@ define(
let fields = [
{
name: 'name',
label: 'Name',
label: MailPoet.I18n.t('name'),
type: 'text'
},
{
name: 'description',
label: 'Description',
type: 'textarea'
label: MailPoet.I18n.t('description'),
type: 'textarea',
tip: MailPoet.I18n.t('segmentDescriptionTip')
}
];
const messages = {
onUpdate: function() {
MailPoet.Notice.success('Segment successfully updated!');
MailPoet.Notice.success(MailPoet.I18n.t('segmentUpdated'));
},
onCreate: function() {
MailPoet.Notice.success('Segment successfully added!');
MailPoet.Notice.success(MailPoet.I18n.t('segmentAdded'));
}
};
const SegmentForm = React.createClass({
mixins: [
Router.History
],
render: function() {
return (
<div>
<h2 className="title">
Segment
{MailPoet.I18n.t('segment')}
</h2>
<Form

View File

@@ -10,32 +10,32 @@ import Listing from 'listing/listing.jsx'
var columns = [
{
name: 'name',
label: 'Name',
label: MailPoet.I18n.t('name'),
sortable: true
},
{
name: 'description',
label: 'Description',
label: MailPoet.I18n.t('description'),
sortable: false
},
{
name: 'subscribed',
label: 'Subscribed',
label: MailPoet.I18n.t('subscribed'),
sortable: false
},
{
name: 'unconfirmed',
label: 'Unconfirmed',
label: MailPoet.I18n.t('unconfirmed'),
sortable: false
},
{
name: 'unsubscribed',
label: 'Unsubscribed',
label: MailPoet.I18n.t('unsubscribed'),
sortable: false
},
{
name: 'created_at',
label: 'Created on',
label: MailPoet.I18n.t('createdOn'),
sortable: true
}
];
@@ -47,11 +47,11 @@ const messages = {
if(count === 1) {
message = (
'1 segment was moved to the trash.'
MailPoet.I18n.t('oneSegmentTrashed')
);
} else {
message = (
'%$1d segments were moved to the trash.'
MailPoet.I18n.t('multipleSegmentsTrashed')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
@@ -62,11 +62,11 @@ const messages = {
if(count === 1) {
message = (
'1 segment was permanently deleted.'
MailPoet.I18n.t('oneSegmentDeleted')
);
} else {
message = (
'%$1d segments were permanently deleted.'
MailPoet.I18n.t('multipleSegmentsDeleted')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
@@ -77,11 +77,11 @@ const messages = {
if(count === 1) {
message = (
'1 segment has been restored from the trash.'
MailPoet.I18n.t('oneSegmentRestored')
);
} else {
message = (
'%$1d segments have been restored from the trash.'
MailPoet.I18n.t('multipleSegmentsRestored')
).replace('%$1d', count);
}
MailPoet.Notice.success(message);
@@ -91,10 +91,10 @@ const messages = {
const item_actions = [
{
name: 'edit',
label: 'Edit',
label: MailPoet.I18n.t('edit'),
link: function(item) {
return (
<Link to={ `/edit/${item.id}` }>Edit</Link>
<Link to={ `/edit/${item.id}` }>{MailPoet.I18n.t('edit')}</Link>
);
},
display: function(segment) {
@@ -111,7 +111,7 @@ const item_actions = [
data: item.id
}).done(function(response) {
MailPoet.Notice.success(
('List "%$1s" has been duplicated.').replace('%$1s', response.name)
(MailPoet.I18n.t('listDuplicated')).replace('%$1s', response.name)
);
refresh();
});
@@ -122,7 +122,7 @@ const item_actions = [
},
{
name: 'synchronize_segment',
label: 'Update',
label: MailPoet.I18n.t('update'),
className: 'update',
onClick: function(item, refresh) {
MailPoet.Modal.loading(true);
@@ -133,7 +133,7 @@ const item_actions = [
MailPoet.Modal.loading(false);
if(response === true) {
MailPoet.Notice.success(
('List "%$1s" has been synchronized.').replace('%$1s', item.name)
(MailPoet.I18n.t('listSynchronized')).replace('%$1s', item.name)
);
refresh();
}
@@ -147,7 +147,7 @@ const item_actions = [
name: 'view_subscribers',
link: function(item) {
return (
<a href={ item.subscribers_url }>View subscribers</a>
<a href={ item.subscribers_url }>{MailPoet.I18n.t('viewSubscribers')}</a>
);
}
},
@@ -159,9 +159,6 @@ const item_actions = [
}
];
const bulk_actions = [
];
const SegmentList = React.createClass({
renderItem: function(segment, actions) {
var rowClasses = classNames(
@@ -190,7 +187,7 @@ const SegmentList = React.createClass({
<abbr>{ segment.subscribers_count.unsubscribed || 0 }</abbr>
</td>
<td className="column-date" data-colname="Created on">
<abbr>{ segment.created_at }</abbr>
<abbr>{ MailPoet.Date.full(segment.created_at) }</abbr>
</td>
</div>
);
@@ -199,7 +196,7 @@ const SegmentList = React.createClass({
return (
<div>
<h2 className="title">
Segments <Link className="add-new-h2" to="/new">New</Link>
{MailPoet.I18n.t('pageTitle')} <Link className="add-new-h2" to="/new">{MailPoet.I18n.t('new')}</Link>
</h2>
<Listing
@@ -211,7 +208,6 @@ const SegmentList = React.createClass({
endpoint="segments"
onRenderItem={ this.renderItem }
columns={ columns }
bulk_actions={ bulk_actions }
item_actions={ item_actions }
/>
</div>
@@ -219,4 +215,4 @@ const SegmentList = React.createClass({
}
});
module.exports = SegmentList;
module.exports = SegmentList;

View File

@@ -1,11 +1,11 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, Route, IndexRoute, Link } from 'react-router'
import { Router, Route, IndexRoute, Link, useRouterHistory } from 'react-router'
import { createHashHistory } from 'history'
import SegmentList from 'segments/list.jsx'
import SegmentForm from 'segments/form.jsx'
import createHashHistory from 'history/lib/createHashHistory'
let history = createHashHistory({ queryKey: false })
const history = useRouterHistory(createHashHistory)({ queryKey: false });
const App = React.createClass({
render() {
@@ -13,7 +13,7 @@ const App = React.createClass({
}
});
let container = document.getElementById('segments_container');
const container = document.getElementById('segments_container');
if(container) {
ReactDOM.render((

View File

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

View File

@@ -3,47 +3,45 @@ define(
'react',
'react-router',
'mailpoet',
'form/form.jsx',
'moment'
'form/form.jsx'
],
function(
React,
Router,
MailPoet,
Form,
Moment
Form
) {
var fields = [
{
name: 'email',
label: 'E-mail',
label: MailPoet.I18n.t('email'),
type: 'text'
},
{
name: 'first_name',
label: 'Firstname',
label: MailPoet.I18n.t('firstname'),
type: 'text'
},
{
name: 'last_name',
label: 'Lastname',
label: MailPoet.I18n.t('lastname'),
type: 'text'
},
{
name: 'status',
label: 'Status',
label: MailPoet.I18n.t('status'),
type: 'select',
values: {
'unconfirmed': 'Unconfirmed',
'subscribed': 'Subscribed',
'unsubscribed': 'Unsubscribed'
'unconfirmed': MailPoet.I18n.t('unconfirmed'),
'subscribed': MailPoet.I18n.t('subscribed'),
'unsubscribed': MailPoet.I18n.t('unsubscribed')
}
},
{
name: 'segments',
label: 'Lists',
label: MailPoet.I18n.t('lists'),
type: 'selection',
placeholder: "Select a list",
placeholder: MailPoet.I18n.t('selectList'),
endpoint: "segments",
multiple: true,
selected: function(subscriber) {
@@ -69,10 +67,15 @@ define(
label = segment.name;
if (subscription.status === 'unsubscribed') {
const unsubscribed_at = Moment(subscription.updated_at)
.utcOffset(parseInt(mailpoet_date_offset))
.format('ddd, D MMM YYYY HH:mm:ss');
label += ' (Unsubscribed on '+unsubscribed_at+')';
const unsubscribed_at = MailPoet.Date
.format(subscription.updated_at);
label += ' (%$1s)'.replace(
'%$1s',
MailPoet.I18n.t('unsubscribedOn').replace(
'%$1s',
unsubscribed_at
)
);
}
}
});
@@ -102,24 +105,21 @@ define(
var messages = {
onUpdate: function() {
MailPoet.Notice.success('Subscriber successfully updated!');
MailPoet.Notice.success(MailPoet.I18n.t('subscriberUpdated'));
},
onCreate: function() {
MailPoet.Notice.success('Subscriber successfully added!');
MailPoet.Notice.success(MailPoet.I18n.t('subscriberAdded'));
}
};
var Link = Router.Link;
var SubscriberForm = React.createClass({
mixins: [
Router.History
],
render: function() {
return (
<div>
<h2 className="title">
Subscriber
{MailPoet.I18n.t('subscriber')}
</h2>
<Form

View File

@@ -150,7 +150,7 @@ define(
if (response.result === false) {
MailPoet.Notice.error(response.errors);
} else {
resultMessage = MailPoetI18n.exportMessage
resultMessage = MailPoet.I18n.t('exportMessage')
.replace('%1$s', '<strong>' + response.data.totalExported + '</strong>')
.replace('[link]', '<a href="' + response.data.exportFileURL + '" target="_blank" >')
.replace('[/link]', '</a>');
@@ -162,7 +162,7 @@ define(
.error(function (error) {
MailPoet.Modal.loading(false);
MailPoet.Notice.error(
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.'
MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.'
);
});
});

View File

@@ -7,7 +7,8 @@ define(
'handlebars',
'papaparse',
'select2',
'asyncqueue'
'asyncqueue',
'xss'
],
function (
Backbone,
@@ -16,7 +17,8 @@ define(
MailPoet,
Handlebars,
Papa,
AsyncQueue
AsyncQueue,
xss
) {
if (!jQuery('#mailpoet_subscribers_import').length) {
return;
@@ -126,7 +128,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(MailPoetI18n.maxPostSizeNotice, {
MailPoet.Notice.error(MailPoet.I18n.t('maxPostSizeNotice'), {
timeout: noticeTimeout,
});
return;
@@ -146,7 +148,7 @@ define(
var ext = this.value.match(/\.(.+)$/);
if (ext === null || ext[1].toLowerCase() !== 'csv') {
this.value = '';
MailPoet.Notice.error(MailPoetI18n.wrongFileFormat, {
MailPoet.Notice.error(MailPoet.I18n.t('wrongFileFormat'), {
timeout: noticeTimeout,
});
}
@@ -210,7 +212,7 @@ define(
.removeClass()
.addClass('mailpoet_mailchimp-key-status mailpoet_mailchimp-ok');
if (!response.data) {
jQuery('.mailpoet_mailchimp-key-status').html(MailPoetI18n.noMailChimpLists);
jQuery('.mailpoet_mailchimp-key-status').html(MailPoet.I18n.t('noMailChimpLists'));
mailChimpListsContainerElement.hide();
toggleNextStepButton(mailChimpProcessButtonElement, 'off');
} else {
@@ -221,7 +223,7 @@ define(
}).error(function (error) {
MailPoet.Modal.loading(false);
MailPoet.Notice.error(
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.', {
MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.', {
timeout: noticeTimeout,
}
);
@@ -256,7 +258,7 @@ define(
}).error(function () {
MailPoet.Modal.loading(false);
MailPoet.Notice.error(
MailPoetI18n.serverError + result.statusText.toLowerCase() + '.', {
MailPoet.I18n.t('serverError') + result.statusText.toLowerCase() + '.', {
timeout: noticeTimeout,
}
);
@@ -348,14 +350,14 @@ define(
comments: advancedOptionComments,
error: function () {
MailPoet.Notice.hide();
MailPoet.Notice.error(MailPoetI18n.dataProcessingError, {
MailPoet.Notice.error(MailPoet.I18n.t('dataProcessingError'), {
timeout: noticeTimeout,
});
},
complete: function (CSV) {
for (var rowCount in CSV.data) {
var rowData = CSV.data[rowCount].map(function (el) {
return el.trim();
return filterXSS(el.trim());
}),
rowColumnCount = rowData.length;
// set the number of row elements based on the first non-empty row
@@ -429,8 +431,8 @@ define(
}
else {
MailPoet.Modal.loading(false);
var errorNotice = MailPoetI18n.noValidRecords;
errorNotice = errorNotice.replace('[link]', MailPoetI18n.csvKBLink);
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,
@@ -499,17 +501,17 @@ define(
}
var import_results = {
notice: MailPoetI18n.importNoticeSkipped.replace(
notice: MailPoet.I18n.t('importNoticeSkipped').replace(
'%1$s',
'<strong>' + (subscribers.invalid.length + subscribers.duplicate.length) + '</strong>'
),
invalid: (subscribers.invalid.length)
? MailPoetI18n.importNoticeInvalid
? MailPoet.I18n.t('importNoticeInvalid')
.replace('%1$s', '<strong>' + subscribers.invalid.length + '</strong>')
.replace('%2$s', subscribers.invalid.join(', '))
: null,
duplicate: (subscribers.duplicate.length)
? MailPoetI18n.importNoticeDuplicate
? MailPoet.I18n.t('importNoticeDuplicate')
.replace('%1$s', '<strong>' + subscribers.duplicate.length + '</strong>')
.replace('%2$s', subscribers.duplicate.join(', '))
: null
@@ -525,8 +527,8 @@ define(
jQuery(details).toggle();
this.text =
(jQuery(details).is(":visible"))
? MailPoetI18n.hideDetails
: MailPoetI18n.showDetails;
? MailPoet.I18n.t('hideDetails')
: MailPoet.I18n.t('showDetails');
});
// show available segments
@@ -562,7 +564,7 @@ define(
var segmentSelectionNotice = jQuery('[data-id="notice_segmentSelection"]');
if (!this.value) {
if (!segmentSelectionNotice.length) {
MailPoet.Notice.error(MailPoetI18n.segmentSelectionRequired, {
MailPoet.Notice.error(MailPoet.I18n.t('segmentSelectionRequired'), {
static: true,
timeout: noticeTimeout,
scroll: true,
@@ -582,7 +584,7 @@ define(
jQuery('.mailpoet_create_segment').click(function () {
MailPoet.Modal.popup({
title: MailPoetI18n.addNewList,
title: MailPoet.I18n.t('addNewList'),
template: jQuery('#new_segment_template').html()
})
jQuery('#new_segment_name').keypress(function (e) {
@@ -642,7 +644,7 @@ define(
else {
MailPoet.Modal.close();
MailPoet.Notice.error(
MailPoetI18n.segmentCreateError + response.message + '.', {
MailPoet.I18n.t('segmentCreateError') + response.message + '.', {
timeout: noticeTimeout,
}
);
@@ -651,7 +653,7 @@ define(
.error(function (error) {
MailPoet.Modal.close();
MailPoet.Notice.error(
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.', {
MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.', {
timeout: noticeTimeout
}
);
@@ -768,7 +770,7 @@ define(
selectEvent.preventDefault();
jQuery(selectElement).select2('close');
MailPoet.Modal.popup({
title: MailPoetI18n.addNewColumn,
title: MailPoet.I18n.t('addNewColumn'),
template: jQuery('#new_column_template').html()
});
jQuery('#new_column_name').keypress(function (e) {
@@ -832,7 +834,7 @@ define(
// if this is the first custom column, create an "optgroup"
if (mailpoetColumnsSelect2.length === 2) {
mailpoetColumnsSelect2.push({
'name': MailPoetI18n.userColumns,
'name': MailPoet.I18n.t('userColumns'),
'children': []
});
}
@@ -858,7 +860,7 @@ define(
filterSubscribers();
}
else {
MailPoet.Notice.error(MailPoetI18n.customFieldCreateError, {
MailPoet.Notice.error(MailPoet.I18n.t('customFieldCreateError'), {
timeout: noticeTimeout,
});
}
@@ -867,7 +869,7 @@ define(
.error(function (error) {
MailPoet.Modal.loading(false);
MailPoet.Notice.error(
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.', {
MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.', {
timeout: noticeTimeout,
}
);
@@ -888,7 +890,7 @@ define(
// if another column has the same value and it's not an 'ignore', prompt user
if (elementId === selectedOptionId
&& elementId !== 'ignore') {
if (confirm(MailPoetI18n.selectedValueAlreadyMatched + ' ' + MailPoetI18n.confirmCorrespondingColumn)) {
if (confirm(MailPoet.I18n.t('selectedValueAlreadyMatched') + ' ' + MailPoet.I18n.t('confirmCorrespondingColumn'))) {
jQuery(element).data('column-id', 'ignore');
}
else {
@@ -931,7 +933,7 @@ define(
if (!emailRegex.test(subscribersClone.subscribers[0][matchedColumn])) {
preventNextStep = true;
if (!jQuery('[data-id="notice_invalidEmail"]').length) {
MailPoet.Notice.error(MailPoetI18n.columnContainsInvalidElement, {
MailPoet.Notice.error(MailPoet.I18n.t('columnContainsInvalidElement'), {
static: true,
timeout: noticeTimeout,
scroll: true,
@@ -951,18 +953,18 @@ define(
date = new Date(rowData.replace(/-/g, '/')), // IE doesn't like
// dashes as date separators
month_name = [
MailPoetI18n.january,
MailPoetI18n.february,
MailPoetI18n.march,
MailPoetI18n.april,
MailPoetI18n.may,
MailPoetI18n.june,
MailPoetI18n.july,
MailPoetI18n.august,
MailPoetI18n.september,
MailPoetI18n.october,
MailPoetI18n.november,
MailPoetI18n.december
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')
];
if (position !== fillterPosition) {
@@ -975,8 +977,8 @@ define(
if (rowData.trim() === '') {
data[matchedColumn] =
'<span class="mailpoet_data_match mailpoet_import_error" title="'
+ MailPoetI18n.noDateFieldMatch + '">'
+ MailPoetI18n.emptyDate
+ MailPoet.I18n.t('noDateFieldMatch') + '">'
+ MailPoet.I18n.t('emptyDate')
+ '</span>';
preventNextStep = true;
return;
@@ -993,25 +995,25 @@ define(
+ ((date.getMinutes() < 10 ? '0' : '')
+ date.getMinutes()) + ' '
+ ((date.getHours() >= 12)
? MailPoetI18n.pm
: MailPoetI18n.am
? MailPoet.I18n.t('pm')
: MailPoet.I18n.t('am')
);
data[matchedColumn] +=
'<span class="mailpoet_data_match" title="'
+ MailPoetI18n.verifyDateMatch + '">'
+ MailPoet.I18n.t('verifyDateMatch') + '">'
+ date + '</span>';
}
else {
data[matchedColumn] +=
'<span class="mailpoet_data_match mailpoet_import_error" title="'
+ MailPoetI18n.noDateFieldMatch + '">'
+ MailPoetI18n.dateMatchError + '</span>';
+ MailPoet.I18n.t('noDateFieldMatch') + '">'
+ MailPoet.I18n.t('dateMatchError') + '</span>';
preventNextStep = true;
}
}
});
if (preventNextStep && !jQuery('.mailpoet_invalidDate').length) {
MailPoet.Notice.error(MailPoetI18n.columnContainsInvalidDate, {
MailPoet.Notice.error(MailPoet.I18n.t('columnContainsInvalidDate'), {
static: true,
timeout: noticeTimeout,
scroll: true,
@@ -1056,7 +1058,7 @@ define(
var columns = {},
queue = new jQuery.AsyncQueue(),
batchNumber = 0,
batchSize = 500,
batchSize = 2000,
timestamp = Date.now() / 1000,
subscribers = [],
importResults = {
@@ -1112,7 +1114,7 @@ define(
})
.error(function (error) {
importResults.errors.push(
MailPoetI18n.serverError + error.statusText.toLowerCase() + '.'
MailPoet.I18n.t('serverError') + error.statusText.toLowerCase() + '.'
);
queue.run();
});
@@ -1169,12 +1171,12 @@ define(
exportMenuElement = jQuery('span.mailpoet_export'),
importResults = {
created: (importData.step2.created)
? MailPoetI18n.subscribersCreated
? MailPoet.I18n.t('subscribersCreated')
.replace('%1$s', '<strong>' + importData.step2.created + '</strong>')
.replace('%2$s', '"' + importData.step2.segments.join('", "') + '"')
: false,
updated: (importData.step2.updated)
? MailPoetI18n.subscribersUpdated
? MailPoet.I18n.t('subscribersUpdated')
.replace('%1$s', '<strong>' + importData.step2.updated + '</strong>')
.replace('%2$s', '"' + importData.step2.segments.join('", "') + '"')
: false,
@@ -1210,4 +1212,4 @@ define(
Backbone.history.start();
}
});
});
});

View File

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

View File

@@ -1,11 +1,11 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, Route, IndexRoute, Link } from 'react-router'
import { Router, Route, IndexRoute, Link, useRouterHistory } from 'react-router'
import { createHashHistory } from 'history'
import SubscriberList from 'subscribers/list.jsx'
import SubscriberForm from 'subscribers/form.jsx'
import createHashHistory from 'history/lib/createHashHistory'
const history = createHashHistory({ queryKey: false })
const history = useRouterHistory(createHashHistory)({ queryKey: false });
const App = React.createClass({
render() {

View File

@@ -9,6 +9,7 @@ settings:
bootstrap: _bootstrap.php
colors: true
memory_limit: 1024M
log: true
extensions:
enabled:
- Codeception\Extension\RunFailed

View File

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

331
composer.lock generated
View File

@@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "db2edea5fb720fcb8013ac470e924c19",
"content-hash": "85119d1ccd5193b6b08fda305a2427e7",
"hash": "2bed8395d84740d7c0ae644a6c6216fd",
"content-hash": "7b66e221814f3d5839ed4faabd2f50ad",
"packages": [
{
"name": "cerdic/css-tidy",
@@ -401,16 +401,16 @@
},
{
"name": "soundasleep/html2text",
"version": "0.3.0",
"version": "0.3.1",
"source": {
"type": "git",
"url": "https://github.com/soundasleep/html2text.git",
"reference": "55be17ddbb7234650722f66816ba2b10f66e565f"
"reference": "2a5fd94bf58b653a5a9d777ac6a5593f82d087db"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/soundasleep/html2text/zipball/55be17ddbb7234650722f66816ba2b10f66e565f",
"reference": "55be17ddbb7234650722f66816ba2b10f66e565f",
"url": "https://api.github.com/repos/soundasleep/html2text/zipball/2a5fd94bf58b653a5a9d777ac6a5593f82d087db",
"reference": "2a5fd94bf58b653a5a9d777ac6a5593f82d087db",
"shasum": ""
},
"require": {
@@ -447,7 +447,7 @@
"php",
"text"
],
"time": "2015-12-18 02:30:26"
"time": "2016-02-25 00:00:36"
},
{
"name": "sunra/php-simple-html-dom-parser",
@@ -547,7 +547,7 @@
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.1.0",
"version": "v1.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
@@ -606,16 +606,16 @@
},
{
"name": "symfony/translation",
"version": "v2.8.2",
"version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
"reference": "bc0b666903944858f4ffec01c4e50c63e5c276c0"
"reference": "b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/bc0b666903944858f4ffec01c4e50c63e5c276c0",
"reference": "bc0b666903944858f4ffec01c4e50c63e5c276c0",
"url": "https://api.github.com/repos/symfony/translation/zipball/b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7",
"reference": "b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7",
"shasum": ""
},
"require": {
@@ -666,7 +666,7 @@
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
"time": "2016-01-03 15:33:41"
"time": "2016-02-02 09:49:18"
},
{
"name": "tburry/pquery",
@@ -785,41 +785,42 @@
"packages-dev": [
{
"name": "codeception/codeception",
"version": "2.1.6",
"version": "2.1.7",
"source": {
"type": "git",
"url": "https://github.com/Codeception/Codeception.git",
"reference": "b199941f5e59d1e7fd32d78296c8ab98db873d89"
"reference": "65971b0dee4972710365b6102154cd412a9bf7b1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/b199941f5e59d1e7fd32d78296c8ab98db873d89",
"reference": "b199941f5e59d1e7fd32d78296c8ab98db873d89",
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/65971b0dee4972710365b6102154cd412a9bf7b1",
"reference": "65971b0dee4972710365b6102154cd412a9bf7b1",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-mbstring": "*",
"facebook/webdriver": ">=1.0.1",
"facebook/webdriver": ">=1.0.1 <2.0",
"guzzlehttp/guzzle": ">=4.1.4 <7.0",
"guzzlehttp/psr7": "~1.0",
"php": ">=5.4.0",
"phpunit/phpunit": "~4.8.0",
"symfony/browser-kit": ">=2.4|<3.1",
"symfony/console": ">=2.4|<3.1",
"symfony/css-selector": ">=2.4|<3.1",
"symfony/dom-crawler": ">=2.4|<3.1",
"symfony/event-dispatcher": ">=2.4|<3.1",
"symfony/finder": ">=2.4|<3.1",
"symfony/yaml": ">=2.4|<3.1"
"php": ">=5.4.0 <8.0",
"phpunit/php-code-coverage": ">=2.1.3",
"phpunit/phpunit": ">4.8.20 <6.0",
"symfony/browser-kit": ">=2.5 <3.1",
"symfony/console": ">=2.5 <3.1",
"symfony/css-selector": ">=2.5 <3.1",
"symfony/dom-crawler": ">=2.5 <3.1",
"symfony/event-dispatcher": ">=2.5 <3.1",
"symfony/finder": ">=2.5 <3.1",
"symfony/yaml": ">=2.5 <3.1"
},
"require-dev": {
"codeception/specify": "~0.3",
"facebook/php-sdk-v4": "~4.0",
"facebook/php-sdk-v4": "~5.0",
"flow/jsonpath": "~0.2",
"monolog/monolog": "~1.8",
"pda/pheanstalk": "~2.0",
"videlalvaro/php-amqplib": "~2.4"
"php-amqplib/php-amqplib": "~2.4"
},
"suggest": {
"codeception/phpbuiltinserver": "Extension to start and stop PHP built-in web server for your tests",
@@ -861,7 +862,7 @@
"functional testing",
"unit testing"
],
"time": "2016-02-09 22:27:48"
"time": "2016-03-12 01:15:25"
},
{
"name": "codeception/verify",
@@ -898,33 +899,36 @@
},
{
"name": "codegyre/robo",
"version": "0.6.0",
"version": "0.7.1",
"source": {
"type": "git",
"url": "https://github.com/Codegyre/Robo.git",
"reference": "d18185f0494c854d36aa5ee0ad931ee23bbef552"
"reference": "1f4e0621fdc37521e2eaca67f33d1c4e240dc7d8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Codegyre/Robo/zipball/d18185f0494c854d36aa5ee0ad931ee23bbef552",
"reference": "d18185f0494c854d36aa5ee0ad931ee23bbef552",
"url": "https://api.github.com/repos/Codegyre/Robo/zipball/1f4e0621fdc37521e2eaca67f33d1c4e240dc7d8",
"reference": "1f4e0621fdc37521e2eaca67f33d1c4e240dc7d8",
"shasum": ""
},
"require": {
"henrikbjorn/lurker": "1.0.*@dev",
"henrikbjorn/lurker": "~1.0",
"php": ">=5.4.0",
"symfony/console": "~2.5",
"symfony/filesystem": "~2.5",
"symfony/finder": "~2.5",
"symfony/process": "~2.5"
"symfony/console": "~2.5|~3.0",
"symfony/filesystem": "~2.5|~3.0",
"symfony/finder": "~2.5|~3.0",
"symfony/process": "~2.5|~3.0"
},
"require-dev": {
"codeception/aspect-mock": "0.5.*",
"codeception/base": "~2.1",
"codeception/codeception": "2.1",
"codeception/aspect-mock": "0.5.4",
"codeception/base": "~2.1.5",
"codeception/verify": "0.2.*",
"natxet/cssmin": "~3.0",
"patchwork/jsqueeze": "~1.0"
"patchwork/jsqueeze": "~1.0",
"pear/archive_tar": "~1.0"
},
"suggest": {
"pear/archive_tar": "Allows tar archives to be created and extracted in taskPack and taskExtract, respectively."
},
"bin": [
"robo"
@@ -946,7 +950,7 @@
}
],
"description": "Modern task runner",
"time": "2015-10-30 11:29:52"
"time": "2016-02-25 19:28:51"
},
{
"name": "doctrine/instantiator",
@@ -1109,16 +1113,16 @@
},
{
"name": "guzzlehttp/promises",
"version": "1.0.3",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "b1e1c0d55f8083c71eda2c28c12a228d708294ea"
"reference": "bb9024c526b22f3fe6ae55a561fd70653d470aa8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/b1e1c0d55f8083c71eda2c28c12a228d708294ea",
"reference": "b1e1c0d55f8083c71eda2c28c12a228d708294ea",
"url": "https://api.github.com/repos/guzzle/promises/zipball/bb9024c526b22f3fe6ae55a561fd70653d470aa8",
"reference": "bb9024c526b22f3fe6ae55a561fd70653d470aa8",
"shasum": ""
},
"require": {
@@ -1156,20 +1160,20 @@
"keywords": [
"promise"
],
"time": "2015-10-15 22:28:00"
"time": "2016-03-08 01:15:46"
},
{
"name": "guzzlehttp/psr7",
"version": "1.2.2",
"version": "1.2.3",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "f5d04bdd2881ac89abde1fb78cc234bce24327bb"
"reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/f5d04bdd2881ac89abde1fb78cc234bce24327bb",
"reference": "f5d04bdd2881ac89abde1fb78cc234bce24327bb",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/2e89629ff057ebb49492ba08e6995d3a6a80021b",
"reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b",
"shasum": ""
},
"require": {
@@ -1214,20 +1218,20 @@
"stream",
"uri"
],
"time": "2016-01-23 01:23:02"
"time": "2016-02-18 21:54:00"
},
{
"name": "henrikbjorn/lurker",
"version": "1.0.0",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/flint/Lurker.git",
"reference": "a020d45b3bc37810aeafe27343c51af8a74c9419"
"reference": "ab45f9cefe600065cc3137a238217598d3a1d062"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/flint/Lurker/zipball/a020d45b3bc37810aeafe27343c51af8a74c9419",
"reference": "a020d45b3bc37810aeafe27343c51af8a74c9419",
"url": "https://api.github.com/repos/flint/Lurker/zipball/ab45f9cefe600065cc3137a238217598d3a1d062",
"reference": "ab45f9cefe600065cc3137a238217598d3a1d062",
"shasum": ""
},
"require": {
@@ -1255,18 +1259,16 @@
],
"authors": [
{
"name": "Henrik Bjornskov",
"email": "henrik@bjrnskov.dk",
"homepage": "http://henrik.bjrnskov.dk"
"name": "Yaroslav Kiliba",
"email": "om.dattaya@gmail.com"
},
{
"name": "Konstantin Kudryashov",
"email": "ever.zet@gmail.com",
"homepage": "http://everzet.com"
"email": "ever.zet@gmail.com"
},
{
"name": "Yaroslav Kiliba",
"email": "om.dattaya@gmail.com"
"name": "Henrik Bjrnskov",
"email": "henrik@bjrnskov.dk"
}
],
"description": "Resource Watcher.",
@@ -1275,7 +1277,7 @@
"resource",
"watching"
],
"time": "2013-05-24 06:47:29"
"time": "2015-10-27 09:19:19"
},
{
"name": "phpdocumentor/reflection-docblock",
@@ -1630,16 +1632,16 @@
},
{
"name": "phpunit/phpunit",
"version": "4.8.23",
"version": "4.8.24",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483"
"reference": "a1066c562c52900a142a0e2bbf0582994671385e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6e351261f9cd33daf205a131a1ba61c6d33bd483",
"reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1066c562c52900a142a0e2bbf0582994671385e",
"reference": "a1066c562c52900a142a0e2bbf0582994671385e",
"shasum": ""
},
"require": {
@@ -1698,7 +1700,7 @@
"testing",
"xunit"
],
"time": "2016-02-11 14:56:33"
"time": "2016-03-14 06:16:08"
},
{
"name": "phpunit/phpunit-mock-objects",
@@ -1974,16 +1976,16 @@
},
{
"name": "sebastian/environment",
"version": "1.3.3",
"version": "1.3.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
"reference": "6e7133793a8e5a5714a551a8324337374be209df"
"reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6e7133793a8e5a5714a551a8324337374be209df",
"reference": "6e7133793a8e5a5714a551a8324337374be209df",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
"reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
"shasum": ""
},
"require": {
@@ -2020,7 +2022,7 @@
"environment",
"hhvm"
],
"time": "2015-12-02 08:37:27"
"time": "2016-02-26 18:40:46"
},
{
"name": "sebastian/exporter",
@@ -2229,7 +2231,7 @@
},
{
"name": "symfony/browser-kit",
"version": "v3.0.2",
"version": "v3.0.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
@@ -2286,22 +2288,25 @@
},
{
"name": "symfony/config",
"version": "v2.8.2",
"version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
"reference": "41ee6c70758f40fa1dbf90d019ae0a66c4a09e74"
"reference": "0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/config/zipball/41ee6c70758f40fa1dbf90d019ae0a66c4a09e74",
"reference": "41ee6c70758f40fa1dbf90d019ae0a66c4a09e74",
"url": "https://api.github.com/repos/symfony/config/zipball/0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19",
"reference": "0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19",
"shasum": ""
},
"require": {
"php": ">=5.3.9",
"symfony/filesystem": "~2.3|~3.0.0"
},
"suggest": {
"symfony/yaml": "To use the yaml reference dumper"
},
"type": "library",
"extra": {
"branch-alias": {
@@ -2332,30 +2337,30 @@
],
"description": "Symfony Config Component",
"homepage": "https://symfony.com",
"time": "2016-01-03 15:33:41"
"time": "2016-02-22 16:12:45"
},
{
"name": "symfony/console",
"version": "v2.8.2",
"version": "v3.0.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "d0239fb42f98dd02e7d342f793c5d2cdee0c478d"
"reference": "2ed5e2706ce92313d120b8fe50d1063bcfd12e04"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/d0239fb42f98dd02e7d342f793c5d2cdee0c478d",
"reference": "d0239fb42f98dd02e7d342f793c5d2cdee0c478d",
"url": "https://api.github.com/repos/symfony/console/zipball/2ed5e2706ce92313d120b8fe50d1063bcfd12e04",
"reference": "2ed5e2706ce92313d120b8fe50d1063bcfd12e04",
"shasum": ""
},
"require": {
"php": ">=5.3.9",
"php": ">=5.5.9",
"symfony/polyfill-mbstring": "~1.0"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/event-dispatcher": "~2.1|~3.0.0",
"symfony/process": "~2.1|~3.0.0"
"symfony/event-dispatcher": "~2.8|~3.0",
"symfony/process": "~2.8|~3.0"
},
"suggest": {
"psr/log": "For using the console logger",
@@ -2365,7 +2370,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.8-dev"
"dev-master": "3.0-dev"
}
},
"autoload": {
@@ -2392,11 +2397,11 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2016-01-14 08:33:16"
"time": "2016-02-28 16:24:34"
},
{
"name": "symfony/css-selector",
"version": "v3.0.2",
"version": "v3.0.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
@@ -2449,16 +2454,16 @@
},
{
"name": "symfony/dom-crawler",
"version": "v3.0.2",
"version": "v3.0.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
"reference": "b693a9650aa004576b593ff2e91ae749dc90123d"
"reference": "981c8edb4538f88ba976ed44bdcaa683fce3d6c6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b693a9650aa004576b593ff2e91ae749dc90123d",
"reference": "b693a9650aa004576b593ff2e91ae749dc90123d",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/981c8edb4538f88ba976ed44bdcaa683fce3d6c6",
"reference": "981c8edb4538f88ba976ed44bdcaa683fce3d6c6",
"shasum": ""
},
"require": {
@@ -2501,20 +2506,20 @@
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
"time": "2016-01-25 09:56:57"
"time": "2016-02-28 16:24:34"
},
{
"name": "symfony/event-dispatcher",
"version": "v2.8.2",
"version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "ee278f7c851533e58ca307f66305ccb9188aceda"
"reference": "78c468665c9568c3faaa9c416a7134308f2d85c3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ee278f7c851533e58ca307f66305ccb9188aceda",
"reference": "ee278f7c851533e58ca307f66305ccb9188aceda",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/78c468665c9568c3faaa9c416a7134308f2d85c3",
"reference": "78c468665c9568c3faaa9c416a7134308f2d85c3",
"shasum": ""
},
"require": {
@@ -2561,20 +2566,20 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"time": "2016-01-13 10:28:07"
"time": "2016-01-27 05:14:19"
},
{
"name": "symfony/filesystem",
"version": "v2.8.2",
"version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "637b64d0ee10f44ae98dbad651b1ecdf35a11e8c"
"reference": "65cb36b6539b1d446527d60457248f30d045464d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/637b64d0ee10f44ae98dbad651b1ecdf35a11e8c",
"reference": "637b64d0ee10f44ae98dbad651b1ecdf35a11e8c",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/65cb36b6539b1d446527d60457248f30d045464d",
"reference": "65cb36b6539b1d446527d60457248f30d045464d",
"shasum": ""
},
"require": {
@@ -2610,29 +2615,29 @@
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
"time": "2016-01-13 10:28:07"
"time": "2016-02-22 15:02:30"
},
{
"name": "symfony/finder",
"version": "v2.8.2",
"version": "v3.0.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "c90fabdd97e431ee19b6383999cf35334dff27da"
"reference": "623bda0abd9aa29e529c8e9c08b3b84171914723"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/c90fabdd97e431ee19b6383999cf35334dff27da",
"reference": "c90fabdd97e431ee19b6383999cf35334dff27da",
"url": "https://api.github.com/repos/symfony/finder/zipball/623bda0abd9aa29e529c8e9c08b3b84171914723",
"reference": "623bda0abd9aa29e529c8e9c08b3b84171914723",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
"php": ">=5.5.9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.8-dev"
"dev-master": "3.0-dev"
}
},
"autoload": {
@@ -2659,20 +2664,20 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
"time": "2016-01-14 08:26:52"
"time": "2016-01-27 05:14:46"
},
{
"name": "symfony/form",
"version": "v2.8.2",
"version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/form.git",
"reference": "7fd5e4034cb8e215887136f5e176430bbf5ef085"
"reference": "25b71aec36879b4a84c2ea06603dbe7498b31f06"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/form/zipball/7fd5e4034cb8e215887136f5e176430bbf5ef085",
"reference": "7fd5e4034cb8e215887136f5e176430bbf5ef085",
"url": "https://api.github.com/repos/symfony/form/zipball/25b71aec36879b4a84c2ea06603dbe7498b31f06",
"reference": "25b71aec36879b4a84c2ea06603dbe7498b31f06",
"shasum": ""
},
"require": {
@@ -2733,20 +2738,20 @@
],
"description": "Symfony Form Component",
"homepage": "https://symfony.com",
"time": "2016-01-12 17:46:01"
"time": "2016-02-28 15:05:09"
},
{
"name": "symfony/intl",
"version": "v3.0.2",
"version": "v3.0.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/intl.git",
"reference": "7fa23b8f2ddd96260f0154946b69eb0f2c2ce7bc"
"reference": "cafee6f65148dab9058cdb6de5631222aca820d0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/intl/zipball/7fa23b8f2ddd96260f0154946b69eb0f2c2ce7bc",
"reference": "7fa23b8f2ddd96260f0154946b69eb0f2c2ce7bc",
"url": "https://api.github.com/repos/symfony/intl/zipball/cafee6f65148dab9058cdb6de5631222aca820d0",
"reference": "cafee6f65148dab9058cdb6de5631222aca820d0",
"shasum": ""
},
"require": {
@@ -2808,20 +2813,20 @@
"l10n",
"localization"
],
"time": "2016-01-27 05:14:46"
"time": "2016-02-23 15:16:06"
},
{
"name": "symfony/options-resolver",
"version": "v2.8.2",
"version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
"reference": "b98ca04f85240531b9ea8a0f00a21f2ecfbdfa51"
"reference": "d1e6e9182d9e5af6367bf85175e708f8b4a828c0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/b98ca04f85240531b9ea8a0f00a21f2ecfbdfa51",
"reference": "b98ca04f85240531b9ea8a0f00a21f2ecfbdfa51",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/d1e6e9182d9e5af6367bf85175e708f8b4a828c0",
"reference": "d1e6e9182d9e5af6367bf85175e708f8b4a828c0",
"shasum": ""
},
"require": {
@@ -2862,26 +2867,29 @@
"configuration",
"options"
],
"time": "2016-01-03 15:33:41"
"time": "2016-01-21 09:05:51"
},
{
"name": "symfony/polyfill-intl-icu",
"version": "v1.1.0",
"version": "v1.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-icu.git",
"reference": "66b0bb4abda229bc073eff6bbc8f2685bdaac165"
"reference": "8328069d9f5322f0e7b3c3518485acfdc94c3942"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/66b0bb4abda229bc073eff6bbc8f2685bdaac165",
"reference": "66b0bb4abda229bc073eff6bbc8f2685bdaac165",
"url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/8328069d9f5322f0e7b3c3518485acfdc94c3942",
"reference": "8328069d9f5322f0e7b3c3518485acfdc94c3942",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"symfony/intl": "~2.3|~3.0"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
@@ -2917,29 +2925,29 @@
"portable",
"shim"
],
"time": "2016-01-20 09:13:37"
"time": "2016-02-26 16:18:12"
},
{
"name": "symfony/process",
"version": "v2.8.2",
"version": "v3.0.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "6f1979c3b0f4c22c77a8a8971afaa7dd07f082ac"
"reference": "dfecef47506179db2501430e732adbf3793099c8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/6f1979c3b0f4c22c77a8a8971afaa7dd07f082ac",
"reference": "6f1979c3b0f4c22c77a8a8971afaa7dd07f082ac",
"url": "https://api.github.com/repos/symfony/process/zipball/dfecef47506179db2501430e732adbf3793099c8",
"reference": "dfecef47506179db2501430e732adbf3793099c8",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
"php": ">=5.5.9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.8-dev"
"dev-master": "3.0-dev"
}
},
"autoload": {
@@ -2966,20 +2974,20 @@
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
"time": "2016-01-06 09:59:23"
"time": "2016-02-02 13:44:19"
},
{
"name": "symfony/property-access",
"version": "v3.0.2",
"version": "v3.0.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/property-access.git",
"reference": "95363dbabd606e404b6c75095669993bf7ddae4b"
"reference": "f138bcc0cdaf6a6aba99ecab20d235b394f46eba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/property-access/zipball/95363dbabd606e404b6c75095669993bf7ddae4b",
"reference": "95363dbabd606e404b6c75095669993bf7ddae4b",
"url": "https://api.github.com/repos/symfony/property-access/zipball/f138bcc0cdaf6a6aba99ecab20d235b394f46eba",
"reference": "f138bcc0cdaf6a6aba99ecab20d235b394f46eba",
"shasum": ""
},
"require": {
@@ -3026,20 +3034,20 @@
"property path",
"reflection"
],
"time": "2016-01-03 15:35:16"
"time": "2016-02-13 09:23:44"
},
{
"name": "symfony/routing",
"version": "v2.8.2",
"version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
"reference": "5451a8a1874fd4e6a4dd347ea611d86cd8441735"
"reference": "ae38e64bae52753c0f4a1a6583f5ba9bb2b742ab"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/routing/zipball/5451a8a1874fd4e6a4dd347ea611d86cd8441735",
"reference": "5451a8a1874fd4e6a4dd347ea611d86cd8441735",
"url": "https://api.github.com/repos/symfony/routing/zipball/ae38e64bae52753c0f4a1a6583f5ba9bb2b742ab",
"reference": "ae38e64bae52753c0f4a1a6583f5ba9bb2b742ab",
"shasum": ""
},
"require": {
@@ -3062,6 +3070,7 @@
"symfony/config": "For using the all-in-one router or any loader",
"symfony/dependency-injection": "For loading routes from a service",
"symfony/expression-language": "For using expression matching",
"symfony/http-foundation": "For using a Symfony Request object",
"symfony/yaml": "For using the YAML loader"
},
"type": "library",
@@ -3100,20 +3109,20 @@
"uri",
"url"
],
"time": "2016-01-11 16:43:36"
"time": "2016-02-04 13:53:00"
},
{
"name": "symfony/twig-bridge",
"version": "v2.8.2",
"version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/twig-bridge.git",
"reference": "e33b512de4b769a1c728cd6775e22668ae89fca9"
"reference": "ba898e4714734948dc00c4d776e1d6be165ff8c4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/twig-bridge/zipball/e33b512de4b769a1c728cd6775e22668ae89fca9",
"reference": "e33b512de4b769a1c728cd6775e22668ae89fca9",
"url": "https://api.github.com/repos/symfony/twig-bridge/zipball/ba898e4714734948dc00c4d776e1d6be165ff8c4",
"reference": "ba898e4714734948dc00c4d776e1d6be165ff8c4",
"shasum": ""
},
"require": {
@@ -3181,20 +3190,20 @@
],
"description": "Symfony Twig Bridge",
"homepage": "https://symfony.com",
"time": "2016-01-12 17:46:01"
"time": "2016-02-22 15:02:30"
},
{
"name": "symfony/yaml",
"version": "v3.0.2",
"version": "v3.0.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "3cf0709d7fe936e97bee9e954382e449003f1d9a"
"reference": "b5ba64cd67ecd6887f63868fa781ca094bd1377c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/3cf0709d7fe936e97bee9e954382e449003f1d9a",
"reference": "3cf0709d7fe936e97bee9e954382e449003f1d9a",
"url": "https://api.github.com/repos/symfony/yaml/zipball/b5ba64cd67ecd6887f63868fa781ca094bd1377c",
"reference": "b5ba64cd67ecd6887f63868fa781ca094bd1377c",
"shasum": ""
},
"require": {
@@ -3230,7 +3239,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2016-02-02 13:44:19"
"time": "2016-02-23 15:16:06"
},
{
"name": "twig/extensions",

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -13,7 +13,7 @@ class Env {
static $assets_path;
static $assets_url;
static $temp_path;
static $temp_URL;
static $temp_url;
static $languages_path;
static $lib_path;
static $plugin_prefix;
@@ -26,6 +26,7 @@ class Env {
static $db_username;
static $db_password;
static $db_charset;
static $db_timezone_offset;
static function init($file, $version) {
global $wpdb;
@@ -37,8 +38,11 @@ class Env {
self::$assets_path = self::$path . '/assets';
self::$assets_url = plugins_url('/assets', $file);
$wp_upload_dir = wp_upload_dir();
self::$temp_path = $wp_upload_dir['path'];
self::$temp_URL = $wp_upload_dir['url'];
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';
self::$plugin_prefix = 'mailpoet_';
@@ -58,6 +62,7 @@ class Env {
self::$db_password = DB_PASSWORD;
self::$db_charset = $wpdb->get_charset_collate();
self::$db_source_name = self::dbSourceName(self::$db_host, self::$db_socket, self::$db_port);
self::$db_timezone_offset = self::getDbTimezoneOffset();
}
private static function dbSourceName($host, $socket, $port) {
@@ -73,4 +78,13 @@ class Env {
);
return implode('', $source_name);
}
private static function getDbTimezoneOffset() {
$mins = get_option('gmt_offset') * 60;
$sgn = ($mins < 0 ? -1 : 1);
$mins = abs($mins);
$hrs = floor($mins / 60);
$mins -= $hrs * 60;
return sprintf('%+d:%02d', $hrs * $sgn, $mins);
}
}

View File

@@ -1,5 +1,7 @@
<?php
namespace MailPoet\Config;
use MailPoet\Cron\Workers\Scheduler;
use MailPoet\Cron\Workers\SendingQueue;
use \MailPoet\Models\Setting;
class Hooks {
@@ -7,13 +9,13 @@ class Hooks {
}
function init() {
$this->setupSubscribe();
$this->setupWPUsers();
$this->setupImageSize();
$this->setupListing();
$this->setupSubscriptionEvents();
}
function setupSubscribe() {
function setupSubscriptionEvents() {
$subscribe = Setting::getValue('subscribe', array());
// Subscribe in comments
if(
@@ -95,7 +97,7 @@ class Hooks {
add_action(
'profile_update',
'\MailPoet\Segments\WP::synchronizeUser',
1
1, 2
);
add_action(
'delete_user',
@@ -144,4 +146,4 @@ class Hooks {
return $status;
}
}
}
}

View File

@@ -4,8 +4,6 @@ namespace MailPoet\Config;
use MailPoet\Models;
use MailPoet\Cron\Supervisor;
use MailPoet\Router;
use MailPoet\Models\Setting;
use MailPoet\Settings\Pages;
if(!defined('ABSPATH')) exit;
@@ -26,6 +24,7 @@ class Initializer {
register_activation_hook(Env::$file, array($this, 'runPopulator'));
add_action('plugins_loaded', array($this, 'setup'));
add_action('init', array($this, 'onInit'));
add_action('widgets_init', array($this, 'setupWidget'));
}
@@ -34,16 +33,13 @@ class Initializer {
$this->setupRenderer();
$this->setupLocalizer();
$this->setupMenu();
$this->setupRouter();
$this->setupPermissions();
$this->setupPublicAPI();
$this->setupAnalytics();
$this->setupChangelog();
$this->runQueueSupervisor();
$this->setupShortcodes();
$this->setupHooks();
$this->setupPages();
$this->setupImages();
$this->runQueueSupervisor();
} catch(\Exception $e) {
// if anything goes wrong during init
// automatically deactivate the plugin
@@ -51,46 +47,66 @@ class Initializer {
}
}
function onInit() {
$this->setupRouter();
$this->setupPublicAPI();
$this->setupPages();
}
function setupDB() {
\ORM::configure(Env::$db_source_name);
\ORM::configure('username', Env::$db_username);
\ORM::configure('password', Env::$db_password);
\ORM::configure('logging', WP_DEBUG);
\ORM::configure('logger', function($query, $time) {
// error_log("\n".$query."\n");
});
\ORM::configure('driver_options', array(
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET TIME_ZONE = "+00:00"'
\PDO::MYSQL_ATTR_INIT_COMMAND =>
'SET TIME_ZONE = "' . Env::$db_timezone_offset. '"'
));
$subscribers = Env::$db_prefix . 'subscribers';
$settings = Env::$db_prefix . 'settings';
$newsletters = Env::$db_prefix . 'newsletters';
$newsletter_templates = Env::$db_prefix . 'newsletter_templates';
$segments = Env::$db_prefix . 'segments';
$forms = Env::$db_prefix . 'forms';
$subscriber_segment = Env::$db_prefix . 'subscriber_segment';
$newsletter_segment = Env::$db_prefix . 'newsletter_segment';
$custom_fields = Env::$db_prefix . 'custom_fields';
$subscribers = Env::$db_prefix . 'subscribers';
$subscriber_segment = Env::$db_prefix . 'subscriber_segment';
$subscriber_custom_field = Env::$db_prefix . 'subscriber_custom_field';
$newsletter_segment = Env::$db_prefix . 'newsletter_segment';
$sending_queues = Env::$db_prefix . 'sending_queues';
$newsletters = Env::$db_prefix . 'newsletters';
$newsletter_templates = Env::$db_prefix . 'newsletter_templates';
$newsletter_option_fields = Env::$db_prefix . 'newsletter_option_fields';
$newsletter_option = Env::$db_prefix . 'newsletter_option';
$sending_queues = Env::$db_prefix . 'sending_queues';
$newsletter_statistics = Env::$db_prefix . 'newsletter_statistics';
$newsletter_links = Env::$db_prefix . 'newsletter_links';
$statistics_newsletters = Env::$db_prefix . 'statistics_newsletters';
$statistics_clicks = Env::$db_prefix . 'statistics_clicks';
$statistics_opens = Env::$db_prefix . 'statistics_opens';
$statistics_unsubscribes = Env::$db_prefix . 'statistics_unsubscribes';
$statistics_forms = Env::$db_prefix . 'statistics_forms';
define('MP_SUBSCRIBERS_TABLE', $subscribers);
define('MP_SETTINGS_TABLE', $settings);
define('MP_NEWSLETTERS_TABLE', $newsletters);
define('MP_SEGMENTS_TABLE', $segments);
define('MP_FORMS_TABLE', $forms);
define('MP_CUSTOM_FIELDS_TABLE', $custom_fields);
define('MP_SUBSCRIBERS_TABLE', $subscribers);
define('MP_SUBSCRIBER_SEGMENT_TABLE', $subscriber_segment);
define('MP_SUBSCRIBER_CUSTOM_FIELD_TABLE', $subscriber_custom_field);
define('MP_SENDING_QUEUES_TABLE', $sending_queues);
define('MP_NEWSLETTERS_TABLE', $newsletters);
define('MP_NEWSLETTER_TEMPLATES_TABLE', $newsletter_templates);
define('MP_NEWSLETTER_SEGMENT_TABLE', $newsletter_segment);
define('MP_CUSTOM_FIELDS_TABLE', $custom_fields);
define('MP_SUBSCRIBER_CUSTOM_FIELD_TABLE', $subscriber_custom_field);
define('MP_NEWSLETTER_OPTION_FIELDS_TABLE', $newsletter_option_fields);
define('MP_NEWSLETTER_LINKS_TABLE', $newsletter_links);
define('MP_NEWSLETTER_OPTION_TABLE', $newsletter_option);
define('MP_SENDING_QUEUES_TABLE', $sending_queues);
define('MP_NEWSLETTER_STATISTICS_TABLE', $newsletter_statistics);
define('MP_STATISTICS_NEWSLETTERS_TABLE', $statistics_newsletters);
define('MP_STATISTICS_CLICKS_TABLE', $statistics_clicks);
define('MP_STATISTICS_OPENS_TABLE', $statistics_opens);
define('MP_STATISTICS_UNSUBSCRIBES_TABLE', $statistics_unsubscribes);
define('MP_STATISTICS_FORMS_TABLE', $statistics_forms);
}
function runMigrator() {
@@ -124,7 +140,7 @@ class Initializer {
}
function setupWidget() {
$widget = new Widget();
$widget = new Widget($this->renderer);
$widget->init();
}
@@ -144,7 +160,7 @@ class Initializer {
}
function setupPages() {
$pages = new Pages();
$pages = new \MailPoet\Settings\Pages();
$pages->init();
}
@@ -175,4 +191,4 @@ class Initializer {
function setupImages() {
add_image_size('mailpoet_newsletter_max', 1320);
}
}
}

View File

@@ -364,9 +364,11 @@ class Menu {
$data = array();
$data['segments'] = Segment::findArray();
$data['segments'] = Segment::getSegmentsWithSubscriberCount();
$data['settings'] = Setting::getAll();
$data['roles'] = $wp_roles->get_names();
$data['roles']['mailpoet_all'] = __('In any WordPress role');
echo $this->renderer->render('newsletters.html', $data);
}
@@ -380,11 +382,12 @@ class Menu {
$data = array(
'customFields' => $custom_fields,
'settings' => Setting::getAll(),
);
wp_enqueue_media();
wp_enqueue_script('tinymce-wplink', includes_url('js/tinymce/plugins/wplink/plugin.js'));
wp_enqueue_style('editor', includes_url('css/editor.css'));
echo $this->renderer->render('newsletter/form.html', $data);
echo $this->renderer->render('newsletter/editor.html', $data);
}
function import() {

View File

@@ -10,20 +10,25 @@ class Migrator {
$this->prefix = Env::$db_prefix;
$this->charset = Env::$db_charset;
$this->models = array(
'subscribers',
'segments',
'settings',
'custom_fields',
'sending_queues',
'subscribers',
'subscriber_segment',
'subscriber_custom_field',
'newsletters',
'newsletter_templates',
'segments',
'subscriber_segment',
'newsletter_segment',
'custom_fields',
'subscriber_custom_field',
'newsletter_option_fields',
'newsletter_option',
'sending_queues',
'newsletter_statistics',
'forms'
'newsletter_segment',
'newsletter_links',
'forms',
'statistics_newsletters',
'statistics_clicks',
'statistics_opens',
'statistics_unsubscribes',
'statistics_forms'
);
}
@@ -49,6 +54,72 @@ class Migrator {
array_map($drop_table, $this->models);
}
function segments() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,',
'type varchar(90) NOT NULL DEFAULT "default",',
'description varchar(250) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name (name)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function settings() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(20) NOT NULL,',
'value longtext,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name (name)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function custom_fields() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,',
'type varchar(90) NOT NULL,',
'params longtext NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name (name)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function sending_queues() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'newsletter_rendered_body longtext,',
'newsletter_rendered_body_hash varchar(250) NULL DEFAULT NULL,',
'newsletter_rendered_subject varchar(250) NULL DEFAULT NULL,',
'subscribers longtext,',
'status varchar(12) NULL DEFAULT NULL,',
'priority mediumint(9) NOT NULL DEFAULT 0,',
'count_total mediumint(9) NOT NULL DEFAULT 0,',
'count_processed mediumint(9) NOT NULL DEFAULT 0,',
'count_to_process mediumint(9) NOT NULL DEFAULT 0,',
'count_failed mediumint(9) NOT NULL DEFAULT 0,',
'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,',
'PRIMARY KEY (id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function subscribers() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
@@ -66,15 +137,30 @@ class Migrator {
return $this->sqlify(__FUNCTION__, $attributes);
}
function settings() {
function subscriber_segment() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(20) NOT NULL,',
'value longtext,',
'subscriber_id mediumint(9) NOT NULL,',
'segment_id mediumint(9) NOT NULL,',
'status varchar(12) NOT NULL DEFAULT "subscribed",',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name (name)'
'UNIQUE KEY subscriber_segment (subscriber_id,segment_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function subscriber_custom_field() {
$attributes = array(
'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,',
'PRIMARY KEY (id),',
'UNIQUE KEY subscriber_id_custom_field_id (subscriber_id,custom_field_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
@@ -113,76 +199,6 @@ class Migrator {
return $this->sqlify(__FUNCTION__, $attributes);
}
function segments() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,',
'type varchar(90) NOT NULL DEFAULT "default",',
'description varchar(250) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name (name)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function subscriber_segment() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'subscriber_id mediumint(9) NOT NULL,',
'segment_id mediumint(9) NOT NULL,',
'status varchar(12) NOT NULL DEFAULT "subscribed",',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY subscriber_segment (subscriber_id,segment_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function newsletter_segment() {
$attributes = array(
'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,',
'PRIMARY KEY (id),',
'UNIQUE KEY newsletter_segment (newsletter_id,segment_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function custom_fields() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'name varchar(90) NOT NULL,',
'type varchar(90) NOT NULL,',
'params longtext NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),',
'UNIQUE KEY name (name)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function subscriber_custom_field() {
$attributes = array(
'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,',
'PRIMARY KEY (id),',
'UNIQUE KEY subscriber_id_custom_field_id (subscriber_id,custom_field_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function newsletter_option_fields() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
@@ -210,33 +226,28 @@ class Migrator {
return $this->sqlify(__FUNCTION__, $attributes);
}
function sending_queues() {
function newsletter_segment() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'subscribers longtext,',
'status varchar(12) NULL DEFAULT NULL,',
'priority mediumint(9) NOT NULL DEFAULT 0,',
'count_total mediumint(9) NOT NULL DEFAULT 0,',
'count_processed mediumint(9) NOT NULL DEFAULT 0,',
'count_to_process mediumint(9) NOT NULL DEFAULT 0,',
'count_failed mediumint(9) NOT NULL DEFAULT 0,',
'processed_at TIMESTAMP NOT NULL DEFAULT 0,',
'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,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'PRIMARY KEY (id)',
'PRIMARY KEY (id),',
'UNIQUE KEY newsletter_segment (newsletter_id,segment_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function newsletter_statistics() {
function newsletter_links() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'subscriber_id mediumint(9) NOT NULL,',
'queue_id mediumint(9) NOT NULL,',
'sent_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'url varchar(255) NOT NULL,',
'hash varchar(20) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
@@ -257,6 +268,69 @@ class Migrator {
return $this->sqlify(__FUNCTION__, $attributes);
}
function statistics_newsletters() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'subscriber_id mediumint(9) NOT NULL,',
'queue_id mediumint(9) NOT NULL,',
'sent_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function statistics_clicks() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'subscriber_id mediumint(9) NOT NULL,',
'queue_id mediumint(9) NOT NULL,',
'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,',
'PRIMARY KEY (id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function statistics_opens() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'subscriber_id mediumint(9) NOT NULL,',
'queue_id mediumint(9) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'PRIMARY KEY (id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function statistics_unsubscribes() {
$attributes = array(
'id mediumint(9) NOT NULL AUTO_INCREMENT,',
'newsletter_id mediumint(9) NOT NULL,',
'subscriber_id mediumint(9) NOT NULL,',
'queue_id mediumint(9) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'PRIMARY KEY (id)',
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function statistics_forms() {
$attributes = array(
'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,',
'PRIMARY KEY (id),',
'UNIQUE KEY form_subscriber (form_id,subscriber_id)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
private function sqlify($model, $attributes) {
$table = $this->prefix . $model;
@@ -267,4 +341,4 @@ class Migrator {
return implode("\n", $sql);
}
}
}

View File

@@ -70,35 +70,41 @@ class Populator {
if($page === null) {
$mailpoet_page_id = Pages::createMailPoetPage();
Setting::setValue('subscription.page', $mailpoet_page_id);
} else {
$mailpoet_page_id = (int)$page->ID;
}
$subscription = Setting::getValue('subscription.pages', array());
if(empty($subscription)) {
Setting::setValue('subscription.pages', array(
'unsubscribe' => $mailpoet_page_id,
'manage' => $mailpoet_page_id,
'confirmation' => $mailpoet_page_id
));
}
}
private function createDefaultSettings() {
$current_user = wp_get_current_user();
// user name
$user_name = '';
if($current_user->user_firstname) {
$user_name = $current_user->user_firstname;
}
if($current_user->user_lastname) {
if($user_name) {
$user_name .= ' '.$current_user->user_lastname;
}
}
if(!$user_name) {
$user_name = $current_user->display_name;
}
// default sender info based on current user
$sender = array(
'name' => $current_user->display_name,
'address' => $current_user->user_email
);
// default from name & address
Setting::setValue('sender', array(
'name' => $user_name,
'address' => $current_user->user_email
));
Setting::setValue('sender', $sender);
// enable signup confirmation by default
Setting::setValue('signup_confirmation.enabled', true);
Setting::setValue('signup_confirmation', array(
'enabled' => true,
'from' => array(
'name' => get_option('blogname'),
'address' => get_option('admin_email')
),
'reply_to' => $sender
));
}
private function createDefaultSegments() {
@@ -175,6 +181,14 @@ class Populator {
'name' => 'nthWeekDay',
'newsletter_type' => 'notification',
),
array(
'name' => 'schedule',
'newsletter_type' => 'notification',
),
array(
'name' => 'segments',
'newsletter_type' => 'notification',
)
);
}

View File

@@ -142,7 +142,7 @@ class BlankTemplate {
"blocks" => array(
array(
"type" => "footer",
"text" => "<a href=\"[unsubscribeUrl]\">Unsubscribe</a> | <a href=\"[manageSubscriptionUrl]\">Manage subscription</a><br /><b>Add your postal address here!</b>",
"text" => "<a href=\"[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=\"[viewInBrowserUrl]\">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=\"[unsubscribeUrl]\">Unsubscribe</a> | <a href=\"[manageSubscriptionUrl]\">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=\"[unsubscribeUrl]\">Unsubscribe</a> | <a href=\"[manageSubscriptionUrl]\">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=\"[viewInBrowserUrl]\">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=\"[unsubscribeUrl]\">Unsubscribe</a> | <a href=\"[manageSubscriptionUrl]\">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,44 +2,68 @@
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\Util\Helpers;
if(!defined('ABSPATH')) exit;
class PublicAPI {
public $api;
public $section;
public $endpoint;
public $action;
public $request_payload;
public $data;
function __construct() {
# http://example.com/?mailpoet-api&section=&action=&request_payload=
$this->api = isset($_GET['mailpoet-api']) ? true : false;
$this->section = isset($_GET['section']) ? $_GET['section'] : false;
# http://example.com/?mailpoet&endpoint=&action=&data=
$this->api = isset($_GET['mailpoet']) ? true : false;
$this->endpoint = isset($_GET['endpoint']) ?
Helpers::underscoreToCamelCase($_GET['endpoint']) :
false;
$this->action = isset($_GET['action']) ?
Helpers::underscoreToCamelCase($_GET['action']) :
false;
$this->request_payload = isset($_GET['request_payload']) ?
unserialize(base64_decode($_GET['request_payload'])) :
$this->data = isset($_GET['data']) ?
unserialize(base64_decode($_GET['data'])) :
false;
}
function init() {
if(!$this->api && !$this->section) return;
$this->_checkAndCallMethod($this, $this->section, $terminate = true);
if(!$this->api && !$this->endpoint) return;
$this->_checkAndCallMethod($this, $this->endpoint, $terminate_request = true);
}
function queue() {
try {
$queue = new Daemon($this->request_payload);
$this->_checkAndCallMethod($queue, $this->action);
} catch(\Exception $e) {
}
$queue = new Daemon($this->data);
$this->_checkAndCallMethod($queue, $this->action);
}
private function _checkAndCallMethod($class, $method, $terminate = false) {
function subscription() {
$subscription = new Subscription\Pages($this->action, $this->data);
$this->_checkAndCallMethod($subscription, $this->action);
}
function track() {
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) {
if(!method_exists($class, $method)) {
if(!$terminate) return;
if(!$terminate_request) return;
header('HTTP/1.0 404 Not Found');
exit;
}

View File

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

View File

@@ -3,6 +3,7 @@ namespace MailPoet\Config;
use \MailPoet\Models\Newsletter;
use \MailPoet\Models\Subscriber;
use \MailPoet\Models\SubscriberSegment;
use \MailPoet\Subscription;
class Shortcodes {
function __construct() {

View File

@@ -1,11 +1,17 @@
<?php
namespace MailPoet\Config;
use \MailPoet\Util\Security;
use \MailPoet\Models\Form;
if(!defined('ABSPATH')) exit;
class Widget {
function __construct() {
private $renderer = null;
function __construct($renderer = null) {
if($renderer !== null) {
$this->renderer = $renderer;
}
}
function init() {
@@ -13,11 +19,67 @@ class Widget {
if(!is_admin()) {
$this->setupDependencies();
$this->setupIframe();
} else {
$this->setupAdminDependencies();
}
}
function setupIframe() {
$form_id = (isset($_GET['mailpoet_form_iframe']) ? (int)$_GET['mailpoet_form_iframe'] : 0);
if($form_id > 0) {
$form = Form::findOne($form_id);
if($form !== false) {
$form_widget = new \MailPoet\Form\Widget();
$form_html = $form_widget->widget(array(
'form' => $form_id,
'form_type' => 'iframe'
));
// capture javascripts
ob_start();
wp_print_scripts('jquery');
wp_print_scripts('mailpoet_vendor');
wp_print_scripts('mailpoet_public');
$scripts = ob_get_contents();
ob_end_clean();
// language attributes
$language_attributes = array();
$is_rtl = (bool)(function_exists('is_rtl') && is_rtl());
if($is_rtl) {
$language_attributes[] = 'dir="rtl"';
}
if($lang = get_bloginfo('language')) {
if(get_option('html_type') === 'text/html') {
$language_attributes[] = "lang=\"$lang\"";
}
}
$language_attributes = apply_filters(
'language_attributes', implode(' ', $language_attributes)
);
$data = array(
'language_attributes' => $language_attributes,
'scripts' => $scripts,
'form' => $form_html,
'mailpoet_form' => array(
'ajax_url' => admin_url('admin-ajax.php', 'absolute'),
'is_rtl' => $is_rtl,
'token' => Security::generateToken()
)
);
echo $this->renderer->render('form/iframe.html', $data);
}
exit();
}
}
function registerWidget() {
register_widget('\MailPoet\Form\Widget');
}

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,10 +37,10 @@ class CronHelper {
return Security::generateRandomString();
}
static function accessDaemon($token, $timeout = self::daemon_request_timeout) {
$payload = serialize(array('token' => $token));
$url = '/?mailpoet-api&section=queue&action=run&request_payload=' .
base64_encode($payload);
static function accessDaemon($token, $timeout = self::DAEMON_REQUEST_TIMEOUT) {
$data = serialize(array('token' => $token));
$url = '/?mailpoet&endpoint=queue&action=run&data=' .
base64_encode($data);
$args = array(
'timeout' => $timeout,
'user-agent' => 'MailPoet (www.mailpoet.com) Cron'
@@ -68,4 +68,11 @@ class CronHelper {
// throw an error if all connection attempts failed
throw new \Exception(__('Site URL is unreachable.'));
}
static function checkExecutionTimer($timer) {
$elapsed_time = microtime(true) - $timer;
if($elapsed_time >= self::DAEMON_EXECUTION_LIMIT) {
throw new \Exception(__('Maximum execution time reached.'));
}
}
}

View File

@@ -1,6 +1,7 @@
<?php
namespace MailPoet\Cron;
use MailPoet\Cron\Workers\Scheduler;
use MailPoet\Cron\Workers\SendingQueue;
require_once(ABSPATH . 'wp-includes/pluggable.php');
@@ -9,50 +10,58 @@ if(!defined('ABSPATH')) exit;
class Daemon {
public $daemon;
public $request_payload;
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($request_payload = array()) {
set_time_limit(0);
function __construct($data) {
if(empty($data)) $this->abortWithError(__('Invalid or missing cron data.'));
ignore_user_abort();
$this->daemon = CronHelper::getDaemon();
$this->token = CronHelper::createToken();
$this->request_payload = $request_payload;
$this->data = $data;
$this->timer = microtime(true);
}
function run() {
$daemon = $this->daemon;
set_time_limit(0);
if(!$daemon) {
$this->abortWithError(__('Daemon does not exist.'));
}
if(!isset($this->request_payload['token']) ||
$this->request_payload['token'] !== $daemon['token']
if(!isset($this->data['token']) ||
$this->data['token'] !== $daemon['token']
) {
$this->abortWithError(__('Invalid or missing token.'));
}
$this->abortIfStopped($daemon);
try {
$sending_queue = new SendingQueue($this->timer);
$sending_queue->process();
$scheduler = new Scheduler();
$scheduler->process($this->timer);
$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
$daemon = CronHelper::getDaemon();
if(!$daemon || $daemon['token'] !== $this->request_payload['token']) {
if(!$daemon || $daemon['token'] !== $this->data['token']) {
exit;
}
$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);
@@ -60,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_STOPPING;
CronHelper::saveDaemon($daemon);
exit;
}
@@ -73,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,7 +23,7 @@ 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']);
@@ -31,17 +31,17 @@ class Supervisor {
$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

@@ -0,0 +1,168 @@
<?php
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;
require_once(ABSPATH . 'wp-includes/pluggable.php');
if(!defined('ABSPATH')) exit;
class Scheduler {
public $timer;
function __construct($timer = false) {
$this->timer = ($timer) ? $timer : microtime(true);
CronHelper::checkExecutionTimer($this->timer);
}
function process() {
$scheduled_queues = SendingQueue::where('status', 'scheduled')
->whereLte('scheduled_at', Carbon::createFromTimestamp(current_time('timestamp')))
->findMany();
if(!count($scheduled_queues)) return;
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') {
$this->processWelcomeNewsletter($newsletter, $queue);
}
else if($newsletter->type === 'notification') {
$this->processPostNotificationNewsletter($newsletter, $queue);
}
CronHelper::checkExecutionTimer($this->timer);
}
}
function processWelcomeNewsletter($newsletter, $queue) {
$subscriber = unserialize($queue->subscribers);
$subscriber_id = $subscriber['to_process'][0];
if($newsletter->event === 'segment') {
if ($this->verifyMailPoetSubscriber($subscriber_id, $newsletter, $queue) === false) {
return;
}
}
else if($newsletter->event === 'user') {
if ($this->verifyWPSubscriber($subscriber_id, $newsletter) === false) {
$queue->delete();
return;
}
}
$queue->status = null;
$queue->save();
}
function processPostNotificationNewsletter($newsletter, $queue) {
$next_run_date = $this->getQueueNextRunDate($newsletter->schedule);
$segments = unserialize($newsletter->segments);
$subscribers = Subscriber::getSubscribedInSegments($segments)
->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();
return;
}
// update current queue
$queue->subscribers = serialize(
array(
'to_process' => $subscribers
)
);
$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) {
// 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) {
$queue->delete();
return false;
}
// check if subscriber is confirmed (subscribed)
$subscriber = $subscriber_in_segment->subscriber()->findOne();
if ($subscriber->status !== 'subscribed') {
// reschedule delivery in 5 minutes
$scheduled_at = Carbon::createFromTimestamp(current_time('timestamp'));
$queue->scheduled_at = $scheduled_at->addMinutes(5);
$queue->save();
return false;
}
return true;
}
private function verifyWPSubscriber($subscriber_id, $newsletter) {
// check if user has the proper role
$subscriber = Subscriber::findOne($subscriber_id);
if(!$subscriber || $subscriber->wp_user_id === null) {
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'])) {
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);
});
}
$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');
}
}

View File

@@ -4,9 +4,11 @@ namespace MailPoet\Cron\Workers;
use MailPoet\Cron\CronHelper;
use MailPoet\Mailer\Mailer;
use MailPoet\Models\Newsletter;
use MailPoet\Models\NewsletterStatistics;
use MailPoet\Models\Setting;
use MailPoet\Models\StatisticsNewsletters;
use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\Links\Links;
use MailPoet\Newsletter\Renderer\PostProcess\OpenTracking;
use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Newsletter\Shortcodes\Shortcodes;
use MailPoet\Util\Helpers;
@@ -18,7 +20,8 @@ class SendingQueue {
public $mta_log;
public $processing_method;
private $timer;
const batch_size = 50;
const BATCH_SIZE = 50;
const DIVIDER = '***MailPoet***';
function __construct($timer = false) {
$this->mta_config = $this->getMailerConfig();
@@ -27,14 +30,18 @@ class SendingQueue {
'processBulkSubscribers' :
'processIndividualSubscriber';
$this->timer = ($timer) ? $timer : microtime(true);
CronHelper::checkExecutionTimer($this->timer);
}
function process() {
foreach($this->getQueues() as $queue) {
$newsletter = Newsletter::findOne($queue->newsletter_id);
if(!$newsletter) {
$queue->delete();
continue;
}
$newsletter = $newsletter->asArray();
$newsletter['body'] = $this->getOrRenderNewsletterBody($queue, $newsletter);
$queue->subscribers = (object) unserialize($queue->subscribers);
if(!isset($queue->subscribers->processed)) {
$queue->subscribers->processed = array();
@@ -42,13 +49,22 @@ class SendingQueue {
if(!isset($queue->subscribers->failed)) {
$queue->subscribers->failed = array();
}
$newsletter = $newsletter->asArray();
$newsletter['body'] = $this->renderNewsletter($newsletter);
$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)) {
$queue->subscribers->to_process = $this->recalculateSubscriberCount(
Helpers::arrayColumn($subscribers, 'id'),
$subscribers_ids,
$queue->subscribers->to_process
);
}
if (!count($queue->subscribers->to_process)) {
$this->updateQueue($queue);
continue;
}
$queue->subscribers = call_user_func_array(
array(
$this,
@@ -65,10 +81,47 @@ class SendingQueue {
}
}
function getOrRenderNewsletterBody($queue, $newsletter) {
// check if newsletter has been rendered, in which case return its contents
// or render and save for future reuse
if($queue->newsletter_rendered_body === null) {
if((boolean) Setting::getValue('tracking.enabled')) {
// insert tracking code
add_filter('mailpoet_rendering_post_process', function($template) {
return OpenTracking::process($template);
});
// render newsletter
list($rendered_newsletter, $queue->newsletter_rendered_body_hash) =
$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
);
list($newsletter['body']['html'], $newsletter['body']['text']) =
$this->splitObject($processed_newsletter);
}
else {
// render newsletter
list($newsletter['body'], $queue->newsletter_rendered_body_hash) =
$this->renderNewsletter($newsletter);
}
$queue->newsletter_rendered_body = json_encode($newsletter['body']);
$queue->save();
} else {
$newsletter['body'] = json_decode($queue->newsletter_rendered_body);
}
return (array) $newsletter['body'];
}
function processBulkSubscribers($mailer, $newsletter, $subscribers, $queue) {
foreach($subscribers as $subscriber) {
$processed_newsletters[] =
$this->processNewsletter($newsletter, $subscriber);
$this->processNewsletterBeforeSending($newsletter, $subscriber, $queue);
if(!$queue->newsletter_rendered_subject) {
$queue->newsletter_rendered_subject = $processed_newsletters[0]['subject'];
}
$transformed_subscribers[] =
$mailer->transformSubscriber($subscriber);
}
@@ -85,7 +138,7 @@ class SendingQueue {
);
} else {
$newsletter_statistics =
array_map(function ($data) use ($newsletter, $subscribers_ids, $queue) {
array_map(function($data) use ($newsletter, $subscribers_ids, $queue) {
return array(
$newsletter['id'],
$subscribers_ids[$data],
@@ -102,14 +155,17 @@ class SendingQueue {
}
$this->updateQueue($queue);
$this->checkSendingLimit();
$this->checkExecutionTimer();
CronHelper::checkExecutionTimer($this->timer);
return $queue->subscribers;
}
function processIndividualSubscriber($mailer, $newsletter, $subscribers, $queue) {
foreach($subscribers as $subscriber) {
$this->checkSendingLimit();
$processed_newsletter = $this->processNewsletter($newsletter, $subscriber);
$processed_newsletter = $this->processNewsletterBeforeSending($newsletter, $subscriber, $queue);
if(!$queue->newsletter_rendered_subject) {
$queue->newsletter_rendered_subject = $processed_newsletter['subject'];
}
$transformed_subscriber = $mailer->transformSubscriber($subscriber);
$result = $this->sendNewsletter(
$mailer,
@@ -117,7 +173,7 @@ class SendingQueue {
$transformed_subscriber
);
if(!$result) {
$queue->subscribers->failed[] = $subscriber['id'];;
$queue->subscribers->failed[] = $subscriber['id'];
} else {
$queue->subscribers->processed[] = $subscriber['id'];
$newsletter_statistics = array(
@@ -129,32 +185,77 @@ class SendingQueue {
$this->updateNewsletterStatistics($newsletter_statistics);
}
$this->updateQueue($queue);
$this->checkExecutionTimer();
CronHelper::checkExecutionTimer($this->timer);
}
return $queue->subscribers;
}
function updateNewsletterStatistics($data) {
return NewsletterStatistics::createMultiple($data);
return StatisticsNewsletters::createMultiple($data);
}
function renderNewsletter($newsletter) {
$renderer = new Renderer($newsletter);
return $renderer->render();
$rendered_newsletter = $renderer->render();
$rendered_newsletter_hash = md5($rendered_newsletter['text']);
return array($rendered_newsletter, $rendered_newsletter_hash);
}
function processNewsletter($newsletter, $subscriber = false) {
$divider = '***MailPoet***';
$shortcodes = new Shortcodes(
implode($divider, $newsletter['body']),
$newsletter,
$subscriber
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')
);
list($newsletter['body']['html'], $newsletter['body']['text']) =
explode($divider, $shortcodes->replace());
// 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 processNewsletterBeforeSending($newsletter, $subscriber = false, $queue) {
$data_for_shortcodes = array(
$newsletter['subject'],
$newsletter['body']['html'],
$newsletter['body']['text']
);
$processed_newsletter = $this->replaceShortcodes(
$newsletter,
$subscriber,
$queue,
$this->joinObject($data_for_shortcodes)
);
if((boolean) Setting::getValue('tracking.enabled')) {
$processed_newsletter = Links::replaceSubscriberData(
$newsletter['id'],
$subscriber['id'],
$queue->id,
$processed_newsletter
);
}
list($newsletter['subject'],
$newsletter['body']['html'],
$newsletter['body']['text']
) = $this->splitObject($processed_newsletter);
return $newsletter;
}
function replaceShortcodes($newsletter, $subscriber, $queue, $body) {
$shortcodes = new Shortcodes(
$newsletter,
$subscriber,
$queue
);
return $shortcodes->replace($body);
}
function sendNewsletter($mailer, $newsletter, $subscriber) {
return $mailer->mailer_instance->send(
$newsletter,
@@ -201,7 +302,9 @@ class SendingQueue {
$queue->subscribers->failed
)
);
$queue->subscribers->to_process = array_values($queue->subscribers->to_process);
$queue->subscribers->to_process = array_values(
$queue->subscribers->to_process
);
$queue->count_processed =
count($queue->subscribers->processed) + count($queue->subscribers->failed);
$queue->count_to_process = count($queue->subscribers->to_process);
@@ -209,7 +312,7 @@ class SendingQueue {
$queue->count_total =
$queue->count_processed + $queue->count_to_process;
if(!$queue->count_to_process) {
$queue->processed_at = date('Y-m-d H:i:s');
$queue->processed_at = current_time('mysql');
$queue->status = 'completed';
}
$queue->subscribers = serialize((array) $queue->subscribers);
@@ -260,10 +363,17 @@ class SendingQueue {
return;
}
function checkExecutionTimer() {
$elapsed_time = microtime(true) - $this->timer;
if($elapsed_time >= CronHelper::daemon_execution_limit) {
throw new \Exception(__('Maximum execution time reached.'));
}
function recalculateSubscriberCount(
$found_subscriber, $existing_subscribers, $subscribers_to_process) {
$subscibers_to_exclude = array_diff($existing_subscribers, $found_subscriber);
return array_diff($subscribers_to_process, $subscibers_to_exclude);
}
private function joinObject($object = array()) {
return implode(self::DIVIDER, $object);
}
private function splitObject($object = array()) {
return explode(self::DIVIDER, $object);
}
}

View File

@@ -112,4 +112,17 @@ abstract class Base {
&& strlen(trim($block['params']['value'])) > 0)
? esc_attr(trim($block['params']['value'])) : '';
}
protected static function getInputModifiers($block = array()) {
$modifiers = array();
if(isset($block['params']['readonly'])) {
$modifiers[] = 'readonly';
}
if(isset($block['params']['disabled'])) {
$modifiers[] = 'disabled';
}
return join(' ', $modifiers);
}
}

View File

@@ -20,18 +20,22 @@ class Checkbox extends Base {
foreach($options as $option) {
$html .= '<label class="mailpoet_checkbox_label">';
$html .= '<input type="hidden" name="'.$field_name.'" value="" />';
$html .= '<input type="checkbox" class="mailpoet_checkbox" ';
$html .= 'name="'.$field_name.'" ';
$html .= 'value="1" ';
$html .= (isset($option['is_checked']) && $option['is_checked'])
? 'checked="checked"' : '';
$html .= (
(isset($option['is_checked']) && $option['is_checked'])
||
(self::getFieldValue($block))
) ? 'checked="checked"' : '';
$html .= $field_validation;
$html .= ' />'.$option['value'];
$html .= ' /> '.esc_attr($option['value']);
$html .= '</label>';
}

View File

@@ -33,18 +33,37 @@ class Date extends Base {
// generate an array of selectors based on date format
$date_selectors = explode('/', $date_format);
// format value if present
$value = self::getFieldValue($block);
$day = null;
$month = null;
$year = null;
if($value) {
$day = (int)strftime('%d', $value);
$month = (int)strftime('%m', $value);
$year = (int)strftime('%Y', $value);
} else if(!empty($block['params']['is_default_today'])) {
$day = (int)strftime('%d');
$month = (int)strftime('%m');
$year = (int)strftime('%Y');
}
foreach($date_selectors as $date_selector) {
if($date_selector === 'dd') {
$block['selected'] = $day;
$html .= '<select class="mailpoet_date_day" ';
$html .= 'name="'.$field_name.'[day]" placeholder="'.__('Day').'">';
$html .= static::getDays($block);
$html .= '</select>';
} else if($date_selector === 'mm') {
$block['selected'] = $month;
$html .= '<select class="mailpoet_date_month" ';
$html .= 'name="'.$field_name.'[month]" placeholder="'.__('Month').'">';
$html .= static::getMonths($block);
$html .= '</select>';
} else if($date_selector === 'yyyy') {
$block['selected'] = $year;
$html .= '<select class="mailpoet_date_year" ';
$html .= 'name="'.$field_name.'[year]" placeholder="'.__('Year').'">';
$html .= static::getYears($block);
@@ -84,11 +103,6 @@ class Date extends Base {
'selected' => null
);
// is default today
if(!empty($block['params']['is_default_today'])) {
$defaults['selected'] = (int)strftime('%m');
}
// merge block with defaults
$block = array_merge($defaults, $block);

View File

@@ -25,14 +25,24 @@ class Radio extends Base {
$html .= 'name="'.$field_name.'" ';
$html .= 'value="'.esc_attr($option['value']).'" ';
if(is_array($option['value'])) {
$value = key($option['value']);
$label = reset($option['value']);
} else {
$value = $option['value'];
$label = $option['value'];
}
$html .= 'value="'.esc_attr($value).'" ';
$html .= (
(isset($option['is_checked']) && $option['is_checked'])
||
(self::getFieldValue($block) === $value)
) ? 'checked="checked"' : '';
$html .= (isset($option['is_checked']) && $option['is_checked'])
? 'checked="checked"' : '';
$html .= $field_validation;
$html .= ' />&nbsp;'.esc_attr($option['value']);
$html .= ' /> '.esc_attr($label);
$html .= '</label>';
}

View File

@@ -28,7 +28,7 @@ class Segment extends Base {
$html .= 'name="'.$field_name.'[]" ';
$html .= 'value="'.$option['id'].'" '.$is_checked.' ';
$html .= $field_validation;
$html .= ' />'.$option['name'];
$html .= ' /> '.esc_attr($option['name']);
$html .= '</label>';
}

View File

@@ -10,9 +10,7 @@ class Select extends Base {
$field_validation = static::getInputValidation($block);
$html .= '<p class="mailpoet_paragraph">';
$html .= static::renderLabel($block);
$html .= '<select class="mailpoet_select" name="'.$field_name.'">';
if(isset($block['params']['label_within'])
@@ -20,11 +18,28 @@ class Select extends Base {
$html .= '<option value="">'.static::getFieldLabel($block).'</option>';
}
foreach($block['params']['values'] as $option) {
$is_selected = (isset($option['is_checked']) && $option['is_checked'])
? 'selected="selected"' : '';
$html .= '<option value="'.$option['value'].'" '.$is_selected.'>';
$html .= $option['value'];
$options = (!empty($block['params']['values'])
? $block['params']['values']
: array()
);
foreach($options as $option) {
$is_selected = (
(isset($option['is_checked']) && $option['is_checked'])
||
(self::getFieldValue($block) === $option['value'])
) ? 'selected="selected"' : '';
if(is_array($option['value'])) {
$value = key($option['value']);
$label = reset($option['value']);
} else {
$value = $option['value'];
$label = $option['value'];
}
$html .= '<option value="'.$value.'" '.$is_selected.'>';
$html .= esc_attr($label);
$html .= '</option>';
}
$html .= '</select>';

View File

@@ -6,7 +6,7 @@ class Text extends Base {
static function render($block) {
$type = 'text';
if($block['id'] === 'email') {
$type = 'email';
$type = 'email';
}
$html = '';
@@ -27,6 +27,8 @@ class Text extends Base {
$html .= static::getInputValidation($block);
$html .= static::getInputModifiers($block);
$html .= '/>';
$html .= '</p>';

View File

@@ -19,6 +19,8 @@ class Textarea extends Base {
$html .= static::getInputValidation($block);
$html .= static::getInputModifiers($block);
$html .= '></textarea>';
$html .= '</p>';

View File

@@ -39,8 +39,7 @@ class Renderer {
}
}
// private: rendering methods
private static function renderBlocks($blocks = array()) {
static function renderBlocks($blocks = array()) {
$html = '';
foreach ($blocks as $key => $block) {
$html .= static::renderBlock($block)."\n";
@@ -49,7 +48,7 @@ class Renderer {
return $html;
}
private static function renderBlock($block = array()) {
static function renderBlock($block = array()) {
$html = '';
switch($block['type']) {
case 'html':

View File

@@ -17,8 +17,7 @@ class Export {
case 'iframe':
// generate url to load iframe's content
$iframe_url = add_query_arg(array(
'mailpoet_page' => 'mailpoet_form_iframe',
'mailpoet_form' => $form['id']
'mailpoet_form_iframe' => $form['id']
), site_url());
// generate iframe
@@ -31,7 +30,7 @@ class Export {
'class="mailpoet_form_iframe"',
'vspace="0"',
'tabindex="0"',
'onload="javascript:(this.style.height = this.contentWindow.document.body.scrollHeight + \'px\');"',
'onload="MailPoet.Iframe.autoSize(this);"',
'marginwidth="0"',
'marginheight="0"',
'hspace="0"',

View File

@@ -17,7 +17,7 @@ class Styles {
}
/* labels */
.mailpoet_input_label,
.mailpoet_text_label,
.mailpoet_textarea_label,
.mailpoet_select_label,
.mailpoet_radio_label,
@@ -28,7 +28,7 @@ class Styles {
}
/* inputs */
.mailpoet_input,
.mailpoet_text,
.mailpoet_textarea,
.mailpoet_select,
.mailpoet_date {
@@ -36,9 +36,7 @@ class Styles {
}
.mailpoet_checkbox {
display:inline;
margin-right: 5px;
vertical-align:middle;
}
.mailpoet_validate_success {

View File

@@ -7,33 +7,12 @@ use \MailPoet\Models\Setting;
use \MailPoet\Models\Subscriber;
use \MailPoet\Form\Renderer as FormRenderer;
use \MailPoet\Form\Util;
use \MailPoet\Util\Security;
if(!defined('ABSPATH')) exit;
class Widget extends \WP_Widget {
function __construct () {
// add_action(
// 'wp_ajax_mailpoet_form_subscribe',
// array($this, 'subscribe')
// );
// add_action(
// 'wp_ajax_nopriv_mailpoet_form_subscribe',
// array($this, 'subscribe')
// );
// add_action(
// 'admin_post_nopriv_mailpoet_form_subscribe',
// array($this, 'subscribe')
// );
// add_action(
// 'admin_post_mailpoet_form_subscribe',
// array($this, 'subscribe')
// );
// add_action(
// 'init',
// array($this, 'subscribe')
// );
return parent::__construct(
'mailpoet_form',
__('MailPoet Form'),
@@ -154,7 +133,7 @@ class Widget extends \WP_Widget {
$output = '';
if(!empty($body)) {
$form_id = $this->id_base.'_'.$this->number;
$form_id = $this->id_base.'_'.$form['id'];
$data = array(
'form_id' => $form_id,
@@ -169,21 +148,15 @@ class Widget extends \WP_Widget {
'after_title' => (!empty($after_title) ? $after_title : '')
);
// if(isset($_GET['mailpoet_form']) && (int)$_GET['mailpoet_form'] === $form['id']) {
// // form messages (success / error)
// $output .= '<div class="mailpoet_message">';
// // success message
// if(isset($_GET['mailpoet_success'])) {
// $output .= '<p class="mailpoet_validate_success">'.strip_tags(urldecode($_GET['mailpoet_success']), '<a><strong><em><br><p>').'</p>';
// }
// // error message
// if(isset($_GET['mailpoet_error'])) {
// $output .= '<p class="mailpoet_validate_error">'.strip_tags(urldecode($_GET['mailpoet_error']), '<a><strong><em><br><p>').'</p>';
// }
// $output .= '</div>';
// } else {
// $output .= '<div class="mailpoet_message"></div>';
// }
// check if the form was successfully submitted via POST (non ajax)
$data['success'] = (
(isset($_GET['mailpoet_success']))
&&
((int)$_GET['mailpoet_success'] === (int)$form['id'])
);
// generate security token
$data['token'] = Security::generateToken();
// render form
$renderer = new Renderer();

View File

@@ -93,7 +93,7 @@ class Handler {
return array(
'count' => $count,
'filters' => $this->model->filter('filters'),
'filters' => $this->model->filter('filters', $this->data['group']),
'groups' => $this->model->filter('groups'),
'items' => $items
);

View File

@@ -100,9 +100,9 @@ class Mailer {
}
function getSender($sender = false) {
if(!$sender) {
$sender = Setting::getValue('sender', null);
if(!$sender['address']) throw new \Exception(__('Sender name and email are not configured.'));
if(empty($sender)) {
$sender = Setting::getValue('sender', array());
if(empty($sender['address'])) throw new \Exception(__('Sender name and email are not configured.'));
}
return array(
'from_name' => $sender['name'],

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