Compare commits

..

162 Commits
3.0.2 ... 3.1.0

Author SHA1 Message Date
8dfaf9ba32 Release 3.1.0 2017-11-07 15:20:00 +02:00
89d0da93d3 Merge pull request #1180 from mailpoet/wp_sync_db_error_fix
Fixes "column cannot be null" error during WP sync [MAILPOET-1191]
2017-11-07 12:23:39 +02:00
a8ccfec5c6 Merge pull request #1178 from mailpoet/superadmin_access
Enable permissions for superadmin users [MAILPOET-1200]
2017-11-07 11:15:33 +03:00
d8609c9e84 Merge pull request #1179 from mailpoet/survey_nov7
Updates weekly survey [MAILPOET-1199]
2017-11-07 10:41:07 +03:00
5461c975d4 Fixes "column cannot be null" error during WP sync 2017-11-06 22:29:59 -05:00
11298bc101 Updates weekly survey 2017-11-06 22:25:35 -05:00
c42cf2f622 Switch to using current_user_can function to check capabilities 2017-11-06 18:09:38 +02:00
c9f1d38baa Merge pull request #1177 from mailpoet/mpapi_create_list
Add a method to create a new list to public API [MAILPOET-1197]
2017-11-02 18:54:06 -04:00
3d78d6bbe9 Add a method to create a new list to public API [MAILPOET-1197] 2017-11-02 22:13:50 +03:00
2b4288f301 Merge pull request #1176 from mailpoet/throttling_test_fix
Fix an off-by-one error in some timezones [MAILPOET-1186]
2017-11-02 14:34:03 +02:00
09a2dd231a Fix an off-by-one error in some timezones [MAILPOET-1186] 2017-11-02 14:56:44 +03:00
3fd4ef9985 Cleanup after WP sync test [MAILPOET-1185] 2017-11-01 15:43:31 +00:00
05979965ba Merge pull request #1174 from mailpoet/es6
ESLint: ES6 rules 2 [MAILPOET-1138]
2017-11-01 11:32:34 -04:00
e625a7602a Merge pull request #1171 from mailpoet/mixpanel
Report more system environment data via MixPanel [MAILPOET-1189]
2017-11-01 11:21:29 -04:00
a0c41ad7ab Merge pull request #1168 from mailpoet/jquery
plugin scripts should be loaded with a dependency on jquery [MAILPOET-1149]
2017-11-01 15:01:38 +02:00
7163747eb9 no-extra-boolean-cast 2017-11-01 10:20:13 +00:00
c30e2b6cf3 no-sequences 2017-10-31 17:16:24 +00:00
e9eae92ba9 no-useless-concat 2017-10-31 17:15:09 +00:00
0fd6fa8879 max-len 2017-10-31 17:12:59 +00:00
52ca6eac18 no-else-return 2017-10-31 17:10:10 +00:00
28776b8558 no-case-declarations 2017-10-31 16:52:00 +00:00
588ad3eab7 class-methods-use-this 2017-10-31 16:46:19 +00:00
d791538086 no-lonely-if 2017-10-31 16:40:08 +00:00
62173e7996 Bump up release version to 3.0.9 2017-10-31 19:18:42 +03:00
0acdcd1ca7 Merge pull request #1170 from mailpoet/jquery_serialize_object
Rename jQuery Serialize Object function to prevent conflicts [MAILPOET-1190]
2017-10-31 15:05:00 +01:00
f39a2c8dda added env data to weekly report 2017-10-31 11:17:34 +00:00
b648852ef5 Rename jQuery Serialize Object function to prevent conflicts [MAILPOET-1190] 2017-10-30 22:49:00 +03:00
6e45892118 trim values on listing search 2017-10-30 16:24:00 +00:00
cd145c51f7 public.js now depends on jquery 2017-10-30 14:25:05 +00:00
2c12d9ee2d changes on welcome and update pages 2017-10-30 11:36:40 +00:00
5fe28623f1 Merge pull request #1164 from mailpoet/acceptance_js_errors
Add JS error checking to acceptance tests [MAILPOET-1179]
2017-10-29 21:31:45 -04:00
3086b3cfc2 Merge pull request #1165 from mailpoet/esltests
ESLint: Test rules 2 [MAILPOET-1135]
2017-10-29 21:30:27 -04:00
5fd29872ff Merge pull request #1166 from mailpoet/remove-tags
Remove tags from ALC display
2017-10-26 14:28:02 +03:00
732c8a314f Merge pull request #1160 from mailpoet/ci-tables-prefix
changing the tables prefix to "mp_" on CircleCI [MAILPOET-1178]
2017-10-26 13:43:08 +03:00
23c650bfa6 Merge pull request #1163 from mailpoet/new-poll
new poll [MAILPOET-1172]
2017-10-26 13:34:16 +03:00
f5ced785e0 Tests: padded-blocks 2017-10-25 14:54:28 +00:00
4dc9004303 Tests: no-shaddow 2017-10-25 14:52:53 +00:00
893c6bd72b Remove tags from ALC display
[MAILPOET-1182]
2017-10-25 15:49:23 +01:00
2ac32484e1 Tests: max-len 2017-10-25 12:16:32 +00:00
b31e8ce5f2 Tests: no-bitwise 2017-10-25 12:10:29 +00:00
4270e4c315 Check for missing CSS files in acceptance tests [MAILPOET-1177] 2017-10-25 14:04:11 +03:00
4b13395b0c Add JS error checking to acceptance tests [MAILPOET-1179] 2017-10-25 13:34:16 +03:00
2e67029ef5 Merge pull request #1162 from mailpoet/new
Capitalize new [MAILPOET-1174]
2017-10-24 17:48:05 +03:00
257517b9a9 new poll 2017-10-24 14:13:07 +00:00
88ee64e15d Capitalize new 2017-10-24 14:03:27 +00:00
c2030e9a86 Fix readme.txt rendering 2017-10-24 14:55:21 +01:00
16111a99fb Release 3.0.8 2017-10-24 13:17:49 +01:00
543b3e5a91 changing tables prefix for acceptance tests 2017-10-24 09:13:43 +00:00
e33b60065e Merge pull request #1159 from mailpoet/ci_js_compile
Compile JS and CSS assets on CI before running acceptance tests [MAILPOET-1166]
2017-10-24 11:55:24 +03:00
3bcfadd2ab Merge pull request #1161 from mailpoet/translations_update
Translations update [MAILPOET-1161]
2017-10-24 11:30:59 +03:00
4729583d8d Merge pull request #1158 from mailpoet/scroll
enabling body scroll when settings are open [MAILPOET-1148]
2017-10-23 21:52:39 -04:00
aa3f457595 Updates welcome page translations that were not included in pot file 2017-10-23 21:45:48 -04:00
3ad490f840 Loads translations before the rest of the plugin 2017-10-23 21:45:48 -04:00
eb27ed65ae Adds missing translations to migration page 2017-10-23 21:45:40 -04:00
d543f62c5b Merge pull request #1157 from mailpoet/poll
new poll [MAILPOET-1171]
2017-10-23 20:11:19 +03:00
4e6f7a05de changing the tables prefix to "mp_" on CircleCI 2017-10-23 16:49:36 +00:00
5d467115ad Compile JS and CSS assets on CI before running acceptance tests [MAILPOET-1166] 2017-10-23 19:02:28 +03:00
6858b266fe enabling body scroll when settings are open 2017-10-23 16:01:28 +00:00
852b1a4c08 new poll 2017-10-23 14:59:29 +00:00
b2324db7b4 Merge pull request #1156 from mailpoet/number_formatting_update
Formats number as per locale's convention [MAILPOET-1125]
2017-10-23 15:19:47 +02:00
e8c85e2a54 Merge pull request #1155 from mailpoet/wp_user_sync_db_query_fix
Fixes MySQL's "column is ambiguous" error in WP user sync [MAILPOET-1177]
2017-10-23 14:15:14 +03:00
a4ac74c84a Remove more ambiguity in WP sync queries [MAILPOET-1177] 2017-10-23 13:58:23 +03:00
4e378484ea Formats number as per locale's convention 2017-10-22 21:56:33 -04:00
c55be70b2c Merge pull request #1154 from mailpoet/php_version_mixpanel
Report PHP version in weekly MixPanel analytics [MAILPOET-1175]
2017-10-20 17:10:14 +02:00
70898790f5 Cleans up code & fixes possible MySQL's "column is ambiguous" error 2017-10-19 21:14:45 -04:00
bfdb535f5b Fixes method name typo 2017-10-19 21:00:15 -04:00
996e6b16e7 Merge pull request #1142 from mailpoet/links
Enabling links on header and footer on preview [MAILPOET-1121]
2017-10-19 20:07:32 +03:00
0217e21753 Restore a default argument value [MAILPOET-1121] 2017-10-19 19:43:39 +03:00
f6f79d42e1 Report PHP version in weekly MixPanel analytics [MAILPOET-1175] 2017-10-19 19:03:55 +03:00
4bbd26b098 Merge pull request #1152 from mailpoet/form_translation_fix
Adds translated "required field" message to subscription form [MAILPOET-1173]
2017-10-19 18:55:02 +03:00
028179af37 Merge pull request #1153 from mailpoet/form_acceptance_tests
Add an acceptance test for the public subscription form widget [MAILPOET-1166]
2017-10-19 17:46:54 +03:00
87283bf838 Merge pull request #1143 from mailpoet/image-url
Force absolute path for images [MAILPOET-1160]
2017-10-19 12:38:45 +03:00
c680badaa2 Fix WP uploads folder permissions [MAILPOET-1166] 2017-10-19 12:34:16 +03:00
df4a936e43 Fix exit code returned by acceptance tests [MAILPOET-1166] 2017-10-19 12:34:16 +03:00
e27946ebcc Fix artifacts storage [MAILPOET-1166] 2017-10-19 12:34:16 +03:00
13e64c012f Restore PHP 5 support [MAILPOET-1166] 2017-10-19 12:34:16 +03:00
f69302be48 Add an acceptance test for the public subscription form widget [MAILPOET-1166] 2017-10-19 12:34:16 +03:00
16edfc16ea Merge pull request #1144 from mailpoet/dynamic-segments
Dynamic segments [PREMIUM-38]
2017-10-18 18:57:48 +03:00
229a9c8102 Fix QA issue
[PREMIUM-38]
2017-10-18 15:11:57 +01:00
490091a7e2 Move table definition to premium plugin
[PREMIUM-38]
2017-10-18 15:09:16 +01:00
5f58e5ca82 Add tests
[PREMIUM-38]
2017-10-18 15:04:44 +01:00
16beda530a Fix post notifications to work with dynamic segments
[PREMIUM-38]
2017-10-18 10:08:04 +01:00
f1b373924f Remove subscribers filtering for now
[PREMIUM-38]
2017-10-18 10:08:04 +01:00
6a73c463cb QA fixes
[PREMIUM-38]
2017-10-18 10:08:04 +01:00
0271675cd0 Use dynamic segments for sending queue
[PREMIUM-38]
2017-10-18 10:08:04 +01:00
b7555aa640 Show only default segments in listings
[PREMIUM-38]
2017-10-18 10:08:04 +01:00
fa9ef6e5bd Add 500 status code
[PREMIUM-38]
2017-10-18 10:08:04 +01:00
1c97b004ca Add blank template for premium plugin
[PREMIUM-38]
2017-10-18 10:08:04 +01:00
f0ab42adf1 Expose packages for premium
[PREMIUM-38]
2017-10-18 10:08:04 +01:00
165d8358d4 Add action after lists menu item
[PREMIUM-38]
2017-10-18 10:08:04 +01:00
6bd6e74bcb Add dynamic filters table
[PREMIUM-38]
2017-10-18 10:08:04 +01:00
b23df9e0a4 Adds translated "required field" message 2017-10-17 21:43:55 -04:00
7b12affb77 Bumps up release version to 3.0.7 and updates changelog 2017-10-17 14:08:16 -04:00
8c8c01aa75 shows links when previewing sent newsletters 2017-10-17 14:14:08 +00:00
0150e699a2 Merge pull request #1151 from mailpoet/oct17_poll_update
Updates weekly poll [MAILPOET-1170]
2017-10-17 17:06:39 +03:00
e6943e2638 Updates weekly poll 2017-10-17 09:34:04 -04:00
6f80dcb1de adding unit tests 2017-10-17 13:09:37 +00:00
60ed294302 no need to normalize src on the frontend 2017-10-17 12:57:39 +00:00
e8017b58f5 removing tooltip message 2017-10-17 12:48:20 +00:00
c79bf7d337 forcing absolute image source when rendering 2017-10-17 12:35:11 +00:00
ac268c1ec9 Merge pull request #1150 from mailpoet/beacon_cron_url_update
Uses CronHelper's method to return cron ping URL in beacon [MAILPOET-1164]
2017-10-17 09:54:12 +03:00
1ef131fa2d Merge pull request #1149 from mailpoet/capabilities_fix
Fixes "Call to a member function add_cap() on null" error [MAILPOET-1169]
2017-10-17 09:46:48 +03:00
1873007550 Improve a unit test for non-existent roles' capabilities [MAILPOET-1169] 2017-10-17 09:35:50 +03:00
fa2ccb51c9 Uses CronHelper's method to return cron ping URL in beacon 2017-10-16 23:46:17 -04:00
8f87d654af Merge pull request #1148 from mailpoet/stats_cta
Add CTA links for detailed stats [MAILPOET-1152]
2017-10-16 23:33:10 -04:00
dee6e9fbad Fixes "Call to a member function add_cap() on null" error 2017-10-16 22:55:11 -04:00
ef90264316 Add CTA links for detailed stats [MAILPOET-1152] 2017-10-13 01:11:06 +03:00
70bf4be723 ESLint: Test rules 1
[MAILPOET-1134]
2017-10-12 15:36:40 +01:00
07ef727654 Merge pull request #1139 from mailpoet/throttling
Add progressive throttling of subscriptions from the same IP address [MAILPOET-1128]
2017-10-12 15:56:40 +02:00
a346e5be29 normalizing image URL on render 2017-10-12 13:55:04 +00:00
5ce1eadde7 Merge pull request #1147 from mailpoet/honeypot-autofill
Subscription form honeypot gets filled by Autofill [MAILPOET-1163]
2017-10-12 16:52:22 +03:00
8a4d5395b1 Add attribute for Chrome
[MAILPOET-1163]
2017-10-12 14:38:45 +01:00
b5feed0f46 Remove WP subscribers with empty emails when syncing [MAILPOET-1158] 2017-10-12 10:31:11 +01:00
11f9579101 Don't synchronize WP users without emails [MAILPOET-1158] 2017-10-12 10:31:11 +01:00
c6000c959a Force absolute path for images 2017-10-11 14:21:16 +00:00
19e67ea2b0 Enabling links on header and footer on preview 2017-10-11 12:45:07 +00:00
0f6619e25d Merge pull request #1140 from mailpoet/svn_publish
Remove a dependency on WP in svn:publish command [MAILPOET-1156]
2017-10-11 13:21:10 +03:00
daf747d3be Throw an exception if plugin version could not be determined [MAILPOET-1156] 2017-10-11 13:07:53 +03:00
7393b1f2cf Remove a dependency on WP in svn:publish command [MAILPOET-1156] 2017-10-11 10:54:28 +03:00
efe861a9ba Merge pull request #1137 from mailpoet/eslint4
Eslint for tests [MAILPOET-1083]
2017-10-11 10:00:53 +03:00
2c358ab179 Add progressive throttling of subscriptions from the same IP address [MAILPOET-1128] 2017-10-10 19:36:20 +03:00
ca157fc91d Release 3.0.6 2017-10-10 16:46:37 +03:00
1fbe5d7bc6 Merge pull request #1138 from mailpoet/parsley
fixing missing parsley method [MAILPOET-1157]
2017-10-10 16:34:18 +03:00
71c031ccf9 fixing missing parsley method 2017-10-10 13:10:49 +00:00
6449b7ccca fixed minor issue 2017-10-10 09:29:22 +00:00
6a956472fe Release 3.0.5 2017-10-10 12:27:16 +03:00
d6af88d667 Tests object-curly-spacing 2017-10-10 09:09:03 +00:00
b9184a202f Tests func-call-spacing 2017-10-10 09:09:03 +00:00
f898746967 Tests keyword-spacing 2017-10-10 09:09:03 +00:00
68165b7b78 Tests space-before-function-paren 2017-10-10 09:09:03 +00:00
bb8591a67b Tests space-before-blocks 2017-10-10 09:07:29 +00:00
bda71ae78e Tests space-unary-ops 2017-10-10 09:07:29 +00:00
abd4f6cac2 Tests no-spaced-func 2017-10-10 09:07:29 +00:00
87e6cc2a4f Tests no-whitespace-before-property 2017-10-10 09:07:29 +00:00
dde598eb64 rebasing on master 2017-10-10 09:07:29 +00:00
0c4407f43a Merge pull request #1126 from mailpoet/eslint3
Eslint rules [MAILPOET-1084]
2017-10-10 11:46:45 +03:00
b6c864e7a1 Change the JSON API error message
[MAILPOET-1103]
2017-10-10 09:45:50 +01:00
3d9dc6465d Merge pull request #1132 from mailpoet/image_alignment
Add image alignment option to newsletter editor [MAILPOET-1124]
2017-10-10 11:37:11 +03:00
9a6fec094a fixing divider and spacer resize 2017-10-10 08:35:26 +00:00
61af224d7d Fixing image resize 2017-10-09 12:12:47 +00:00
9b41641e97 Tests vars-on-top 2017-10-09 11:17:23 +00:00
4e2e9f6f8f rebasing on master 2017-10-09 11:17:23 +00:00
c29dc8b4c7 fixing tests 2017-10-09 11:15:58 +00:00
98a3c6b156 Tests no-unused-vars 2017-10-09 11:15:58 +00:00
69c540288b Tests one-var 2017-10-09 11:15:58 +00:00
651c9f5692 Tests one-var-declaration-per-line 2017-10-09 11:15:58 +00:00
9ad3778cf7 ES6 rules 2017-10-09 11:15:58 +00:00
c90e0e9f64 ES5 no-unused-vars 2017-10-09 11:15:58 +00:00
cb1730c4e2 ES5 no-var 2017-10-09 11:15:58 +00:00
3dd8a973fd ES5 vars-on-top 2017-10-09 11:15:58 +00:00
c3ea088fca ES5 one-var 2017-10-09 11:15:58 +00:00
a11d6d7868 ES5 one-var-declaration-per-line 2017-10-09 11:15:58 +00:00
a596add838 ES5 block-scoped-var 2017-10-09 11:15:58 +00:00
7e7103ddab ES5 block-scoped-var 2017-10-09 11:15:58 +00:00
0064970ed7 Add a test for an alignment setting of an image block in the editor [MAILPOET-1124] 2017-10-09 13:00:39 +03:00
9d93f3ea95 Add a test for image alignment rendering [MAILPOET-1124] 2017-10-09 09:37:31 +03:00
ff2a3cd19e Release 3.0.4 2017-10-05 12:09:28 +00:00
8419d95ea1 Fix spacer and divider resizing [MAILPOET-1131] 2017-10-05 10:48:52 +01:00
4ad317ac7b Release MP3 3.0.3 2017-10-03 21:37:51 +03:00
7cccebbf2c Merge pull request #1135 from mailpoet/wp_sync_collations_fix
Get rid of WP user IDs updating query in favor of an insert-update due to collation problems [MAILPOET-1132]
2017-10-03 21:01:44 +03:00
e4f76ee9eb Get rid of WP user IDs updating query in favor of an insert-update due to collation problems [MAILPOET-1132] 2017-10-03 20:52:35 +03:00
26241afb86 Add image alignment option to newsletter editor [MAILPOET-1124] 2017-10-02 15:30:43 +03:00
154 changed files with 6146 additions and 2458 deletions

View File

@ -81,6 +81,7 @@ jobs:
curl -sS https://getcomposer.org/installer | php
php composer.phar install
./do install
./do compile:all --env production
- save_cache:
key: composer-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }}
paths:
@ -94,9 +95,9 @@ jobs:
command: |
docker-compose run codeception --steps --debug -vvv --html --xml
- store_artifacts:
path: ~/mailpoet/tests/acceptance-tests/_output
path: tests/_output
- store_test_results:
path: ~/mailpoet/tests/acceptance-tests/_output
path: tests/_output
php7:
working_directory: /home/circleci/mailpoet
docker:

View File

@ -37,6 +37,7 @@ function setup {
./wp-cli.phar core download --allow-root --path=wordpress
# Generate `wp-config.php` file with debugging enabled
echo "define(\"WP_DEBUG\", true);" | ./wp-cli.phar core config --allow-root --dbname=wordpress --dbuser=root --dbhost=127.0.0.1 --path=wordpress --extra-php
sed -i "s/\$table_prefix = 'wp_';/\$table_prefix = 'mp_';/" ./wordpress/wp-config.php
# Install WordPress
./wp-cli.phar core install --allow-root --admin_name=admin --admin_password=admin --admin_email=admin@mailpoet.loc --url=http://mailpoet.loc:8080 --title=WordPress --path=wordpress
# Softlink plugin to plugin path

View File

@ -12,7 +12,6 @@
"prefer-arrow-callback": 0,
"radix": 0,
"no-alert": 0,
"block-scoped-var": 0,
"guard-for-in": 0,
"no-prototype-builtins": 0,
"no-restricted-syntax": 0,
@ -38,20 +37,16 @@
"global-require": 0,
"no-throw-literal": 0,
"no-extra-bind": 0,
"one-var-declaration-per-line": 0,
"consistent-return": 0,
"no-shadow": 0,
"no-underscore-dangle": 0,
"brace-style": 0,
"no-else-return": 0,
"no-use-before-define": 0,
"one-var": 0,
"camelcase": 0,
"padded-blocks": 0,
"strict": 0,
"vars-on-top": 0,
"no-var": 0,
"no-unused-vars": 0,
"space-infix-ops": 0,
"object-shorthand": 0,
"new-parens": 0,
"eol-last": 0,

View File

@ -47,24 +47,14 @@
"import/no-extraneous-dependencies": 0,
"camelcase": 0,
"eqeqeq": 0,
"no-lonely-if": 0,
"block-scoped-var": 0,
"no-extra-bind": 0,
"class-methods-use-this": 0,
"no-case-declarations": 0,
"no-else-return": 0,
"max-len": 0,
"no-useless-concat": 0,
"no-sequences": 0,
"no-extra-boolean-cast": 0,
"space-unary-ops": 0,
"dot-notation": 0,
"no-shadow": 0,
"one-var": 0,
"no-alert": 0,
"one-var-declaration-per-line": 0,
"no-script-url": 0,
"wrap-iife": 0,
"vars-on-top": 0,
"space-infix-ops": 0,
"no-irregular-whitespace": 0,
"padded-blocks": 0,
"no-underscore-dangle": 0
}

View File

@ -8,25 +8,9 @@
"ecmaVersion": 6
},
"rules": {
"import/no-amd": 0,
"one-var": 0,
"no-whitespace-before-property": 0,
"global-require": 0,
"keyword-spacing": 0,
"no-bitwise": 0,
"no-spaced-func": 0,
"func-call-spacing": 0,
"max-len": 0,
"space-unary-ops": 0,
"no-unused-vars": 0,
"no-underscore-dangle": 0,
"no-shadow": 0,
"padded-blocks": 0,
"vars-on-top": 0,
"space-before-blocks": 0,
"object-curly-spacing": 0,
"one-var-declaration-per-line": 0,
// Exceptions
"func-names": 0,
"space-before-function-paren": 0
// Temporary
"no-underscore-dangle": 0
}
}

1
.gitignore vendored
View File

@ -2,6 +2,7 @@
TODO
composer.phar
/vendor
/vendor_backup
tests/_output/*
tests/_support/_generated/*
node_modules

View File

@ -289,15 +289,16 @@ class RoboFile extends \Robo\Tasks {
}
function svnPublish($opts = ['force' => false]) {
$this->loadWPFunctions();
$this->loadEnv();
$svn_dir = ".mp_svn";
$plugin_data = get_plugin_data('mailpoet.php', false, false);
$plugin_version = $plugin_data['Version'];
$plugin_dist_name = sanitize_title_with_dashes($plugin_data['Name']);
$plugin_dist_name = explode('-', $plugin_dist_name);
$plugin_dist_name = $plugin_dist_name[0];
$plugin_version = $this->getPluginVersion('mailpoet.php');
$plugin_dist_name = 'mailpoet';
$plugin_dist_file = $plugin_dist_name . '.zip';
if(!$plugin_version) {
throw new \Exception('Could not parse plugin version, check the plugin header');
}
$this->say('Publishing version: ' . $plugin_version);
// Sanity checks
@ -416,13 +417,9 @@ class RoboFile extends \Robo\Tasks {
$dotenv->load();
}
protected function loadWPFunctions() {
$this->loadEnv();
define('ABSPATH', getenv('WP_TEST_PATH') . '/');
define('WPINC', 'wp-includes');
require_once(ABSPATH . WPINC . '/functions.php');
require_once(ABSPATH . WPINC . '/formatting.php');
require_once(ABSPATH . WPINC . '/plugin.php');
require_once(ABSPATH . 'wp-admin/includes/plugin.php');
protected function getPluginVersion($file) {
$data = file_get_contents($file);
preg_match('/^[ \t*]*Version:(.*)$/mi', $data, $m);
return !empty($m[1]) ? trim($m[1]) : false;
}
}

View File

@ -2,11 +2,10 @@ function requestFailed(errorMessage, xhr) {
if (xhr.responseJSON) {
return xhr.responseJSON;
}
var message = errorMessage.replace('%d', xhr.status);
return {
errors: [
{
message: message
message: errorMessage.replace('%d', xhr.status)
}
]
};
@ -54,21 +53,23 @@ define('ajax', ['mailpoet', 'jquery', 'underscore'], function (mp, jQuery, _) {
};
},
request: function (method, options) {
// set options
var params;
var deferred;
// set options
this.init(options);
// set request params
var params = this.getParams();
// set request params
params = this.getParams();
// remove null values from the data object
// remove null values from the data object
if (_.isObject(params.data)) {
params.data = _.pick(params.data, function (value) {
return (value !== null);
});
}
// ajax request
var deferred = jQuery.post(
// ajax request
deferred = jQuery.post(
this.options.url,
params,
null,

View File

@ -43,9 +43,10 @@ define('date',
},
format: function (date, opts) {
var options = opts || {};
var momentDate;
this.init(options);
var momentDate = Moment(date, this.convertFormat(options.parseFormat));
momentDate = Moment(date, this.convertFormat(options.parseFormat));
if (options.offset === 0) momentDate = momentDate.utc();
return momentDate.format(this.convertFormat(this.options.format));
},
@ -71,6 +72,11 @@ define('date',
});
},
convertFormat: function (format) {
var replacements;
var convertedFormat;
var escapeToken;
var index;
var token;
var format_mappings = {
date: {
d: 'DD',
@ -140,12 +146,11 @@ define('date',
if (!format || format.length <= 0) return format;
var replacements = format_mappings['date'];
replacements = format_mappings['date'];
convertedFormat = [];
escapeToken = false;
var convertedFormat = [];
var escapeToken = false;
for (var index = 0, token = ''; format.charAt(index); index += 1) {
for (index = 0, token = ''; format.charAt(index); index += 1) {
token = format.charAt(index);
if (escapeToken === true) {
convertedFormat.push('[' + token + ']');

View File

@ -77,14 +77,14 @@ define([
{ description }
</span>
);
} else {
return (
<div key={'field-' + (data.index || 0)}>
{ field }
{ description }
</div>
);
}
return (
<div key={'field-' + (data.index || 0)}>
{ field }
{ description }
</div>
);
},
render: function () {
let field = false;

View File

@ -60,13 +60,11 @@ define([
templateResult: function (item) {
if (item.element && item.element.selected) {
return null;
} else {
if (item.title) {
return item.title;
} else {
return item.text;
}
} else if (item.title) {
return item.title;
}
return item.text;
},
});
@ -156,9 +154,9 @@ define([
transformChangedValue: function (value) {
if (typeof this.props.field['transformChangedValue'] === 'function') {
return this.props.field.transformChangedValue.call(this, value);
} else {
return value;
}
return value;
},
render: function () {
const options = this.state.items.map((item, index) => {

View File

@ -44,7 +44,7 @@ define(
this.loadItem(this.props.params.id);
} else {
this.setState({
item: jQuery('.mailpoet_form').serializeObject(),
item: jQuery('.mailpoet_form').mailpoetSerializeObject(),
});
}
}
@ -142,17 +142,17 @@ define(
handleValueChange: function (e) {
if (this.props.onChange) {
return this.props.onChange(e);
} else {
const item = this.state.item;
const field = e.target.name;
item[field] = e.target.value;
this.setState({
item: item,
});
return true;
}
const item = this.state.item;
const field = e.target.name;
item[field] = e.target.value;
this.setState({
item: item,
});
return true;
},
render: function () {
let errors;

View File

@ -7,6 +7,10 @@
'use strict';
var Observable;
var WysijaHistory;
var WysijaForm;
Event.cacheDelegated = {};
Object.extend(document, (function () {
var cache = Event.cacheDelegated;
@ -30,18 +34,21 @@ Object.extend(document, (function () {
}
function destroyWrapper(selector, eventName, handler) {
var wrapper;
var c = getCacheForSelector(selector);
if (!c[eventName]) return false;
var wrapper = findWrapper(selector, eventName, handler);
wrapper = findWrapper(selector, eventName, handler);
c[eventName] = c[eventName].without(wrapper);
return wrapper;
}
function createWrapper(selector, eventName, handler, context) {
var wrapper, c = getWrappersForSelector(selector, eventName);
var wrapper;
var element;
var c = getWrappersForSelector(selector, eventName);
if (c.pluck('handler').include(handler)) return false;
wrapper = function (event) {
var element = event.findElement(selector);
element = event.findElement(selector);
if (element) handler.call(context || element, event, element);
};
wrapper.handler = handler;
@ -49,13 +56,14 @@ Object.extend(document, (function () {
return wrapper;
}
return {
delegate: function (selector, eventName, handler, context) {
delegate: function (selector, eventName) {
var wrapper = createWrapper.apply(null, arguments);
if (wrapper) document.observe(eventName, wrapper);
return document;
},
stopDelegating: function (selector, eventName, handler) {
stopDelegating: function (selector, eventName) {
var length = arguments.length;
var wrapper;
switch (length) {
case 2:
getWrappersForSelector(selector, eventName).each(function (wrapper) {
@ -73,7 +81,7 @@ Object.extend(document, (function () {
});
break;
default:
var wrapper = destroyWrapper.apply(null, arguments);
wrapper = destroyWrapper.apply(null, arguments);
if (wrapper) document.stopObserving(eventName, wrapper);
}
return document;
@ -81,7 +89,7 @@ Object.extend(document, (function () {
};
})());
var Observable = (function () {
Observable = (function () {
function getEventName(nameA, namespace) {
var name = nameA.substring(2);
if (namespace) name = namespace + ':' + name;
@ -89,8 +97,8 @@ var Observable = (function () {
}
function getHandlers(klass) {
var proto = klass.prototype,
namespace = proto.namespace;
var proto = klass.prototype;
var namespace = proto.namespace;
return Object.keys(proto).grep(/^on/).inject(window.$H(), function (handlers, name) {
if (name === 'onDomLoaded') return handlers;
handlers.set(getEventName(name, namespace), getWrapper(proto[name], klass));
@ -111,9 +119,9 @@ var Observable = (function () {
}
return {
observe: function (selector) {
var klass = this;
if (!this.handlers) this.handlers = {};
if (this.handlers[selector]) return;
var klass = this;
if (this.prototype.onDomLoaded) {
if (document.loaded) {
onDomLoad(selector, klass);
@ -146,8 +154,9 @@ Object.extend(window.Droppables, {
return proceed(drop);
}),
show: function (point, element) {
var drop;
var affected = [];
if (!this.drops.length) return;
var drop, affected = [];
this.drops.each(function (drop) {
if (window.Droppables.isAffected(point, element, drop)) affected.push(drop);
});
@ -159,13 +168,13 @@ Object.extend(window.Droppables, {
if (drop !== this.last_active) window.Droppables.activate(drop, element);
}
},
displayArea: function (draggable) {
displayArea: function () {
if (!this.drops.length) return;
// hide controls when displaying drop areas.
WysijaForm.hideBlockControls();
this.drops.each(function (drop, iterator) {
this.drops.each(function (drop) {
if (drop.element.hasClassName('block_placeholder')) {
drop.element.addClassName('active');
}
@ -173,7 +182,7 @@ Object.extend(window.Droppables, {
},
hideArea: function () {
if (!this.drops.length) return;
this.drops.each(function (drop, iterator) {
this.drops.each(function (drop) {
if (drop.element.hasClassName('block_placeholder')) {
drop.element.removeClassName('active');
} else if (drop.element.hasClassName('image_placeholder')) {
@ -195,7 +204,7 @@ Object.extend(window.Droppables, {
- set a maximum number of items to be stored
*/
var WysijaHistory = {
WysijaHistory = {
container: 'mailpoet_form_history',
size: 30,
enqueue: function (element) {
@ -239,7 +248,7 @@ var WysijaHistory = {
};
/* MailPoet Form */
var WysijaForm = {
WysijaForm = {
version: '0.7',
options: {
container: 'mailpoet_form_container',
@ -292,6 +301,7 @@ var WysijaForm = {
});
},
load: function (data) {
var settings_elements;
if (data === undefined) return;
// load body
@ -302,7 +312,7 @@ var WysijaForm = {
});
// load settings
var settings_elements = window.$('mailpoet_form_settings').getElements();
settings_elements = window.$('mailpoet_form_settings').getElements();
settings_elements.each(function (setting) {
// skip lists
if (setting.name === 'segments') {
@ -324,13 +334,13 @@ var WysijaForm = {
}
},
save: function () {
var position = 1,
data = {
name: window.$F('mailpoet_form_name'),
settings: window.$('mailpoet_form_settings').serialize(true),
body: [],
styles: (window.MailPoet.CodeEditor !== undefined) ? window.MailPoet.CodeEditor.getValue() : null
};
var position = 1;
var data = {
name: window.$F('mailpoet_form_name'),
settings: window.$('mailpoet_form_settings').serialize(true),
body: [],
styles: (window.MailPoet.CodeEditor !== undefined) ? window.MailPoet.CodeEditor.getValue() : null
};
// body
WysijaForm.getBlocks().each(function (b) {
var block_data = (typeof (b.block['save']) === 'function') ? b.block.save() : null;
@ -401,6 +411,7 @@ var WysijaForm = {
return data;
},
toggleWidgets: function () {
var hasSegmentSelection;
window.$$('a[wysija_unique="1"]').invoke('removeClassName', 'disabled');
// loop through each unique field already inserted in the editor and disable its toolbar equivalent
@ -411,7 +422,7 @@ var WysijaForm = {
}
});
var hasSegmentSelection = WysijaForm.hasSegmentSelection();
hasSegmentSelection = WysijaForm.hasSegmentSelection();
if (hasSegmentSelection) {
window.$('mailpoet_form_segments').writeAttribute('required', false).disable();
@ -426,8 +437,9 @@ var WysijaForm = {
},
isSegmentSelectionValid: function () {
var segment_selection = window.$$('#' + WysijaForm.options.editor + ' [wysija_id="segments"]')[0];
var block;
if (segment_selection !== undefined) {
var block = WysijaForm.get(segment_selection).block.getData();
block = WysijaForm.get(segment_selection).block.getData();
return (
(block.params.values !== undefined)
&&
@ -437,10 +449,12 @@ var WysijaForm = {
return false;
},
setBlockPositions: function (event, target) {
var index = 1;
var block_placeholder;
var previous_placeholder;
// release dragging lock
WysijaForm.locks.dragging = false;
var index = 1;
WysijaForm.getBlocks().each(function (container) {
container.setPosition(index++);
// remove z-index value to avoid issues when resizing images
@ -453,8 +467,8 @@ var WysijaForm = {
if (target !== undefined) {
// get placeholders (previous placeholder matches the placeholder linked to the next block)
var block_placeholder = window.$(target.element.readAttribute('wysija_placeholder')),
previous_placeholder = target.element.previous('.block_placeholder');
block_placeholder = window.$(target.element.readAttribute('wysija_placeholder'));
previous_placeholder = target.element.previous('.block_placeholder');
if (block_placeholder !== null) {
// put block placeholder before the current block
@ -479,23 +493,17 @@ var WysijaForm = {
},
setSettingsPosition: function () {
// get viewport offsets and dimensions
var viewportHeight = document.viewport.getHeight(),
blockPadding = 5;
var viewportHeight = document.viewport.getHeight();
window.$(WysijaForm.options.container).select('.wysija_settings').each(function (element) {
// get parent dimensions and position
var parentDim = element.up('.mailpoet_form_block').getDimensions(),
parentPos = element.up('.mailpoet_form_block').cumulativeOffset(),
is_visible = (parentPos.top <= (WysijaForm.scroll.top + viewportHeight)),
buttonMargin = 5,
relativeTop = buttonMargin;
var parentDim = element.up('.mailpoet_form_block').getDimensions();
var parentPos = element.up('.mailpoet_form_block').cumulativeOffset();
var is_visible = (parentPos.top <= (WysijaForm.scroll.top + viewportHeight));
var buttonMargin = 5;
var relativeTop = buttonMargin;
if (is_visible) {
// desired position is set to center of viewport
var absoluteTop = parseInt(WysijaForm.scroll.top + ((viewportHeight / 2) - (element.getHeight() / 2)), 10),
parentTop = parseInt(parentPos.top - blockPadding, 10),
parentBottom = parseInt(parentPos.top + parentDim.height - blockPadding, 10);
// always center
relativeTop = parseInt((parentDim.height / 2) - (element.getHeight() / 2), 10);
}
@ -519,9 +527,10 @@ var WysijaForm = {
},
setToolbarPosition: function () {
var position;
WysijaForm.initToolbarPosition();
var position = {
position = {
top: WysijaForm.toolbar.y + 'px',
visibility: 'visible'
};
@ -585,10 +594,12 @@ var WysijaForm = {
instances: {},
get: function (element, typ) {
var type = typ;
var id;
var instance;
if (type === undefined) type = 'block';
// identify element
var id = element.identify();
var instance = WysijaForm.instances[id] || new WysijaForm[type.capitalize().camelize()](id);
id = element.identify();
instance = WysijaForm.instances[id] || new WysijaForm[type.capitalize().camelize()](id);
WysijaForm.instances[id] = instance;
return instance;
@ -636,8 +647,8 @@ var WysijaForm = {
},
encodeURIComponent: function (str) {
// check if it's a url and if so, prevent encoding of protocol
var regexp = new RegExp(/^http[s]?:\/\//),
protocol = regexp.exec(str);
var regexp = new RegExp(/^http[s]?:\/\//);
var protocol = regexp.exec(str);
if (protocol === null) {
// this is not a url so encode the whole thing
@ -680,13 +691,13 @@ WysijaForm.DraggableItem = window.Class.create({
},
STYLES: new window.Template('position: absolute; top: #{top}px; left: #{left}px;'),
cloneElement: function () {
var clone = this.element.clone(),
offset = this.element.cumulativeOffset(),
list = this.getList(),
styles = this.STYLES.evaluate({
top: offset.top - list.scrollTop,
left: offset.left - list.scrollLeft
});
var clone = this.element.clone();
var offset = this.element.cumulativeOffset();
var list = this.getList();
var styles = this.STYLES.evaluate({
top: offset.top - list.scrollTop,
left: offset.left - list.scrollLeft
});
clone.setStyle(styles);
clone.addClassName('mailpoet_form_widget');
@ -767,15 +778,17 @@ WysijaForm.Block = window.Class.create({
}
},
makeBlockDroppable: function () {
var block_placeholder;
if (this.isBlockDroppableEnabled() === false) {
var block_placeholder = this.getBlockDroppable();
block_placeholder = this.getBlockDroppable();
window.Droppables.add(block_placeholder.identify(), WysijaForm.blockDropOptions);
block_placeholder.addClassName('enabled');
}
},
removeBlockDroppable: function () {
var block_placeholder;
if (this.isBlockDroppableEnabled()) {
var block_placeholder = this.getBlockDroppable();
block_placeholder = this.getBlockDroppable();
window.Droppables.remove(block_placeholder.identify());
block_placeholder.removeClassName('enabled');
}
@ -807,6 +820,7 @@ WysijaForm.Block = window.Class.create({
return this.element.down('.wysija_controls');
},
setupControls: function () {
var block;
// enable controls
this.controls = this.getControls();
@ -857,9 +871,8 @@ WysijaForm.Block = window.Class.create({
if (this.settingsButton !== null) {
this.settingsButton.observe('click', function (event) {
// TODO: refactor
var block = window.$(event.target).up('.mailpoet_form_block') || null;
block = window.$(event.target).up('.mailpoet_form_block') || null;
if (block !== null) {
var field = WysijaForm.getFieldData(block);
this.editSettings();
}
}.bind(this));
@ -905,18 +918,23 @@ WysijaForm.Block = window.Class.create({
/* Invoked on item dropped */
WysijaForm.Block.create = function (createBlock, target) {
var block = createBlock;
var body;
var block_template;
var template;
var output;
var settings_segments;
if (window.$('form_template_' + block.type) === null) {
return false;
}
var body = window.$(WysijaForm.options.body),
block_template = window.Handlebars.compile(window.$('form_template_block').innerHTML),
template = window.Handlebars.compile(window.$('form_template_' + block.type).innerHTML),
output = '';
body = window.$(WysijaForm.options.body);
block_template = window.Handlebars.compile(window.$('form_template_block').innerHTML);
template = window.Handlebars.compile(window.$('form_template_' + block.type).innerHTML);
output = '';
if (block.type === 'segment') {
if (block.params.values === undefined) {
var settings_segments = window.jQuery('#mailpoet_form_segments').val();
settings_segments = window.jQuery('#mailpoet_form_segments').val();
if (settings_segments !== null && settings_segments.length > 0) {
block.params.values = window.mailpoet_segments.filter(function (segment) {
return (settings_segments.indexOf(segment.id) !== -1);
@ -936,14 +954,13 @@ WysijaForm.Block.create = function (createBlock, target) {
}
// if the drop target was the bottom placeholder
var element = null;
if (target.identify() === 'block_placeholder') {
// insert block at the bottom
element = body.insert(output);
body.insert(output);
// block = body.childElements().last();
} else {
// insert block before the drop target
element = target.insert({
target.insert({
before: output
});
// block = target.previous('.mailpoet_form_block');
@ -984,8 +1001,8 @@ WysijaForm.Widget = window.Class.create(WysijaForm.Block, {
this.setupControls();
},
save: function () {
info('widget -> save');
var data = this.getData();
info('widget -> save');
if (data.element !== undefined) {
delete data.element;
@ -994,8 +1011,8 @@ WysijaForm.Widget = window.Class.create(WysijaForm.Block, {
return data;
},
setData: function (data) {
var current_data = this.getData(),
params = window.$H(current_data.params).merge(data.params).toObject();
var current_data = this.getData();
var params = window.$H(current_data.params).merge(data.params).toObject();
// update type if it changed
if (data.type !== undefined && data.type !== current_data.type) {
@ -1020,16 +1037,20 @@ WysijaForm.Widget = window.Class.create(WysijaForm.Block, {
this.removeBlock();
},
redraw: function (data) {
var options;
var block_template;
var template;
var params;
// set parameters
this.setData(data);
var options = this.getData();
options = this.getData();
// redraw block
var block_template = window.Handlebars.compile(window.$('form_template_block').innerHTML),
template = window.Handlebars.compile(window.$('form_template_' + options.type).innerHTML),
data = window.$H(options).merge({
template: template(options)
}).toObject();
this.element.replace(block_template(data));
block_template = window.Handlebars.compile(window.$('form_template_block').innerHTML);
template = window.Handlebars.compile(window.$('form_template_' + options.type).innerHTML);
params = window.$H(options).merge({
template: template(options)
}).toObject();
this.element.replace(block_template(params));
WysijaForm.init();
},
@ -1039,7 +1060,7 @@ WysijaForm.Widget = window.Class.create(WysijaForm.Block, {
template: window.jQuery('#form_template_field_settings').html(),
data: this.getData(),
onSuccess: function () {
var data = window.jQuery('#form_field_settings').serializeObject();
var data = window.jQuery('#form_field_settings').mailpoetSerializeObject();
this.redraw(data);
}.bind(this)
});
@ -1061,8 +1082,8 @@ function info(value) {
var noop = function () {};
var methods = ['assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 'markTimeline', 'profile', 'profileEnd', 'markTimeline', 'table', 'time', 'timeEnd', 'timeStamp', 'trace', 'warn'];
var length = methods.length;
window.console = {};
var console = {};
window.console = {};
while (length--) {
console[methods[length]] = noop;
}

View File

@ -1,26 +1,28 @@
define('handlebars_helpers', ['handlebars'], function (Handlebars) {
// Handlebars helpers
Handlebars.registerHelper('concat', function () {
var size = (arguments.length - 1),
output = '';
for (var i = 0; i < size; i++) {
var size = (arguments.length - 1);
var output = '';
var i;
for (i = 0; i < size; i++) {
output += arguments[i];
}
return output;
});
Handlebars.registerHelper('number_format', function (value, block) {
Handlebars.registerHelper('number_format', function (value) {
return Number(value).toLocaleString();
});
Handlebars.registerHelper('date_format', function (timestamp, block) {
var f;
if (window.moment) {
if (timestamp === undefined || isNaN(timestamp) || timestamp <= 0) {
return;
}
// set date format
var f = block.hash.format || 'MMM Do, YYYY';
// check if we passed a timestamp
// set date format
f = block.hash.format || 'MMM Do, YYYY';
// check if we passed a timestamp
if (parseInt(timestamp, 10) == timestamp) {
return window.moment.unix(timestamp).format(f);
} else {
@ -59,25 +61,24 @@ define('handlebars_helpers', ['handlebars'], function (Handlebars) {
case '||':
return (v1 || v2) ? options.fn(this) : options.inverse(this);
case 'in':
var values = v2.split(',');
return (v2.indexOf(v1) !== -1) ? options.fn(this) : options.inverse(this);
default:
return options.inverse(this);
}
});
Handlebars.registerHelper('nl2br', function (value, block) {
Handlebars.registerHelper('nl2br', function (value) {
return value.gsub('\n', '<br />');
});
Handlebars.registerHelper('json_encode', function (value, block) {
Handlebars.registerHelper('json_encode', function (value) {
return JSON.stringify(value);
});
Handlebars.registerHelper('json_decode', function (value, block) {
Handlebars.registerHelper('json_decode', function (value) {
return JSON.parse(value);
});
Handlebars.registerHelper('url', function (value, block) {
Handlebars.registerHelper('url', function (value) {
var url = window.location.protocol + '//' + window.location.host + window.location.pathname;
return url + value;
@ -90,16 +91,17 @@ define('handlebars_helpers', ['handlebars'], function (Handlebars) {
return value;
}
});
Handlebars.registerHelper('lookup', function (obj, field, options) {
Handlebars.registerHelper('lookup', function (obj, field) {
return obj && obj[field];
});
Handlebars.registerHelper('rsa_key', function (value, block) {
// extract all lines into an array
Handlebars.registerHelper('rsa_key', function (value) {
var lines;
// extract all lines into an array
if (value === undefined) return '';
var lines = value.trim().split('\n');
lines = value.trim().split('\n');
// remove header & footer
lines.shift();
@ -109,7 +111,7 @@ define('handlebars_helpers', ['handlebars'], function (Handlebars) {
return lines.join('');
});
Handlebars.registerHelper('trim', function (value, block) {
Handlebars.registerHelper('trim', function (value) {
if (value === null || value === undefined) return '';
return value.trim();
});
@ -126,10 +128,10 @@ define('handlebars_helpers', ['handlebars'], function (Handlebars) {
*/
Handlebars.registerHelper('ellipsis', function (str, limit, append) {
var strAppend = append;
var sanitized = str.replace(/(<([^>]+)>)/g, '');
if (strAppend === undefined) {
strAppend = '';
}
var sanitized = str.replace(/(<([^>]+)>)/g, '');
if (sanitized.length > limit) {
return sanitized.substr(0, limit - strAppend.length) + strAppend;
} else {

View File

@ -23,9 +23,9 @@ function printData(data) {
height: '400px',
}}
/>);
} else {
return (<p>{MailPoet.I18n.t('systemInfoDataError')}</p>);
}
return (<p>{MailPoet.I18n.t('systemInfoDataError')}</p>);
}
function KnowledgeBase() {

View File

@ -23,21 +23,21 @@ define(
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
$.fn.serializeObject = function (coerce) {
var obj = {},
coerce_types = { true: !0, false: !1, null: null };
$.fn.mailpoetSerializeObject = function (coerce) {
var obj = {};
var coerce_types = { true: !0, false: !1, null: null };
// Iterate over all name=value pairs.
$.each(this.serializeArray(), function (j, v) {
var key = v.name,
val = v.value,
cur = obj,
i = 0,
var key = v.name;
var val = v.value;
var cur = obj;
var i = 0;
// If key is more complex than 'foo', like 'a[]' or 'a[b][c]', split it
// into its component parts.
keys = key.split(']['),
keys_last = keys.length - 1;
// If key is more complex than 'foo', like 'a[]' or 'a[b][c]', split it
// into its component parts.
var keys = key.split('][');
var keys_last = keys.length - 1;
// If the first keys part contains [ and the last ends with ], then []
// are correctly balanced.
@ -106,4 +106,4 @@ define(
return $;
}
);
);

View File

@ -223,24 +223,24 @@ const ListingItems = React.createClass({
</tr>
</tbody>
);
} else {
const select_all_classes = classNames(
}
const select_all_classes = classNames(
'mailpoet_select_all',
{ mailpoet_hidden: (
{ mailpoet_hidden: (
this.props.selection === false
|| (this.props.count <= this.props.limit)
),
}
}
);
return (
<tbody>
<tr className={select_all_classes}>
<td colSpan={
return (
<tbody>
<tr className={select_all_classes}>
<td colSpan={
this.props.columns.length
+ (this.props.is_selectable ? 1 : 0)
}>
{
{
(this.props.selection !== 'all')
? MailPoet.I18n.t('selectAllLabel')
: MailPoet.I18n.t('selectedAllLabel').replace(
@ -249,41 +249,41 @@ const ListingItems = React.createClass({
)
}
&nbsp;
<a
onClick={this.props.onSelectAll}
href="javascript:;">{
<a
onClick={this.props.onSelectAll}
href="javascript:;">{
(this.props.selection !== 'all')
? MailPoet.I18n.t('selectAllLink')
: MailPoet.I18n.t('clearSelection')
}</a>
</td>
</tr>
</td>
</tr>
{this.props.items.map((item, index) => {
const renderItem = item;
renderItem.id = parseInt(item.id, 10);
renderItem.selected = (this.props.selected_ids.indexOf(renderItem.id) !== -1);
{this.props.items.map((item, index) => {
const renderItem = item;
renderItem.id = parseInt(item.id, 10);
renderItem.selected = (this.props.selected_ids.indexOf(renderItem.id) !== -1);
return (
<ListingItem
columns={this.props.columns}
onSelectItem={this.props.onSelectItem}
onRenderItem={this.props.onRenderItem}
onDeleteItem={this.props.onDeleteItem}
onRestoreItem={this.props.onRestoreItem}
onTrashItem={this.props.onTrashItem}
onRefreshItems={this.props.onRefreshItems}
selection={this.props.selection}
is_selectable={this.props.is_selectable}
item_actions={this.props.item_actions}
group={this.props.group}
key={`item-${renderItem.id}-${index}`}
item={renderItem} />
);
})}
</tbody>
);
return (
<ListingItem
columns={this.props.columns}
onSelectItem={this.props.onSelectItem}
onRenderItem={this.props.onRenderItem}
onDeleteItem={this.props.onDeleteItem}
onRestoreItem={this.props.onRestoreItem}
onTrashItem={this.props.onTrashItem}
onRefreshItems={this.props.onRefreshItems}
selection={this.props.selection}
is_selectable={this.props.is_selectable}
item_actions={this.props.item_actions}
group={this.props.group}
key={`item-${renderItem.id}-${index}`}
item={renderItem} />
);
})}
</tbody>
);
}
},
});
@ -321,14 +321,13 @@ const Listing = React.createClass({
if (params.splat) {
params.splat.split('/').map((param) => {
const [key, value] = this.getParam(param);
const filters = {};
switch (key) {
case 'filter':
const filters = {};
value.split('&').map((pair) => {
const [k, v] = pair.split('=');
filters[k] = v;
}
);
});
state.filter = filters;
break;
@ -414,9 +413,9 @@ const Listing = React.createClass({
if (base_url !== null) {
base_url = this.setBaseUrlParams(base_url);
return `/${base_url}/${params}`;
} else {
return `/${params}`;
}
return `/${params}`;
},
setBaseUrlParams: function (base_url) {
let ret = base_url;
@ -658,8 +657,8 @@ const Listing = React.createClass({
});
},
handleSelectItem: function (id, is_checked) {
let selected_ids = this.state.selected_ids,
selection = false;
let selected_ids = this.state.selected_ids;
let selection = false;
if (is_checked) {
selected_ids = jQuery.merge(selected_ids, [id]);

View File

@ -59,125 +59,125 @@ define([
render: function () {
if (this.props.count === 0) {
return false;
} else {
let pagination = false;
let firstPage = (
<span aria-hidden="true" className="tablenav-pages-navspan">«</span>
}
let pagination = false;
let firstPage = (
<span aria-hidden="true" className="tablenav-pages-navspan">«</span>
);
let previousPage = (
<span aria-hidden="true" className="tablenav-pages-navspan"></span>
let previousPage = (
<span aria-hidden="true" className="tablenav-pages-navspan"></span>
);
let nextPage = (
<span aria-hidden="true" className="tablenav-pages-navspan"></span>
let nextPage = (
<span aria-hidden="true" className="tablenav-pages-navspan"></span>
);
let lastPage = (
<span aria-hidden="true" className="tablenav-pages-navspan">»</span>
let lastPage = (
<span aria-hidden="true" className="tablenav-pages-navspan">»</span>
);
if (this.props.limit > 0 && this.props.count > this.props.limit) {
if (this.props.page > 1) {
previousPage = (
<a href="javascript:;"
onClick={this.setPreviousPage}
className="prev-page">
<span className="screen-reader-text">{MailPoet.I18n.t('previousPage')}</span>
<span aria-hidden="true"></span>
</a>
if (this.props.limit > 0 && this.props.count > this.props.limit) {
if (this.props.page > 1) {
previousPage = (
<a href="javascript:;"
onClick={this.setPreviousPage}
className="prev-page">
<span className="screen-reader-text">{MailPoet.I18n.t('previousPage')}</span>
<span aria-hidden="true"></span>
</a>
);
}
if (this.props.page > 2) {
firstPage = (
<a href="javascript:;"
onClick={this.setFirstPage}
className="first-page">
<span className="screen-reader-text">{MailPoet.I18n.t('firstPage')}</span>
<span aria-hidden="true">«</span>
</a>
);
}
if (this.props.page < this.getLastPage()) {
nextPage = (
<a href="javascript:;"
onClick={this.setNextPage}
className="next-page">
<span className="screen-reader-text">{MailPoet.I18n.t('nextPage')}</span>
<span aria-hidden="true"></span>
</a>
);
}
if (this.props.page < this.getLastPage() - 1) {
lastPage = (
<a href="javascript:;"
onClick={this.setLastPage}
className="last-page">
<span className="screen-reader-text">{MailPoet.I18n.t('lastPage')}</span>
<span aria-hidden="true">»</span>
</a>
);
}
let pageValue = this.props.page;
if (this.state.page !== null) {
pageValue = this.state.page;
}
pagination = (
<span className="pagination-links">
{firstPage}
&nbsp;
{previousPage}
&nbsp;
<span className="paging-input">
<label
className="screen-reader-text"
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="2"
ref="page"
value={pageValue}
name="paged"
id="current-page-selector"
className="current-page" />
&nbsp;{MailPoet.I18n.t('pageOutOf')}&nbsp;
<span className="total-pages">
{Math.ceil(this.props.count / this.props.limit).toLocaleString()}
</span>
</span>
&nbsp;
{nextPage}
&nbsp;
{lastPage}
</span>
);
}
const classes = classNames(
if (this.props.page > 2) {
firstPage = (
<a href="javascript:;"
onClick={this.setFirstPage}
className="first-page">
<span className="screen-reader-text">{MailPoet.I18n.t('firstPage')}</span>
<span aria-hidden="true">«</span>
</a>
);
}
if (this.props.page < this.getLastPage()) {
nextPage = (
<a href="javascript:;"
onClick={this.setNextPage}
className="next-page">
<span className="screen-reader-text">{MailPoet.I18n.t('nextPage')}</span>
<span aria-hidden="true"></span>
</a>
);
}
if (this.props.page < this.getLastPage() - 1) {
lastPage = (
<a href="javascript:;"
onClick={this.setLastPage}
className="last-page">
<span className="screen-reader-text">{MailPoet.I18n.t('lastPage')}</span>
<span aria-hidden="true">»</span>
</a>
);
}
let pageValue = this.props.page;
if (this.state.page !== null) {
pageValue = this.state.page;
}
pagination = (
<span className="pagination-links">
{firstPage}
&nbsp;
{previousPage}
&nbsp;
<span className="paging-input">
<label
className="screen-reader-text"
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="2"
ref="page"
value={pageValue}
name="paged"
id="current-page-selector"
className="current-page" />
{MailPoet.I18n.t('pageOutOf')}&nbsp;
<span className="total-pages">
{Math.ceil(this.props.count / this.props.limit).toLocaleString()}
</span>
</span>
&nbsp;
{nextPage}
&nbsp;
{lastPage}
</span>
);
}
const classes = classNames(
'tablenav-pages',
{ 'one-page': (this.props.count <= this.props.limit) }
);
let numberOfItemsLabel;
if (this.props.count == 1) {
numberOfItemsLabel = MailPoet.I18n.t('numberOfItemsSingular');
} else {
numberOfItemsLabel = MailPoet.I18n.t('numberOfItemsMultiple')
let numberOfItemsLabel;
if (this.props.count == 1) {
numberOfItemsLabel = MailPoet.I18n.t('numberOfItemsSingular');
} else {
numberOfItemsLabel = MailPoet.I18n.t('numberOfItemsMultiple')
.replace('%$1d', this.props.count.toLocaleString());
}
return (
<div className={classes}>
<span className="displaying-num">{ numberOfItemsLabel }</span>
{ pagination }
</div>
);
}
return (
<div className={classes}>
<span className="displaying-num">{ numberOfItemsLabel }</span>
{ pagination }
</div>
);
},
});

View File

@ -10,7 +10,7 @@ define([
handleSearch: function (e) {
e.preventDefault();
this.props.onSearch(
this.refs.search.value
this.refs.search.value.trim()
);
},
componentWillReceiveProps: function (nextProps) {
@ -19,27 +19,27 @@ define([
render: function () {
if (this.props.search === false) {
return false;
} else {
return (
<form name="search" onSubmit={this.handleSearch}>
<p className="search-box">
<label htmlFor="search_input" className="screen-reader-text">
{MailPoet.I18n.t('searchLabel')}
</label>
<input
type="search"
id="search_input"
ref="search"
name="s"
defaultValue={this.props.search} />
<input
type="submit"
defaultValue={MailPoet.I18n.t('searchLabel')}
className="button" />
</p>
</form>
);
}
return (
<form name="search" onSubmit={this.handleSearch}>
<p className="search-box">
<label htmlFor="search_input" className="screen-reader-text">
{MailPoet.I18n.t('searchLabel')}
</label>
<input
type="search"
id="search_input"
ref="search"
name="s"
defaultValue={this.props.search} />
<input
type="submit"
defaultValue={MailPoet.I18n.t('searchLabel')}
className="button" />
</p>
</form>
);
},
});

View File

@ -116,6 +116,7 @@ define('modal', ['mailpoet', 'jquery'],
}
},
init: function (options) {
var modal;
if (this.initialized === true) {
this.close();
}
@ -134,7 +135,7 @@ define('modal', ['mailpoet', 'jquery'],
if (this.options.type !== null) {
// insert modal depending on its type
if (this.options.type === 'popup') {
var modal = this.compileTemplate(
modal = this.compileTemplate(
this.templates[this.options.type]
);
// create modal
@ -178,7 +179,7 @@ define('modal', ['mailpoet', 'jquery'],
return this;
},
initOverlay: function (toggle) {
initOverlay: function () {
if (jQuery('#mailpoet_modal_overlay').length === 0) {
// insert overlay into the DOM
jQuery('body').append(this.templates.overlay);
@ -343,20 +344,21 @@ define('modal', ['mailpoet', 'jquery'],
return this;
},
setPosition: function () {
var screenWidth;
var screenHeight;
var modalWidth;
var modalHeight;
switch (this.options.type) {
case 'popup':
var screenWidth = jQuery(window).width(),
screenHeight = jQuery(window).height(),
modalWidth = jQuery('.mailpoet_' + this.options.type + '_wrapper').width(),
modalHeight = jQuery('.mailpoet_' + this.options.type + '_wrapper').height();
screenWidth = jQuery(window).width();
screenHeight = jQuery(window).height();
modalWidth = jQuery('.mailpoet_'+ this.options.type +'_wrapper').width();
modalHeight = jQuery('.mailpoet_'+ this.options.type +'_wrapper').height();
var top = Math.max(48, parseInt((screenHeight / 2) - (modalHeight / 2))),
left = Math.max(0, parseInt((screenWidth / 2) - (modalWidth / 2)));
// set position of popup depending on screen dimensions.
// set position of popup depending on screen dimensions.
jQuery('#mailpoet_popup').css({
top: top,
left: left
top: Math.max(48, parseInt((screenHeight / 2) - (modalHeight / 2))),
left: Math.max(0, parseInt((screenWidth / 2) - (modalWidth / 2)))
});
break;
case 'panel':
@ -384,9 +386,6 @@ define('modal', ['mailpoet', 'jquery'],
// remember the previously focused element
this.prevFocus = jQuery(':focus');
// add a flag on the body so that we can prevent scrolling
jQuery('body').addClass('mailpoet_modal_opened');
// show popup
jQuery('#mailpoet_' + this.options.type).show();
@ -436,7 +435,7 @@ define('modal', ['mailpoet', 'jquery'],
.removeClass('mailpoet_modal_highlight');
return this;
},
hideModal: function (callback) {
hideModal: function () {
// set modal as closed
this.opened = false;
@ -446,12 +445,9 @@ define('modal', ['mailpoet', 'jquery'],
// remove class on highlighted elements
this.highlightOff();
// remove class from body to let it be scrollable
jQuery('body').removeClass('mailpoet_modal_opened');
return this;
},
showOverlay: function (force) {
showOverlay: function () {
jQuery('#mailpoet_modal_overlay').show();
return this;
},

View File

@ -1,12 +1,8 @@
define([
'backbone',
'backbone.marionette',
'backbone.radio',
'jquery',
'underscore',
'handlebars',
'handlebars_helpers'
], function (Backbone, Marionette, BackboneRadio, jQuery, _, Handlebars) {
'backbone.radio'
], function (Backbone, Marionette, BackboneRadio) {
var Radio = BackboneRadio;
var AppView = Marionette.View.extend({

View File

@ -8,13 +8,13 @@ define([
'newsletter_editor/behaviors/BehaviorsLookup',
'mailpoet',
'spectrum'
], function (Marionette, BehaviorsLookup, MailPoet, Spectrum) {
], function (Marionette, BehaviorsLookup, MailPoet) {
var BL = BehaviorsLookup;
BL.ColorPickerBehavior = Marionette.Behavior.extend({
onRender: function () {
var that = this,
preferredFormat = 'hex6';
var that = this;
var preferredFormat = 'hex6';
this.view.$('.mailpoet_color').each(function () {
var $input = that.view.$(this);
var updateColorInput = function (color) {

View File

@ -25,11 +25,11 @@ define([
this.addDropZone();
}
},
addDropZone: function (_event) {
var that = this,
view = this.view,
domElement = that.$el.get(0),
acceptableElementSelector;
addDropZone: function () {
var that = this;
var view = this.view;
var domElement = that.$el.get(0);
var acceptableElementSelector;
// TODO: Extract this limitation code to be controlled from containers
if (this.view.renderOptions.depth === 0) {
@ -47,11 +47,11 @@ define([
interact(domElement).dropzone({
accept: acceptableElementSelector,
overlap: 'pointer', // Mouse pointer denotes location of a droppable
ondragenter: function (event) {
ondragenter: function () {
// 1. Visually mark block as active for dropping
view.$el.addClass('mailpoet_drop_active');
},
ondragleave: function (event) {
ondragleave: function () {
// 1. Remove visual markings of active dropping container
// 2. Remove visual markings of drop position visualization
that.cleanup();
@ -63,20 +63,26 @@ define([
// 3b. If insertion is special, compute position (which side) and which cell the insertion belongs to
// 4. If insertion at that position is not visualized, display position visualization there, remove other visualizations from this container
var dropPosition = that.getDropPosition(
event.dragmove.pageX,
event.dragmove.pageY,
view.$el,
view.model.get('orientation'),
view.model.get('blocks').length
),
element = view.$el,
markerWidth = '',
markerHeight = '',
containerOffset = element.offset(),
viewCollection = that.getCollection(),
marker, targetModel, targetView, targetElement,
topOffset, leftOffset, isLastBlockInsertion,
$targetBlock, margin;
event.dragmove.pageX,
event.dragmove.pageY,
view.$el,
view.model.get('orientation'),
view.model.get('blocks').length
);
var element = view.$el;
var markerWidth = '';
var markerHeight = '';
var containerOffset = element.offset();
var viewCollection = that.getCollection();
var marker;
var targetModel;
var targetView;
var targetElement;
var topOffset;
var leftOffset;
var isLastBlockInsertion;
var $targetBlock;
var margin;
if (dropPosition === undefined) return;
@ -178,15 +184,19 @@ define([
// 4. Perform cleanup actions
var dropPosition = that.getDropPosition(
event.dragEvent.pageX,
event.dragEvent.pageY,
view.$el,
view.model.get('orientation'),
view.model.get('blocks').length
),
droppableModel = event.draggable.getDropModel(),
viewCollection = that.getCollection(),
droppedView, droppedModel, index, tempCollection, tempCollection2;
event.dragEvent.pageX,
event.dragEvent.pageY,
view.$el,
view.model.get('orientation'),
view.model.get('blocks').length
);
var droppableModel = event.draggable.getDropModel();
var viewCollection = that.getCollection();
var droppedView;
var index;
var tempCollection;
var tempCollection2;
var tempModel;
if (dropPosition === undefined) return;
@ -210,7 +220,7 @@ define([
} else {
// Special insertion by replacing target block with collection
// and inserting dropModel into that
var tempModel = viewCollection.at(dropPosition.index);
tempModel = viewCollection.at(dropPosition.index);
tempCollection = new (window.EditorApplication.getBlockTypeModel('container'))({
orientation: (view.model.get('orientation') === 'vertical') ? 'horizontal' : 'vertical'
@ -272,26 +282,30 @@ define([
this.view.$('.mailpoet_drop_marker').remove();
},
getDropPosition: function (eventX, eventY, is_unsafe) {
var SPECIAL_AREA_INSERTION_WIDTH = 0.00, // Disable special insertion. Default: 0.3
var SPECIAL_AREA_INSERTION_WIDTH = 0.00; // Disable special insertion. Default: 0.3
element = this.view.$el,
orientation = this.view.model.get('orientation'),
var element = this.view.$el;
var orientation = this.view.model.get('orientation');
elementOffset = element.offset(),
elementPageX = elementOffset.left,
elementPageY = elementOffset.top,
elementWidth = element.outerWidth(true),
elementHeight = element.outerHeight(true),
var elementOffset = element.offset();
var elementPageX = elementOffset.left;
var elementPageY = elementOffset.top;
var elementWidth = element.outerWidth(true);
var elementHeight = element.outerHeight(true);
relativeX = eventX - elementPageX,
relativeY = eventY - elementPageY,
var relativeX = eventX - elementPageX;
var relativeY = eventY - elementPageY;
relativeOffset, elementLength,
var relativeOffset;
var elementLength;
canAcceptNormalInsertion = this._canAcceptNormalInsertion(),
canAcceptSpecialInsertion = this._canAcceptSpecialInsertion(),
var canAcceptNormalInsertion = this._canAcceptNormalInsertion();
var canAcceptSpecialInsertion = this._canAcceptSpecialInsertion();
insertionType, index, position, indexAndPosition;
var insertionType;
var index;
var position;
var indexAndPosition;
var unsafe = !!is_unsafe;
@ -359,12 +373,14 @@ define([
// target element if event happens on the second half of the element.
// Halves depend on orientation.
var index = this._computeCellIndex(eventX, eventY),
// TODO: Handle case when there are no children, container is empty
targetView = this.getChildren().findByModel(this.getCollection().at(index)),
orientation = this.view.model.get('orientation'),
element = targetView.$el,
eventOffset, closeOffset, elementDimension;
var index = this._computeCellIndex(eventX, eventY);
// TODO: Handle case when there are no children, container is empty
var targetView = this.getChildren().findByModel(this.getCollection().at(index));
var orientation = this.view.model.get('orientation');
var element = targetView.$el;
var eventOffset;
var closeOffset;
var elementDimension;
if (orientation === 'vertical') {
eventOffset = eventY;
@ -394,39 +410,40 @@ define([
return this._computeCellIndex(eventX, eventY);
},
_computeCellIndex: function (eventX, eventY) {
var orientation = this.view.model.get('orientation'),
eventOffset = (orientation === 'vertical') ? eventY : eventX,
resultView = this.getChildren().find(function (view) {
var element = view.$el,
closeOffset, farOffset;
var orientation = this.view.model.get('orientation');
var eventOffset = (orientation === 'vertical') ? eventY : eventX;
var resultView = this.getChildren().find(function (view) {
var element = view.$el;
var closeOffset;
var farOffset;
if (orientation === 'vertical') {
closeOffset = element.offset().top;
farOffset = element.outerHeight(true);
} else {
closeOffset = element.offset().left;
farOffset = element.outerWidth(true);
}
farOffset += closeOffset;
if (orientation === 'vertical') {
closeOffset = element.offset().top;
farOffset = element.outerHeight(true);
} else {
closeOffset = element.offset().left;
farOffset = element.outerWidth(true);
}
farOffset += closeOffset;
return closeOffset <= eventOffset && eventOffset <= farOffset;
});
return closeOffset <= eventOffset && eventOffset <= farOffset;
});
var index = (typeof resultView === 'object') ? resultView._index : 0;
return index;
},
_canAcceptNormalInsertion: function () {
var orientation = this.view.model.get('orientation'),
depth = this.view.renderOptions.depth,
childCount = this.getChildren().length;
var orientation = this.view.model.get('orientation');
var depth = this.view.renderOptions.depth;
var childCount = this.getChildren().length;
// Note that depth is zero indexed. Root container has depth=0
return orientation === 'vertical' || (orientation === 'horizontal' && depth === 1 && childCount < this.options.columnLimit);
},
_canAcceptSpecialInsertion: function () {
var orientation = this.view.model.get('orientation'),
depth = this.view.renderOptions.depth,
childCount = this.getChildren().length;
var orientation = this.view.model.get('orientation');
var depth = this.view.renderOptions.depth;
var childCount = this.getChildren().length;
return depth === 0 || (depth === 1 && orientation === 'horizontal' && childCount <= this.options.columnLimit);
},
getCollectionView: function () {

View File

@ -28,12 +28,12 @@ define([
throw "Missing 'drop' function for DraggableBehavior";
},
onDrop: function (model, view) {},
testAttachToInstance: function (model, view) { return true; }
onDrop: function () {},
testAttachToInstance: function () { return true; }
},
onRender: function () {
var that = this,
interactable;
var that = this;
var interactable;
// Give instances more control over whether Draggable should be applied
if (!this.options.testAttachToInstance(this.view.model, this.view)) return;
@ -49,16 +49,19 @@ define([
onstart: function (startEvent) {
var event = startEvent;
var centerXOffset;
var centerYOffset;
var tempClone;
var clone;
var $clone;
if (that.options.cloneOriginal === true) {
// Use substitution instead of a clone
var tempClone = (_.isFunction(that.options.onDragSubstituteBy)) ? that.options.onDragSubstituteBy(that) : undefined,
// Or use a clone
clone = tempClone || event.target.cloneNode(true),
$original = jQuery(event.target),
$clone = jQuery(clone),
centerXOffset, centerYOffset, parentOffset;
tempClone = (_.isFunction(that.options.onDragSubstituteBy)) ? that.options.onDragSubstituteBy(that) : undefined;
// Or use a clone
clone = tempClone || event.target.cloneNode(true);
jQuery(event.target);
$clone = jQuery(clone);
$clone.addClass('mailpoet_droppable_active');
$clone.css('position', 'absolute');
@ -85,10 +88,10 @@ define([
},
// call this function on every dragmove event
onmove: function (event) {
var target = event.target,
// keep the dragged position in the data-x/data-y attributes
x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx,
y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
var target = event.target;
// keep the dragged position in the data-x/data-y attributes
var x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
var y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
// translate the element
target.style.transform = 'translate(' + x + 'px, ' + y + 'px)';

View File

@ -14,12 +14,13 @@ define([
defaults: {
elementSelector: null,
resizeHandleSelector: true, // true will use edges of the element itself
transformationFunction: function (y) { return y; }, // for blocks that use the default onResize function
minLength: 0,
maxLength: Infinity,
modelField: 'styles.block.height',
onResize: function (event) {
var currentLength = parseFloat(this.view.model.get(this.options.modelField)),
newLength = currentLength + event.y;
var currentLength = parseFloat(this.view.model.get(this.options.modelField));
var newLength = currentLength + this.options.transformationFunction(event.dy);
newLength = Math.min(this.options.maxLength, Math.max(this.options.minLength, newLength));
this.view.model.set(this.options.modelField, newLength + 'px');
}
@ -36,8 +37,8 @@ define([
}
},
attachResize: function () {
var domElement = (this.options.elementSelector === null) ? this.view.$el.get(0) : this.view.$(this.options.elementSelector).get(0),
that = this;
var domElement = (this.options.elementSelector === null) ? this.view.$el.get(0) : this.view.$(this.options.elementSelector).get(0);
var that = this;
interact(domElement).resizable({
// axis: 'y',
edges: {
@ -47,15 +48,14 @@ define([
bottom: (typeof this.options.resizeHandleSelector === 'string') ? this.view.$(this.options.resizeHandleSelector).get(0) : this.options.resizeHandleSelector
}
})
.on('resizestart', function (event) {
.on('resizestart', function () {
that.isBeingResized = true;
that.$el.addClass('mailpoet_resize_active');
})
.on('resizemove', function (event) {
}).on('resizemove', function (event) {
var onResize = that.options.onResize.bind(that);
return onResize(event);
})
.on('resizeend', function (event) {
.on('resizeend', function () {
that.isBeingResized = null;
that.$el.removeClass('mailpoet_resize_active');
});

View File

@ -24,9 +24,9 @@ define([
ui.item.removeData('previousIndex');
},
update: function (event, ui) {
var previousIndex = ui.item.data('previousIndex'),
newIndex = ui.item.index(),
model = collection.at(previousIndex);
var previousIndex = ui.item.data('previousIndex');
var newIndex = ui.item.index();
var model = collection.at(previousIndex);
// Replicate DOM changes. Move target model to a new position
// within the collection

View File

@ -42,7 +42,7 @@ define([
relative_urls: false,
remove_script_host: false,
convert_urls: true,
urlconverter_callback: function (url, node, on_save, name) {
urlconverter_callback: function (url) {
if (url.match(/\[.+\]/g)) {
// Do not convert URLs with shortcodes
return url;
@ -57,7 +57,7 @@ define([
plugins: this.options.plugins,
setup: function (editor) {
editor.on('change', function (e) {
editor.on('change', function () {
that.view.triggerMethod('text:editor:change', editor.getContent());
});
@ -71,12 +71,12 @@ define([
}
});
editor.on('focus', function (e) {
editor.on('focus', function () {
that.view.triggerMethod('text:editor:focus');
that._isActivationClick = true;
});
editor.on('blur', function (e) {
editor.on('blur', function () {
that.view.triggerMethod('text:editor:blur');
});
}

View File

@ -30,8 +30,8 @@ define([
'use strict';
var Module = {},
base = BaseBlock;
var Module = {};
var base = BaseBlock;
Module.ALCSupervisor = SuperModel.extend({
initialize: function () {
@ -43,12 +43,13 @@ define([
);
},
refresh: function () {
var blocks;
var models = App.findModels(function (model) {
return model.get('type') === 'automatedLatestContent';
}) || [];
if (models.length === 0) return;
var blocks = _.map(models, function (model) {
blocks = _.map(models, function (model) {
return model.toJSON();
});
@ -60,8 +61,8 @@ define([
_.each(
_.zip(models, renderedBlocks),
function (args) {
var model = args[0],
contents = args[1];
var model = args[0];
var contents = args[1];
model.trigger('refreshPosts', contents);
}
);
@ -150,12 +151,12 @@ define([
}),
onDragSubstituteBy: function () { return Module.AutomatedLatestContentWidgetView; },
onRender: function () {
var ContainerView = App.getBlockTypeView('container'),
renderOptions = {
disableTextEditor: true,
disableDragAndDrop: true,
emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay')
};
var ContainerView = App.getBlockTypeView('container');
var renderOptions = {
disableTextEditor: true,
disableDragAndDrop: true,
emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay')
};
this.toolsView = new Module.AutomatedLatestContentBlockToolsView({ model: this.model });
this.showChildView('toolsRegion', this.toolsView);
this.showChildView('postsRegion', new ContainerView({ model: this.model.get('_container'), renderOptions: renderOptions }));
@ -213,12 +214,13 @@ define([
},
transport: function (options, success, failure) {
var taxonomies;
var termsPromise;
var promise = CommunicationComponent.getTaxonomies(
that.model.get('contentType')
).then(function (tax) {
taxonomies = tax;
// Fetch available terms based on the list of taxonomies already fetched
var promise = CommunicationComponent.getTerms({
termsPromise = CommunicationComponent.getTerms({
search: options.data.term,
taxonomies: _.keys(taxonomies)
}).then(function (terms) {
@ -227,7 +229,7 @@ define([
terms: terms
};
});
return promise;
return termsPromise;
});
promise.then(success);
@ -264,9 +266,9 @@ define([
}
}).trigger('change');
},
toggleDisplayOptions: function (event) {
var el = this.$('.mailpoet_automated_latest_content_display_options'),
showControl = this.$('.mailpoet_automated_latest_content_show_display_options');
toggleDisplayOptions: function () {
var el = this.$('.mailpoet_automated_latest_content_display_options');
var showControl = this.$('.mailpoet_automated_latest_content_show_display_options');
if (el.hasClass('mailpoet_closed')) {
el.removeClass('mailpoet_closed');
showControl.addClass('mailpoet_hidden');
@ -275,7 +277,7 @@ define([
showControl.removeClass('mailpoet_hidden');
}
},
showButtonSettings: function (event) {
showButtonSettings: function () {
var buttonModule = ButtonBlock;
(new buttonModule.ButtonBlockSettingsView({
model: this.model.get('readMoreButton'),
@ -286,7 +288,7 @@ define([
}
})).render();
},
showDividerSettings: function (event) {
showDividerSettings: function () {
var dividerModule = DividerBlock;
(new dividerModule.DividerBlockSettingsView({
model: this.model.get('divider'),
@ -349,8 +351,8 @@ define([
this.changeField('titleFormat', event);
},
_updateContentTypes: function (postTypes) {
var select = this.$('.mailpoet_automated_latest_content_content_type'),
selectedValue = this.model.get('contentType');
var select = this.$('.mailpoet_automated_latest_content_content_type');
var selectedValue = this.model.get('contentType');
select.find('option').remove();
_.each(postTypes, function (type) {
@ -378,7 +380,7 @@ define([
}
});
App.on('before:start', function (App, options) {
App.on('before:start', function (App) {
App.registerBlockType('automatedLatestContent', {
blockModel: Module.AutomatedLatestContentBlockModel,
blockView: Module.AutomatedLatestContentBlockView
@ -391,7 +393,7 @@ define([
});
});
App.on('start', function (App, options) {
App.on('start', function (App) {
var Application = App;
Application._ALCSupervisor = new Module.ALCSupervisor();
Application._ALCSupervisor.refresh();

View File

@ -12,17 +12,16 @@ define([
'jquery',
'mailpoet',
'modal'
], function (App, Marionette, SuperModel, _, jQuery, MailPoet, Modal) {
], function (App, Marionette, SuperModel, _, jQuery, MailPoet) {
'use strict';
var Module = {},
AugmentedView = Marionette.View.extend({});
var Module = {};
var AugmentedView = Marionette.View.extend({});
Module.BlockModel = SuperModel.extend({
stale: [], // Attributes to be removed upon saving
initialize: function () {
var that = this;
this.on('change', function () {
App.getChannel().trigger('autoSave');
});
@ -68,7 +67,8 @@ define([
options.dragBehavior.view.model.destroy();
},
onDragSubstituteBy: function (behavior) {
var WidgetView, node;
var WidgetView;
var node;
// When block is being dragged, display the widget icon instead.
// This will create an instance of block's widget view and
// use it's rendered DOM element instead of the content block
@ -98,13 +98,13 @@ define([
this.on('dom:refresh', this.showBlock, this);
this._isFirstRender = true;
},
showTools: function (_event) {
showTools: function () {
if (!this.showingToolsDisabled) {
this.$('> .mailpoet_tools').addClass('mailpoet_display_tools');
this.toolsView.triggerMethod('showTools');
}
},
hideTools: function (e) {
hideTools: function () {
this.$('> .mailpoet_tools').removeClass('mailpoet_display_tools');
this.toolsView.triggerMethod('hideTools');
},
@ -240,8 +240,9 @@ define([
ColorPickerBehavior: {}
},
initialize: function (params) {
var panelParams;
this.model.trigger('startEditing');
var panelParams = {
panelParams = {
element: this.$el,
template: '',
position: 'right',
@ -262,7 +263,7 @@ define([
model: this.model.toJSON()
};
},
close: function (event) {
close: function () {
this.destroy();
},
changeField: function (field, event) {

View File

@ -11,8 +11,8 @@ define([
'use strict';
var Module = {},
base = BaseBlock;
var Module = {};
var base = BaseBlock;
Module.ButtonBlockModel = base.BlockModel.extend({
defaults: function () {
@ -132,7 +132,7 @@ define([
}
});
App.on('before:start', function (App, options) {
App.on('before:start', function (App) {
App.registerBlockType('button', {
blockModel: Module.ButtonBlockModel,
blockView: Module.ButtonBlockView

View File

@ -14,9 +14,9 @@ define([
'use strict';
var Module = {},
base = BaseBlock,
BlockCollection;
var Module = {};
var base = BaseBlock;
var BlockCollection;
BlockCollection = Backbone.Collection.extend({
model: base.BlockModel,
@ -24,7 +24,6 @@ define([
this.on('add change remove', function () { App.getChannel().trigger('autoSave'); });
},
parse: function (response) {
var self = this;
return _.map(response, function (block) {
var Type = App.getBlockTypeModel(block.type);
// TODO: If type has no registered model, use a backup one
@ -66,7 +65,7 @@ define([
return response;
},
getChildren: function () {
var models = this.get('blocks').map(function (model, index, list) {
var models = this.get('blocks').map(function (model) {
return [model, model.getChildren()];
});
@ -121,7 +120,8 @@ define([
options.dragBehavior.view.model.destroy();
},
onDragSubstituteBy: function (behavior) {
var WidgetView, node;
var WidgetView;
var node;
// When block is being dragged, display the widget icon instead.
// This will create an instance of block's widget view and
// use it's rendered DOM element instead of the content block
@ -190,24 +190,24 @@ define([
}
},
toggleEditingLayer: function (event) {
var that = this,
$toggleButton = this.$('> .mailpoet_tools .mailpoet_newsletter_layer_selector'),
$overlay = jQuery('.mailpoet_layer_overlay'),
$container = this.$('> .mailpoet_container'),
enableContainerLayer = function () {
that.$el.addClass('mailpoet_container_layer_active');
$toggleButton.addClass('mailpoet_container_layer_active');
$container.addClass('mailpoet_layer_highlight');
$overlay.click(disableContainerLayer);
$overlay.show();
},
disableContainerLayer = function () {
that.$el.removeClass('mailpoet_container_layer_active');
$toggleButton.removeClass('mailpoet_container_layer_active');
$container.removeClass('mailpoet_layer_highlight');
$overlay.hide();
$overlay.off('click');
};
var that = this;
var $toggleButton = this.$('> .mailpoet_tools .mailpoet_newsletter_layer_selector');
var $overlay = jQuery('.mailpoet_layer_overlay');
var $container = this.$('> .mailpoet_container');
var enableContainerLayer = function () {
that.$el.addClass('mailpoet_container_layer_active');
$toggleButton.addClass('mailpoet_container_layer_active');
$container.addClass('mailpoet_layer_highlight');
$overlay.click(disableContainerLayer);
$overlay.show();
};
var disableContainerLayer = function () {
that.$el.removeClass('mailpoet_container_layer_active');
$toggleButton.removeClass('mailpoet_container_layer_active');
$container.removeClass('mailpoet_layer_highlight');
$overlay.hide();
$overlay.off('click');
};
if ($toggleButton.hasClass('mailpoet_container_layer_active')) {
disableContainerLayer();
} else {
@ -336,7 +336,7 @@ define([
}
});
App.on('before:start', function (App, options) {
App.on('before:start', function (App) {
App.registerBlockType('container', {
blockModel: Module.ContainerBlockModel,
blockView: Module.ContainerBlockView

View File

@ -5,14 +5,13 @@ define([
'newsletter_editor/App',
'newsletter_editor/blocks/base',
'underscore',
'jquery',
'mailpoet'
], function (App, BaseBlock, _, jQuery, MailPoet) {
'jquery'
], function (App, BaseBlock, _, jQuery) {
'use strict';
var Module = {},
base = BaseBlock;
var Module = {};
var base = BaseBlock;
Module.DividerBlockModel = base.BlockModel.extend({
defaults: function () {
@ -49,8 +48,8 @@ define([
}, base.BlockView.prototype.behaviors),
onDragSubstituteBy: function () { return Module.DividerWidgetView; },
initialize: function () {
base.BlockView.prototype.initialize.apply(this, arguments);
var that = this;
base.BlockView.prototype.initialize.apply(this, arguments);
// Listen for attempts to change all dividers in one go
this._replaceDividerHandler = function (data) { that.model.set(data); that.model.trigger('applyToAll'); };
@ -119,7 +118,7 @@ define([
repaintDividerStyleOptions: function () {
this.$('.mailpoet_field_divider_style > div').css('border-top-color', this.model.get('styles.block.borderColor'));
},
applyToAll: function (event) {
applyToAll: function () {
App.getChannel().trigger('replaceAllDividers', this.model.toJSON());
},
updateValueAndCall: function (fieldToUpdate, callable, event) {
@ -139,7 +138,7 @@ define([
}
}
});
App.on('before:start', function (App, options) {
App.on('before:start', function (App) {
App.registerBlockType('divider', {
blockModel: Module.DividerBlockModel,
blockView: Module.DividerBlockView

View File

@ -10,8 +10,8 @@ define([
'use strict';
var Module = {},
base = BaseBlock;
var Module = {};
var base = BaseBlock;
Module.FooterBlockModel = base.BlockModel.extend({
defaults: function () {
@ -110,7 +110,7 @@ define([
}
});
App.on('before:start', function (App, options) {
App.on('before:start', function (App) {
App.registerBlockType('footer', {
blockModel: Module.FooterBlockModel,
blockView: Module.FooterBlockView

View File

@ -10,8 +10,8 @@ define([
'use strict';
var Module = {},
base = BaseBlock;
var Module = {};
var base = BaseBlock;
Module.HeaderBlockModel = base.BlockModel.extend({
defaults: function () {
@ -110,7 +110,7 @@ define([
}
});
App.on('before:start', function (App, options) {
App.on('before:start', function (App) {
App.registerBlockType('header', {
blockModel: Module.HeaderBlockModel,
blockView: Module.HeaderBlockView

View File

@ -11,9 +11,9 @@ define([
'use strict';
var Module = {},
base = BaseBlock,
ImageWidgetView;
var Module = {};
var base = BaseBlock;
var ImageWidgetView;
Module.ImageBlockModel = base.BlockModel.extend({
defaults: function () {
@ -48,8 +48,8 @@ define([
elementSelector: '.mailpoet_image',
resizeHandleSelector: '.mailpoet_image_resize_handle',
onResize: function (event) {
var corner = this.$('.mailpoet_image').offset(),
width = event.pageX - corner.left;
var corner = this.$('.mailpoet_image').offset();
var width = event.pageX - corner.left;
this.view.model.set('width', width + 'px');
}
},
@ -127,13 +127,16 @@ define([
}
},
showMediaManager: function () {
var that = this;
var MediaManager;
var theFrame;
if (this._mediaManager) {
this._mediaManager.resetSelections();
this._mediaManager.open();
return;
}
var MediaManager = window.wp.media.view.MediaFrame.Select.extend({
MediaManager = window.wp.media.view.MediaFrame.Select.extend({
initialize: function () {
window.wp.media.view.MediaFrame.prototype.initialize.apply(this, arguments);
@ -196,6 +199,7 @@ define([
},
bindHandlers: function () {
var handlers;
// from Select
this.on('router:create:browse', this.createRouter, this);
this.on('router:render:browse', this.browseRouter, this);
@ -210,7 +214,7 @@ define([
this.on('updateExcluded', this.browseContent, this);
var handlers = {
handlers = {
content: {
embed: 'embedContent',
'edit-selection': 'editSelectionContent'
@ -244,9 +248,9 @@ define([
},
editSelectionContent: function () {
var state = this.state(),
selection = state.get('selection'),
view;
var state = this.state();
var selection = state.get('selection');
var view;
view = new window.wp.media.view.AttachmentsBrowser({
controller: this,
@ -302,8 +306,8 @@ define([
requires: { selection: true },
click: function () {
var state = controller.state(),
selection = state.get('selection');
var state = controller.state();
var selection = state.get('selection');
controller.close();
state.trigger('insert', selection).reset();
@ -321,46 +325,42 @@ define([
});
var theFrame = new MediaManager({
id: 'mailpoet-media-manager',
frame: 'select',
title: 'Select image',
editing: false,
multiple: false,
library: {
type: 'image'
},
displaySettings: false,
button: {
text: 'Select'
}
}),
that = this;
theFrame = new MediaManager({
id: 'mailpoet-media-manager',
frame: 'select',
title: 'Select image',
editing: false,
multiple: false,
library: {
type: 'image'
},
displaySettings: false,
button: {
text: 'Select'
}
});
this._mediaManager = theFrame;
this._mediaManager.on('insert', function () {
// Append media manager image selections to Images tab
var selection = theFrame.state().get('selection');
selection.each(function (attachment) {
var sizes = attachment.get('sizes'),
// Following advice from Becs, the target width should
// be a double of one column width to render well on
// retina screen devices
targetImageWidth = 1320,
var sizes = attachment.get('sizes');
// Following advice from Becs, the target width should
// be a double of one column width to render well on
// retina screen devices
var targetImageWidth = 1320;
// For main image use the size, that's closest to being 660px in width
sizeKeys = _.keys(sizes),
// Pick the width that is closest to target width
var increasingByWidthDifference = _.sortBy(
_.keys(sizes),
function (size) { return Math.abs(targetImageWidth - sizes[size].width); }
);
var bestWidth = sizes[_.first(increasingByWidthDifference)].width;
var imagesOfBestWidth = _.filter(_.values(sizes), function (size) { return size.width === bestWidth; });
// Pick the width that is closest to target width
increasingByWidthDifference = _.sortBy(
_.keys(sizes),
function (size) { return Math.abs(targetImageWidth - sizes[size].width); }
),
bestWidth = sizes[_.first(increasingByWidthDifference)].width,
imagesOfBestWidth = _.filter(_.values(sizes), function (size) { return size.width === bestWidth; }),
// Maximize the height if there are multiple images with same width
mainSize = _.max(imagesOfBestWidth, function (size) { return size.height; });
// Maximize the height if there are multiple images with same width
var mainSize = _.max(imagesOfBestWidth, function (size) { return size.height; });
that.model.set({
height: mainSize.height + 'px',
@ -413,7 +413,7 @@ define([
});
Module.ImageWidgetView = ImageWidgetView;
App.on('before:start', function (App, options) {
App.on('before:start', function (App) {
App.registerBlockType('image', {
blockModel: Module.ImageBlockModel,
blockView: Module.ImageBlockView

View File

@ -39,8 +39,13 @@ define([
'use strict';
var Module = {},
base = BaseBlock;
var Module = {};
var base = BaseBlock;
var PostsDisplayOptionsSettingsView;
var SinglePostSelectionSettingsView;
var EmptyPostSelectionSettingsView;
var PostSelectionSettingsView;
var PostsSelectionCollectionView;
Module.PostsBlockModel = base.BlockModel.extend({
stale: ['_selectedPosts', '_availablePosts', '_transformedPosts'],
@ -89,10 +94,9 @@ define([
};
},
initialize: function () {
var that = this,
POST_REFRESH_DELAY_MS = 500,
refreshAvailablePosts = _.debounce(this.fetchAvailablePosts.bind(this), POST_REFRESH_DELAY_MS),
refreshTransformedPosts = _.debounce(this._refreshTransformedPosts.bind(this), POST_REFRESH_DELAY_MS);
var POST_REFRESH_DELAY_MS = 500;
var refreshAvailablePosts = _.debounce(this.fetchAvailablePosts.bind(this), POST_REFRESH_DELAY_MS);
var refreshTransformedPosts = _.debounce(this._refreshTransformedPosts.bind(this), POST_REFRESH_DELAY_MS);
// Attach Radio.Requests API primarily for highlighting
_.extend(this, Radio.Requests);
@ -120,9 +124,9 @@ define([
});
},
_loadMorePosts: function () {
var that = this,
postCount = this.get('_availablePosts').length,
nextOffset = this.get('offset') + Number(this.get('amount'));
var that = this;
var postCount = this.get('_availablePosts').length;
var nextOffset = this.get('offset') + Number(this.get('amount'));
if (postCount === 0 || postCount < nextOffset) {
// No more posts to load
@ -141,8 +145,8 @@ define([
});
},
_refreshTransformedPosts: function () {
var that = this,
data = this.toJSON();
var that = this;
var data = this.toJSON();
data.posts = this.get('_selectedPosts').pluck('ID');
@ -158,10 +162,9 @@ define([
});
},
_insertSelectedPosts: function () {
var that = this,
data = this.toJSON(),
index = this.collection.indexOf(this),
collection = this.collection;
var data = this.toJSON();
var index = this.collection.indexOf(this);
var collection = this.collection;
data.posts = this.get('_selectedPosts').pluck('ID');
@ -190,17 +193,19 @@ define([
this.model.reply('blockView', this.notifyAboutSelf, this);
},
onRender: function () {
var ContainerView;
var renderOptions;
if (!this.getRegion('toolsRegion').hasView()) {
this.showChildView('toolsRegion', this.toolsView);
}
this.trigger('showSettings');
var ContainerView = App.getBlockTypeView('container'),
renderOptions = {
disableTextEditor: true,
disableDragAndDrop: true,
emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay')
};
ContainerView = App.getBlockTypeView('container');
renderOptions = {
disableTextEditor: true,
disableDragAndDrop: true,
emptyContainerMessage: MailPoet.I18n.t('noPostsToDisplay')
};
this.showChildView('postsRegion', new ContainerView({ model: this.model.get('_transformedPosts'), renderOptions: renderOptions }));
},
notifyAboutSelf: function () {
@ -237,8 +242,8 @@ define([
this.displayOptionsView = new PostsDisplayOptionsSettingsView({ model: this.model });
},
onRender: function () {
var that = this,
blockView = this.model.request('blockView');
var that = this;
this.model.request('blockView');
this.showChildView('selectionRegion', this.selectionView);
this.showChildView('displayOptionsRegion', this.displayOptionsView);
@ -283,7 +288,7 @@ define([
}
});
var PostsSelectionCollectionView = Marionette.CollectionView.extend({
PostsSelectionCollectionView = Marionette.CollectionView.extend({
className: 'mailpoet_post_scroll_container',
childView: function () { return SinglePostSelectionSettingsView; },
emptyView: function () { return EmptyPostSelectionSettingsView; },
@ -307,7 +312,7 @@ define([
}
});
var PostSelectionSettingsView = Marionette.View.extend({
PostSelectionSettingsView = Marionette.View.extend({
getTemplate: function () { return window.templates.postSelectionPostsBlockSettings; },
regions: {
posts: '.mailpoet_post_selection_container'
@ -334,9 +339,10 @@ define([
}
},
onRender: function () {
var postsView;
// Dynamically update available post types
CommunicationComponent.getPostTypes().done(_.bind(this._updateContentTypes, this));
var postsView = new PostsSelectionCollectionView({
postsView = new PostsSelectionCollectionView({
collection: this.model.get('_availablePosts'),
blockModel: this.model
});
@ -358,12 +364,13 @@ define([
},
transport: function (options, success, failure) {
var taxonomies;
var termsPromise;
var promise = CommunicationComponent.getTaxonomies(
that.model.get('contentType')
).then(function (tax) {
taxonomies = tax;
// Fetch available terms based on the list of taxonomies already fetched
var promise = CommunicationComponent.getTerms({
termsPromise = CommunicationComponent.getTerms({
search: options.data.term,
taxonomies: _.keys(taxonomies)
}).then(function (terms) {
@ -372,7 +379,7 @@ define([
terms: terms
};
});
return promise;
return termsPromise;
});
promise.then(success);
@ -413,8 +420,8 @@ define([
this.model.set(field, jQuery(event.target).val());
},
_updateContentTypes: function (postTypes) {
var select = this.$('.mailpoet_settings_posts_content_type'),
selectedValue = this.model.get('contentType');
var select = this.$('.mailpoet_settings_posts_content_type');
var selectedValue = this.model.get('contentType');
select.find('option').remove();
_.each(postTypes, function (type) {
@ -427,11 +434,11 @@ define([
}
});
var EmptyPostSelectionSettingsView = Marionette.View.extend({
EmptyPostSelectionSettingsView = Marionette.View.extend({
getTemplate: function () { return window.templates.emptyPostPostsBlockSettings; }
});
var SinglePostSelectionSettingsView = Marionette.View.extend({
SinglePostSelectionSettingsView = Marionette.View.extend({
getTemplate: function () { return window.templates.singlePostPostsBlockSettings; },
events: function () {
return {
@ -448,8 +455,8 @@ define([
this.blockModel = options.blockModel;
},
postSelectionChange: function (event) {
var checkBox = jQuery(event.target),
selectedPostsCollection = this.blockModel.get('_selectedPosts');
var checkBox = jQuery(event.target);
var selectedPostsCollection = this.blockModel.get('_selectedPosts');
if (checkBox.prop('checked')) {
selectedPostsCollection.add(this.model);
} else {
@ -458,7 +465,7 @@ define([
}
});
var PostsDisplayOptionsSettingsView = base.BlockSettingsView.extend({
PostsDisplayOptionsSettingsView = base.BlockSettingsView.extend({
getTemplate: function () { return window.templates.displayOptionsPostsBlockSettings; },
events: function () {
return {
@ -488,7 +495,7 @@ define([
model: this.model.toJSON()
};
},
showButtonSettings: function (event) {
showButtonSettings: function () {
var buttonModule = ButtonBlock;
(new buttonModule.ButtonBlockSettingsView({
model: this.model.get('readMoreButton'),
@ -499,7 +506,7 @@ define([
}
})).render();
},
showDividerSettings: function (event) {
showDividerSettings: function () {
var dividerModule = DividerBlock;
(new dividerModule.DividerBlockSettingsView({
model: this.model.get('divider'),
@ -575,7 +582,7 @@ define([
}
});
App.on('before:start', function (App, options) {
App.on('before:start', function (App) {
App.registerBlockType('posts', {
blockModel: Module.PostsBlockModel,
blockView: Module.PostsBlockView

View File

@ -13,12 +13,13 @@ define([
'use strict';
var Module = {},
base = BaseBlock,
SocialBlockSettingsIconSelectorView,
SocialBlockSettingsIconView,
SocialBlockSettingsIconCollectionView,
SocialBlockSettingsStylesView;
var Module = {};
var base = BaseBlock;
var SocialBlockSettingsIconSelectorView;
var SocialBlockSettingsIconView;
var SocialBlockSettingsIconCollectionView;
var SocialBlockSettingsStylesView;
var SocialIconView;
Module.SocialIconModel = SuperModel.extend({
defaults: function () {
@ -33,12 +34,12 @@ define([
text: defaultValues.get('title')
};
},
initialize: function (options) {
initialize: function () {
var that = this;
// Make model swap to default values for that type when iconType changes
this.on('change:iconType', function () {
var defaultValues = App.getConfig().get('socialIcons').get(that.get('iconType')),
iconSet = that.collection.iconBlockModel.getIconSet();
var defaultValues = App.getConfig().get('socialIcons').get(that.get('iconType'));
var iconSet = that.collection.iconBlockModel.getIconSet();
this.set({
link: defaultValues.get('defaultLink'),
image: iconSet.get(that.get('iconType')),
@ -83,7 +84,7 @@ define([
}
});
var SocialIconView = Marionette.View.extend({
SocialIconView = Marionette.View.extend({
tagName: 'span',
getTemplate: function () { return window.templates.socialIconBlock; },
modelEvents: {
@ -175,10 +176,10 @@ define([
}
},
templateContext: function () {
var icons = App.getConfig().get('socialIcons'),
// Construct icon type list of format [{iconType: 'type', title: 'Title'}, ...]
availableIconTypes = _.map(_.keys(icons.attributes), function (key) { return { iconType: key, title: icons.get(key).get('title') }; }),
allIconSets = App.getAvailableStyles().get('socialIconSets');
var icons = App.getConfig().get('socialIcons');
// Construct icon type list of format [{iconType: 'type', title: 'Title'}, ...]
var availableIconTypes = _.map(_.keys(icons.attributes), function (key) { return { iconType: key, title: icons.get(key).get('title') }; });
var allIconSets = App.getAvailableStyles().get('socialIconSets');
return _.extend({}, base.BlockView.prototype.templateContext.apply(this, arguments), {
iconTypes: availableIconTypes,
currentType: icons.get(this.model.get('iconType')).toJSON(),
@ -297,7 +298,7 @@ define([
}
});
App.on('before:start', function (App, options) {
App.on('before:start', function (App) {
App.registerBlockType('social', {
blockModel: Module.SocialBlockModel,
blockView: Module.SocialBlockView

View File

@ -9,8 +9,8 @@ define([
'use strict';
var Module = {},
base = BaseBlock;
var Module = {};
var base = BaseBlock;
Module.SpacerBlockModel = base.BlockModel.extend({
defaults: function () {
@ -87,7 +87,7 @@ define([
}
});
App.on('before:start', function (App, options) {
App.on('before:start', function (App) {
App.registerBlockType('spacer', {
blockModel: Module.SpacerBlockModel,
blockView: Module.SpacerBlockView

View File

@ -10,8 +10,8 @@ define([
'use strict';
var Module = {},
base = BaseBlock;
var Module = {};
var base = BaseBlock;
Module.TextBlockModel = base.BlockModel.extend({
defaults: function () {
@ -94,7 +94,7 @@ define([
}
});
App.on('before:start', function (App, options) {
App.on('before:start', function (App) {
App.registerBlockType('text', {
blockModel: Module.TextBlockModel,
blockView: Module.TextBlockView

View File

@ -7,15 +7,15 @@
*/
(function (root, factory) {
var Marionette = require('backbone.marionette');
var Radio = require('backbone.radio');
var _ = require('underscore');
if (typeof define === 'function' && define.amd) {
define(['backbone.marionette', 'backbone.radio', 'underscore'], function (Marionette, Radio, _) {
return factory(Marionette, Radio, _);
});
}
else if (typeof exports !== 'undefined') {
var Marionette = require('backbone.marionette');
var Radio = require('backbone.radio');
var _ = require('underscore');
module.exports = factory(Marionette, Radio, _);
}
else {

View File

@ -98,7 +98,7 @@ define([
});
};
App.on('start', function (App, options) {
App.on('start', function () {
// Prefetch post types
Module.getPostTypes();
});

View File

@ -13,7 +13,7 @@ define([
// handled by other components.
Module.NewsletterModel = SuperModel.extend({
whitelisted: ['id', 'subject', 'preheader'],
initialize: function (options) {
initialize: function () {
this.on('change', function () {
App.getChannel().trigger('autoSave');
});

View File

@ -29,7 +29,7 @@ define([
}
});
App.on('start', function (App, options) {
App.on('start', function (App) {
App._appView.showChildView('headingRegion', new Module.HeadingView({ model: App.getNewsletter() }));
MailPoet.helpTooltip.show(document.getElementById('tooltip-designer-subject-line'), {
tooltipId: 'tooltip-designer-subject-line-ti',

View File

@ -28,8 +28,8 @@ define([
'use strict';
var Module = {},
saveTimeout;
var Module = {};
var saveTimeout;
// Save editor contents to server
Module.save = function () {
@ -76,10 +76,9 @@ define([
// Temporary workaround for html2canvas-alpha2.
// Removes 1px left transparent border from resulting canvas.
var oldContext = oldCanvas.getContext('2d'),
newCanvas = document.createElement('canvas'),
newContext = newCanvas.getContext('2d'),
leftBorderWidth = 1;
var newCanvas = document.createElement('canvas');
var newContext = newCanvas.getContext('2d');
var leftBorderWidth = 1;
newCanvas.width = oldCanvas.width;
newCanvas.height = oldCanvas.height;
@ -95,8 +94,7 @@ define([
};
Module.saveTemplate = function (options) {
var that = this,
promise = jQuery.Deferred();
var promise = jQuery.Deferred();
promise.then(function (thumbnail) {
var data = _.extend(options || {}, {
@ -122,7 +120,6 @@ define([
};
Module.exportTemplate = function (options) {
var that = this;
return Module.getThumbnail(
jQuery('#mailpoet_editor_content > .mailpoet_block').get(0)
).then(function (thumbnail) {
@ -155,7 +152,7 @@ define([
'click .mailpoet_save_export': 'toggleExportTemplate',
'click .mailpoet_export_template': 'exportTemplate'
},
initialize: function (options) {
initialize: function () {
App.getChannel().on('beforeEditorSave', this.beforeSave, this);
App.getChannel().on('afterEditorSave', this.afterSave, this);
},
@ -170,7 +167,7 @@ define([
// TODO: Add a loading animation instead
this.$('.mailpoet_autosaved_at').text(MailPoet.I18n.t('saving'));
},
afterSave: function (json, response) {
afterSave: function (json) {
this.validateNewsletter(json);
// Update 'Last saved timer'
this.$('.mailpoet_editor_last_saved').removeClass('mailpoet_hidden');
@ -192,9 +189,9 @@ define([
this.$('.mailpoet_save_as_template_container').addClass('mailpoet_hidden');
},
saveAsTemplate: function () {
var templateName = this.$('.mailpoet_save_as_template_name').val(),
templateDescription = this.$('.mailpoet_save_as_template_description').val(),
that = this;
var templateName = this.$('.mailpoet_save_as_template_name').val();
var templateDescription = this.$('.mailpoet_save_as_template_description').val();
var that = this;
if (templateName === '') {
MailPoet.Notice.error(
@ -248,9 +245,9 @@ define([
this.$('.mailpoet_export_template_container').addClass('mailpoet_hidden');
},
exportTemplate: function () {
var templateName = this.$('.mailpoet_export_template_name').val(),
templateDescription = this.$('.mailpoet_export_template_description').val(),
that = this;
var templateName = this.$('.mailpoet_export_template_name').val();
var templateDescription = this.$('.mailpoet_export_template_description').val();
var that = this;
if (templateName === '') {
MailPoet.Notice.error(
@ -285,18 +282,19 @@ define([
this.hideOptionContents();
if (!this.$('.mailpoet_save_next').hasClass('button-disabled')) {
Module._cancelAutosave();
Module.save().done(function (response) {
Module.save().done(function () {
window.location.href = App.getConfig().get('urls.send');
});
}
},
validateNewsletter: function (jsonObject) {
var contents;
if (!App._contentContainer.isValid()) {
this.showValidationError(App._contentContainer.validationError);
return;
}
var contents = JSON.stringify(jsonObject);
contents = JSON.stringify(jsonObject);
if (App.getConfig().get('validation.validateUnsubscribeLinkPresent') &&
contents.indexOf('[link:subscription_unsubscribe_url]') < 0 &&
contents.indexOf('[link:subscription_unsubscribe]') < 0) {
@ -340,9 +338,11 @@ define([
};
Module.beforeExitWithUnsavedChanges = function (e) {
var message;
var event;
if (saveTimeout) {
var message = MailPoet.I18n.t('unsavedChangesWillBeLost');
var event = e || window.event;
message = MailPoet.I18n.t('unsavedChangesWillBeLost');
event = e || window.event;
if (event) {
event.returnValue = message;
@ -352,7 +352,7 @@ define([
}
};
App.on('before:start', function (App, options) {
App.on('before:start', function (App) {
var Application = App;
Application.save = Module.save;
Application.getChannel().on('autoSave', Module.autoSave);
@ -362,7 +362,7 @@ define([
Application.getChannel().reply('save', Application.save);
});
App.on('start', function (App, options) {
App.on('start', function (App) {
var saveView = new Module.SaveView();
App._appView.showChildView('bottomRegion', saveView);
});

View File

@ -6,8 +6,7 @@ define([
'backbone.marionette',
'backbone.supermodel',
'underscore',
'jquery',
'sticky-kit'
'jquery'
], function (
App,
CommunicationComponent,
@ -16,14 +15,13 @@ define([
Marionette,
SuperModel,
_,
jQuery,
StickyKit
jQuery
) {
'use strict';
var Module = {};
var SidebarView;
// Widget handlers for use to create new content blocks via drag&drop
Module._contentWidgets = new (Backbone.Collection.extend({
model: SuperModel.extend({
@ -52,7 +50,7 @@ define([
Module.registerLayoutWidget = function (widget) { return Module._layoutWidgets.add(widget); };
Module.getLayoutWidgets = function () { return Module._layoutWidgets; };
var SidebarView = Marionette.View.extend({
SidebarView = Marionette.View.extend({
getTemplate: function () { return window.templates.sidebar; },
regions: {
contentRegion: '.mailpoet_content_region',
@ -62,8 +60,8 @@ define([
},
events: {
'click .mailpoet_sidebar_region h3, .mailpoet_sidebar_region .handlediv': function (event) {
var $openRegion = this.$el.find('.mailpoet_sidebar_region:not(.closed)'),
$targetRegion = this.$el.find(event.target).closest('.mailpoet_sidebar_region');
var $openRegion = this.$el.find('.mailpoet_sidebar_region:not(.closed)');
var $targetRegion = this.$el.find(event.target).closest('.mailpoet_sidebar_region');
$openRegion.find('.mailpoet_region_content').velocity(
'slideUp',
@ -90,7 +88,7 @@ define([
}
}
},
initialize: function (options) {
initialize: function () {
jQuery(window)
.on('resize', this.updateHorizontalScroll.bind(this))
.on('scroll', this.updateHorizontalScroll.bind(this));
@ -113,9 +111,8 @@ define([
// position of the sidebar would be scrollable and not fixed
// partially out of visible screen
this.$el.parent().each(function () {
var calculated_left, self;
self = jQuery(this);
var calculated_left;
var self = jQuery(this);
if (self.css('position') === 'fixed') {
calculated_left = self.parent().offset().left - jQuery(window).scrollLeft();
@ -271,7 +268,7 @@ define([
previewUrl: response.meta.preview_url
});
var view = this.previewView.render();
this.previewView.render();
this.previewView.$el.css('height', '100%');
MailPoet.Modal.popup({
@ -324,7 +321,7 @@ define([
App.getChannel().request('save').always(function () {
CommunicationComponent.previewNewsletter(data).always(function () {
MailPoet.Modal.loading(false);
}).done(function (response) {
}).done(function () {
MailPoet.Notice.success(
MailPoet.I18n.t('newsletterPreviewSent'),
{ scroll: true }
@ -363,7 +360,7 @@ define([
}
});
App.on('before:start', function (App, options) {
App.on('before:start', function (App) {
var Application = App;
Application.registerWidget = Module.registerWidget;
Application.getWidgets = Module.getWidgets;
@ -371,9 +368,8 @@ define([
Application.getLayoutWidgets = Module.getLayoutWidgets;
});
App.on('start', function (App, options) {
var stylesModel = App.getGlobalStyles(),
sidebarView = new SidebarView();
App.on('start', function (App) {
var sidebarView = new SidebarView();
App._appView.showChildView('sidebarRegion', sidebarView);

View File

@ -71,17 +71,19 @@ define([
App.on('before:start', function (App, options) {
var Application = App;
var body;
var globalStyles;
// Expose style methods to global application
Application.getGlobalStyles = Module.getGlobalStyles;
Application.setGlobalStyles = Module.setGlobalStyles;
Application.getAvailableStyles = Module.getAvailableStyles;
var body = options.newsletter.body;
var globalStyles = (_.has(body, 'globalStyles')) ? body.globalStyles : {};
body = options.newsletter.body;
globalStyles = (_.has(body, 'globalStyles')) ? body.globalStyles : {};
this.setGlobalStyles(globalStyles);
});
App.on('start', function (App, options) {
App.on('start', function (App) {
var stylesView = new Module.StylesView({ model: App.getGlobalStyles() });
App._appView.showChildView('stylesRegion', stylesView);
});

View File

@ -10,31 +10,33 @@
/* jshint unused:false */
/* global tinymce:true */
tinymce.PluginManager.add('mailpoet_shortcodes', function (editor, url) {
tinymce.PluginManager.add('mailpoet_shortcodes', function (editor) {
var appendLabelAndClose = function (shortcode) {
editor.insertContent(shortcode);
editor.windowManager.close();
},
generateOnClickFunc = function (shortcode) {
return function () {
appendLabelAndClose(shortcode);
};
editor.insertContent(shortcode);
editor.windowManager.close();
};
var generateOnClickFunc = function (shortcode) {
return function () {
appendLabelAndClose(shortcode);
};
};
editor.addButton('mailpoet_shortcodes', {
icon: 'mailpoet_shortcodes',
onclick: function () {
var shortcodes = [],
configShortcodes = editor.settings.mailpoet_shortcodes;
var shortcodes = [];
var configShortcodes = editor.settings.mailpoet_shortcodes;
var segment;
var i;
for (var segment in configShortcodes) {
for (segment in configShortcodes) {
if (configShortcodes.hasOwnProperty(segment)) {
shortcodes.push({
type: 'label',
text: segment
});
for (var i = 0; i < configShortcodes[segment].length; i += 1) {
for (i = 0; i < configShortcodes[segment].length; i += 1) {
shortcodes.push({
type: 'button',
text: configShortcodes[segment][i].text,

View File

@ -49,7 +49,7 @@ const stats = {
};
class StatsBadge extends React.Component {
getBadgeType(stat, rate) {
static getBadgeType(stat, rate) {
const len = stat.badgeRanges.length;
for (let i = 0; i < len; i += 1) {
if (rate > stat.badgeRanges[i]) {
@ -70,7 +70,7 @@ class StatsBadge extends React.Component {
return null;
}
const badgeType = this.getBadgeType(stat, rate);
const badgeType = StatsBadge.getBadgeType(stat, rate);
const badge = badges[badgeType] || null;
if (!badge) {
return null;

View File

@ -59,90 +59,97 @@ const _QueueMixin = {
return (
<span>{MailPoet.I18n.t('paused')}</span>
);
} else {
if (newsletter.queue.status === 'scheduled') {
return (
<span>
{ MailPoet.I18n.t('scheduledFor') } { MailPoet.Date.format(newsletter.queue.scheduled_at) }
</span>
);
}
const progressClasses = classNames(
}
if (newsletter.queue.status === 'scheduled') {
return (
<span>
{ MailPoet.I18n.t('scheduledFor') } { MailPoet.Date.format(newsletter.queue.scheduled_at) }
</span>
);
}
const progressClasses = classNames(
'mailpoet_progress',
{ mailpoet_progress_complete: newsletter.queue.status === 'completed' }
);
// calculate percentage done
let percentage = Math.round(
let percentage = Math.round(
(newsletter.queue.count_processed * 100) / (newsletter.queue.count_total)
);
let label;
let label;
if (newsletter.queue.status === 'completed') {
label = (
<span>
{
if (newsletter.queue.status === 'completed') {
label = (
<span>
{
MailPoet.I18n.t('newsletterQueueCompleted')
.replace('%$1d', newsletter.queue.count_processed)
.replace('%$2d', newsletter.queue.count_total)
.replace('%$1d', parseInt(newsletter.queue.count_processed, 10).toLocaleString())
.replace('%$2d', parseInt(newsletter.queue.count_total, 10).toLocaleString())
}
</span>
</span>
);
} else {
label = (
<span>
{ newsletter.queue.count_processed } / { newsletter.queue.count_total }
} else {
label = (
<span>
{ newsletter.queue.count_processed } / { newsletter.queue.count_total }
&nbsp;&nbsp;
<a
id={'resume_' + newsletter.id}
className="button"
style={{ display: (newsletter.queue.status === 'paused')
<a
id={'resume_' + newsletter.id}
className="button"
style={{ display: (newsletter.queue.status === 'paused')
? 'inline-block' : 'none' }}
href="javascript:;"
onClick={this.resumeSending.bind(null, newsletter)}
href="javascript:;"
onClick={this.resumeSending.bind(null, newsletter)}
>{MailPoet.I18n.t('resume')}</a>
<a
id={'pause_' + newsletter.id}
className="button mailpoet_pause"
style={{ display: (newsletter.queue.status === null)
<a
id={'pause_' + newsletter.id}
className="button mailpoet_pause"
style={{ display: (newsletter.queue.status === null)
? 'inline-block' : 'none' }}
href="javascript:;"
onClick={this.pauseSending.bind(null, newsletter)}
href="javascript:;"
onClick={this.pauseSending.bind(null, newsletter)}
>{MailPoet.I18n.t('pause')}</a>
</span>
</span>
);
}
let progress_bar_width = 0;
if (isNaN(percentage)) {
percentage = MailPoet.I18n.t('noSubscribers');
} else {
progress_bar_width = percentage;
percentage += '%';
}
return (
<div>
<div className={progressClasses}>
<span
className="mailpoet_progress_bar"
style={{ width: progress_bar_width + '%' }}
></span>
<span className="mailpoet_progress_label">
{ percentage }
</span>
</div>
<p style={{ textAlign: 'center' }}>
{ label }
</p>
</div>
);
}
let progress_bar_width = 0;
if (isNaN(percentage)) {
percentage = MailPoet.I18n.t('noSubscribers');
} else {
progress_bar_width = percentage;
percentage += '%';
}
return (
<div>
<div className={progressClasses}>
<span
className="mailpoet_progress_bar"
style={{ width: progress_bar_width + '%' }}
></span>
<span className="mailpoet_progress_label">
{ percentage }
</span>
</div>
<p style={{ textAlign: 'center' }}>
{ label }
</p>
</div>
);
},
};
const trackStatsCTAClicked = function () {
MailPoet.trackEvent(
'User has clicked a CTA to view detailed stats',
{ 'MailPoet Free version': window.mailpoet_version }
);
};
const _StatisticsMixin = {
renderStatistics: function (newsletter, is_sent, current_time) {
let sent = is_sent;
@ -159,6 +166,7 @@ const _StatisticsMixin = {
}
let params = {};
Hooks.addFilter('mailpoet_newsletters_listing_stats_before', this.addStatsCTALink);
params = Hooks.applyFilters('mailpoet_newsletters_listing_stats_before', params, newsletter);
// welcome emails provide explicit total_sent value
@ -179,11 +187,11 @@ const _StatisticsMixin = {
const percentage_opened_display = MailPoet.Num.toLocaleFixed(percentage_opened, 1);
const percentage_unsubscribed_display = MailPoet.Num.toLocaleFixed(percentage_unsubscribed, 1);
let show_stats_timeout,
newsletter_date,
sent_hours_ago,
too_early_for_stats,
show_kb_link;
let show_stats_timeout;
let newsletter_date;
let sent_hours_ago;
let too_early_for_stats;
let show_kb_link;
if (current_time !== undefined) {
// standard emails and post notifications:
// display green box for newsletters that were just sent
@ -282,18 +290,33 @@ const _StatisticsMixin = {
if (total_sent > 0 && params.link) {
// wrap content in a link
if (params.externalLink) {
return (
<div>
<a
key={`stats-${newsletter.id}`}
href={params.link}
onClick={params.onClick || null}
>
{content}
</a>
{after_content}
</div>
);
}
return (
<div>
<Link
key={`stats-${newsletter.id}`}
to={params.link}
onClick={params.onClick || null}
>
>
{content}
</Link>
{after_content}
</div>
);
}
return (
@ -303,6 +326,37 @@ const _StatisticsMixin = {
</div>
);
},
addStatsCTAAction: function (actions) {
if (window.mailpoet_premium_active) {
return actions;
}
actions.unshift({
name: 'stats',
link: function () {
return (
<a href={'admin.php?page=mailpoet-premium'} onClick={trackStatsCTAClicked}>
{MailPoet.I18n.t('statsListingActionTitle')}
</a>
);
},
display: function (newsletter) {
// welcome emails provide explicit total_sent value
const count_processed = newsletter.queue && newsletter.queue.count_processed;
return ~~(newsletter.total_sent || count_processed) > 0;
},
});
return actions;
},
addStatsCTALink: function (params) {
if (window.mailpoet_premium_active) {
return params;
}
const newParams = params;
newParams.link = 'admin.php?page=mailpoet-premium';
newParams.externalLink = true;
newParams.onClick = trackStatsCTAClicked;
return newParams;
},
};
const _MailerMixin = {

View File

@ -195,12 +195,14 @@ const NewsletterListNotification = React.createClass({
},
renderSettings: function (newsletter) {
let sendingFrequency;
let sendingToSegments;
// get list of segments' name
const segments = newsletter.segments.map((segment) => {
return segment.name;
});
const sendingToSegments = MailPoet.I18n.t('ifNewContentToSegments').replace(
'%$1s', segments.join(', ')
);
// check if the user has specified segments to send to
if (segments.length === 0) {
@ -209,51 +211,48 @@ const NewsletterListNotification = React.createClass({
{ MailPoet.I18n.t('sendingToSegmentsNotSpecified') }
</span>
);
} else {
sendingToSegments = MailPoet.I18n.t('ifNewContentToSegments').replace(
'%$1s', segments.join(', ')
);
}
// set sending frequency
switch (newsletter.options.intervalType) {
case 'daily':
sendingFrequency = MailPoet.I18n.t('sendDaily').replace(
// set sending frequency
switch (newsletter.options.intervalType) {
case 'daily':
sendingFrequency = MailPoet.I18n.t('sendDaily').replace(
'%$1s', timeOfDayValues[newsletter.options.timeOfDay]
);
break;
break;
case 'weekly':
sendingFrequency = MailPoet.I18n.t('sendWeekly').replace(
case 'weekly':
sendingFrequency = MailPoet.I18n.t('sendWeekly').replace(
'%$1s', weekDayValues[newsletter.options.weekDay]
).replace(
'%$2s', timeOfDayValues[newsletter.options.timeOfDay]
);
break;
break;
case 'monthly':
sendingFrequency = MailPoet.I18n.t('sendMonthly').replace(
case 'monthly':
sendingFrequency = MailPoet.I18n.t('sendMonthly').replace(
'%$1s', monthDayValues[newsletter.options.monthDay]
).replace(
'%$2s', timeOfDayValues[newsletter.options.timeOfDay]
);
break;
break;
case 'nthWeekDay':
sendingFrequency = MailPoet.I18n.t('sendNthWeekDay').replace(
case 'nthWeekDay':
sendingFrequency = MailPoet.I18n.t('sendNthWeekDay').replace(
'%$1s', nthWeekDayValues[newsletter.options.nthWeekDay]
).replace(
'%$2s', weekDayValues[newsletter.options.weekDay]
).replace(
'%$3s', timeOfDayValues[newsletter.options.timeOfDay]
);
break;
break;
case 'immediately':
sendingFrequency = MailPoet.I18n.t('sendImmediately');
break;
}
case 'immediately':
sendingFrequency = MailPoet.I18n.t('sendImmediately');
break;
}
return (
<span>
{ sendingFrequency } { sendingToSegments }
@ -266,13 +265,13 @@ const NewsletterListNotification = React.createClass({
return (
MailPoet.I18n.t('notSentYet')
);
} else {
return (
<Link
to={`/notification/history/${newsletter.id}`}
>{ MailPoet.I18n.t('viewHistory') }</Link>
);
}
return (
<Link
to={`/notification/history/${newsletter.id}`}
>{ MailPoet.I18n.t('viewHistory') }</Link>
);
},
renderItem: function (newsletter, actions) {
const rowClasses = classNames(

View File

@ -52,6 +52,7 @@ let newsletter_actions = [
},
];
Hooks.addFilter('mailpoet_newsletters_listings_notification_history_actions', StatisticsMixin.addStatsCTAAction);
newsletter_actions = Hooks.applyFilters('mailpoet_newsletters_listings_notification_history_actions', newsletter_actions);
const NewsletterListNotificationHistory = React.createClass({

View File

@ -157,6 +157,7 @@ let newsletter_actions = [
},
];
Hooks.addFilter('mailpoet_newsletters_listings_standard_actions', StatisticsMixin.addStatsCTAAction);
newsletter_actions = Hooks.applyFilters('mailpoet_newsletters_listings_standard_actions', newsletter_actions);
const NewsletterListStandard = React.createClass({

View File

@ -124,6 +124,7 @@ let newsletter_actions = [
},
];
Hooks.addFilter('mailpoet_newsletters_listings_welcome_notification_actions', StatisticsMixin.addStatsCTAAction);
newsletter_actions = Hooks.applyFilters('mailpoet_newsletters_listings_welcome_notification_actions', newsletter_actions);
const NewsletterListWelcome = React.createClass({
@ -179,6 +180,7 @@ const NewsletterListWelcome = React.createClass({
renderSettings: function (newsletter) {
let sendingEvent;
let sendingDelay;
let segment;
// set sending event
switch (newsletter.options.event) {
@ -195,7 +197,7 @@ const NewsletterListWelcome = React.createClass({
case 'segment':
// get segment
const segment = _.find(mailpoet_segments, (segment) => {
segment = _.find(mailpoet_segments, (segment) => {
return (~~(segment.id) === ~~(newsletter.options.segment));
});
@ -205,11 +207,11 @@ const NewsletterListWelcome = React.createClass({
{ MailPoet.I18n.t('sendingToSegmentsNotSpecified') }
</span>
);
} else {
sendingEvent = MailPoet.I18n.t('welcomeEventSegment').replace(
}
sendingEvent = MailPoet.I18n.t('welcomeEventSegment').replace(
'%$1s', segment.name
);
}
break;
}

View File

@ -37,7 +37,8 @@ if (container) {
{/* Listings */}
<Route path="standard(/)**" params={{ tab: 'standard' }} component={NewsletterListStandard} />
<Route path="welcome(/)**" component={NewsletterListWelcome} />
<Route path="notification/history/:parent_id(/)**" component={NewsletterListNotificationHistory} />
<Route path="notification/history/:parent_id(/)**"
component={NewsletterListNotificationHistory} />
<Route path="notification(/)**" component={NewsletterListNotification} />
{/* Newsletter: type selection */}
<Route path="new" component={NewsletterTypes} />

View File

@ -40,7 +40,7 @@ define(
endpoint: 'segments',
multiple: true,
filter: function (segment) {
return !!(!segment.deleted_at);
return !segment.deleted_at;
},
getLabel: function (segment) {
return segment.name + ' (' + parseInt(segment.subscribers, 10).toLocaleString() + ')';

View File

@ -17,7 +17,7 @@ define(
const jQuery = jq;
const currentTime = window.mailpoet_current_time || '00:00';
const defaultDateTime = window.mailpoet_current_date + ' ' + '00:00:00';
const defaultDateTime = window.mailpoet_current_date + ' 00:00:00';
const timeOfDayItems = window.mailpoet_schedule_time_of_day;
const dateDisplayFormat = window.mailpoet_date_display_format;
const dateStorageFormat = window.mailpoet_date_storage_format;
@ -348,7 +348,7 @@ define(
endpoint: 'segments',
multiple: true,
filter: function (segment) {
return !!(!segment.deleted_at);
return !segment.deleted_at;
},
getLabel: function (segment) {
return segment.name + ' (' + parseInt(segment.subscribers, 10).toLocaleString() + ')';

View File

@ -47,6 +47,8 @@ define('notice', ['mailpoet', 'jquery'], function (mp, jQuery) {
return this;
},
createNotice: function () {
var onClose;
var positionAfter;
// clone element
this.element = jQuery('#mailpoet_notice_' + this.options.type).clone();
@ -62,7 +64,6 @@ define('notice', ['mailpoet', 'jquery'], function (mp, jQuery) {
this.element.removeAttr('id');
// insert notice after its parent
var positionAfter;
if (typeof this.options.positionAfter === 'object') {
positionAfter = this.options.positionAfter;
} else if (typeof this.options.positionAfter === 'string') {
@ -73,7 +74,7 @@ define('notice', ['mailpoet', 'jquery'], function (mp, jQuery) {
positionAfter.after(this.element);
// setup onClose callback
var onClose = null;
onClose = null;
if (this.options.onClose !== null) {
onClose = this.options.onClose;
}
@ -177,12 +178,13 @@ define('notice', ['mailpoet', 'jquery'], function (mp, jQuery) {
}
},
hide: function (all) {
var id;
if (all !== undefined && all === true) {
// all notices
jQuery('.mailpoet_notice:not([id])').trigger('close');
} else if (all !== undefined && jQuery.isArray(all)) {
// array of ids
for (var id in all) {
for (id in all) {
jQuery('[data-id="' + all[id] + '"]').trigger('close');
}
} if (all !== undefined) {

View File

@ -5,8 +5,7 @@ define([
],
function (
MailPoet,
jQuery,
Parsley
jQuery
) {
jQuery(function ($) {
function isSameDomain(url) {
@ -20,7 +19,7 @@ function (
$('form.mailpoet_form').each(function () {
var form = $(this);
form.parsley().on('form:validated', function (parsley) {
form.parsley().on('form:validated', function () {
// clear messages
form.find('.mailpoet_message > p').hide();
@ -31,7 +30,7 @@ function (
});
form.parsley().on('form:submit', function (parsley) {
var form_data = form.serializeObject() || {};
var form_data = form.mailpoetSerializeObject() || {};
// check if we're on the same domain
if (isSameDomain(window.MailPoetForm.ajax_url) === false) {
// non ajax post request
@ -84,4 +83,4 @@ function (
});
});
});
});
});

View File

@ -5,7 +5,7 @@ define(
function (
MailPoet
) {
var element;
function eventHandler() {
if (confirm(MailPoet.I18n.t('reinstallConfirmation'))) {
MailPoet.trackEvent(
@ -36,8 +36,8 @@ define(
return false;
}
var element = document.getElementById('mailpoet_reinstall');
element = document.getElementById('mailpoet_reinstall');
if (element) {
element.addEventListener('click', eventHandler, false);
}
});
});

View File

@ -51,7 +51,7 @@ define(
jQuery('#mailpoet_sending_method_setup').fadeIn();
}
},
tabs: function (tabStr, section) {
tabs: function (tabStr) {
// set default tab
var tab = tabStr || 'mta';
@ -86,4 +86,4 @@ define(
if (!Backbone.History.started) Backbone.history.start();
});
}
);
);

View File

@ -3,98 +3,104 @@ define(
'underscore',
'jquery',
'mailpoet',
'handlebars',
'select2'
'handlebars'
],
function (
_,
jQuery,
MailPoet,
Handlebars,
select2
Handlebars
) {
if (!jQuery('#mailpoet_subscribers_export').length) {
return;
}
jQuery(document).ready(function () {
var segmentsContainerElement;
var subscriberFieldsContainerElement;
var exportConfirmedOptionElement;
var groupBySegmentOptionElement;
var nextStepButton;
var renderSegmentsAndFields;
var subscribers_export_template;
if (!window.exportData.segments) {
return;
}
var subscribers_export_template =
subscribers_export_template =
Handlebars.compile(jQuery('#mailpoet_subscribers_export_template').html());
// render template
jQuery('#mailpoet_subscribers_export > div.inside').html(subscribers_export_template(window.exportData));
// define reusable variables
var segmentsContainerElement = jQuery('#export_lists'),
subscriberFieldsContainerElement = jQuery('#export_columns'),
exportConfirmedOptionElement = jQuery(':radio[name="option_confirmed"]'),
groupBySegmentOptionElement = jQuery(':checkbox[name="option_group_by_list"]'),
nextStepButton = jQuery('a.mailpoet_export_process'),
renderSegmentsAndFields = function (container, data) {
if (container.data('select2')) {
container
.html('')
.select2('destroy');
}
segmentsContainerElement = jQuery('#export_lists');
subscriberFieldsContainerElement = jQuery('#export_columns');
exportConfirmedOptionElement = jQuery(':radio[name="option_confirmed"]');
groupBySegmentOptionElement = jQuery(':checkbox[name="option_group_by_list"]');
nextStepButton = jQuery('a.mailpoet_export_process');
renderSegmentsAndFields = function (container, data) {
if (container.data('select2')) {
container
.select2({
data: data,
width: '20em',
templateResult: function (item) {
return (item.subscriberCount > 0)
? item.name + ' (' + parseInt(item.subscriberCount).toLocaleString() + ')'
: item.name;
},
templateSelection: function (item) {
return (item.subscriberCount > 0)
? item.name + ' (' + parseInt(item.subscriberCount).toLocaleString() + ')'
: item.name;
}
})
.on('select2:selecting', function (selectEvent) {
var selectElement = this,
selectedOptionId = selectEvent.params.args.data.id,
fieldsToExclude = [
'select',
'deselect'
];
if (_.contains(fieldsToExclude, selectedOptionId)) {
selectEvent.preventDefault();
if (selectedOptionId === 'deselect') {
jQuery(selectElement).val('').trigger('change');
} else {
var allOptions = [];
_.each(container.find('option'), function (field) {
if (!_.contains(fieldsToExclude, field.value)) {
allOptions.push(field.value);
}
});
jQuery(selectElement).val(allOptions).trigger('change');
}
jQuery(selectElement).select2('close');
}
})
.on('change', function () {
if ((window.exportData.segments && segmentsContainerElement.select2('data').length && subscriberFieldsContainerElement.select2('data').length)
||
(!window.exportData.segments && subscriberFieldsContainerElement.select2('data').length)
) {
toggleNextStepButton('on');
}
else {
toggleNextStepButton('off');
.html('')
.select2('destroy');
}
container
.select2({
data: data,
width: '20em',
templateResult: function (item) {
return (item.subscriberCount > 0)
? item.name + ' (' + parseInt(item.subscriberCount).toLocaleString() + ')'
: item.name;
},
templateSelection: function (item) {
return (item.subscriberCount > 0)
? item.name + ' (' + parseInt(item.subscriberCount).toLocaleString() + ')'
: item.name;
}
})
.on('select2:selecting', function (selectEvent) {
var selectElement = this;
var selectedOptionId = selectEvent.params.args.data.id;
var fieldsToExclude = [
'select',
'deselect'
];
var allOptions;
if (_.contains(fieldsToExclude, selectedOptionId)) {
selectEvent.preventDefault();
if (selectedOptionId === 'deselect') {
jQuery(selectElement).val('').trigger('change');
} else {
allOptions = [];
_.each(container.find('option'), function (field) {
if (!_.contains(fieldsToExclude, field.value)) {
allOptions.push(field.value);
}
});
jQuery(selectElement).val(allOptions).trigger('change');
}
jQuery(selectElement).select2('close');
}
})
.on('change', function () {
if ((window.exportData.segments && segmentsContainerElement.select2('data').length && subscriberFieldsContainerElement.select2('data').length)
||
(!window.exportData.segments && subscriberFieldsContainerElement.select2('data').length)
) {
toggleNextStepButton('on');
}
else {
toggleNextStepButton('off');
}
if (segmentsContainerElement.select2('data').length > 1 && window.exportData.groupBySegmentOption) {
jQuery('.mailpoet_group_by_list').show();
}
else if (window.exportData.groupBySegmentOption) {
jQuery('.mailpoet_group_by_list').hide();
}
});
};
if (segmentsContainerElement.select2('data').length > 1 && window.exportData.groupBySegmentOption) {
jQuery('.mailpoet_group_by_list').show();
}
else if (window.exportData.groupBySegmentOption) {
jQuery('.mailpoet_group_by_list').hide();
}
});
};
// set confirmed subscribers export option to false
window.exportData.exportConfirmedOption = false;
@ -133,11 +139,12 @@ define(
}
nextStepButton.click(function () {
var exportFormat;
if (jQuery(this).hasClass('button-disabled')) {
return;
}
MailPoet.Modal.loading(true);
var exportFormat = jQuery(':radio[name="option_format"]:checked').val();
exportFormat = jQuery(':radio[name="option_format"]:checked').val();
MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'ImportExport',
@ -149,7 +156,7 @@ define(
segments: (window.exportData.segments) ? segmentsContainerElement.val() : false,
subscriber_fields: subscriberFieldsContainerElement.val()
})
}).always(function (response) {
}).always(function () {
MailPoet.Modal.loading(false);
}).done(function (response) {
var resultMessage = MailPoet.I18n.t('exportMessage')

View File

@ -7,8 +7,7 @@ define(
'handlebars',
'papaparse',
'asyncqueue',
'moment',
'select2'
'moment'
],
function (
Backbone,
@ -18,16 +17,16 @@ define(
Handlebars,
Papa,
AsyncQueue,
Moment,
select2
Moment
) {
if (!jQuery('#mailpoet_subscribers_import').length) {
return;
}
jQuery(document).ready(function () {
var router;
jQuery('input[name="select_method"]').attr('checked', false);
// configure router
var router = new (Backbone.Router.extend({
router = new (Backbone.Router.extend({
routes: {
'': 'home',
step1: 'step1',
@ -50,8 +49,20 @@ define(
* STEP 1 (upload or copy/paste)
*/
router.on('route:step1', function () {
var methodProcessContainerTemplate;
var currentStepE;
var methodSelectionElement;
var pasteInputElement;
var pasteInputPlaceholderElement;
var pasteProcessButtonElement;
var mailChimpKeyInputElement;
var mailChimpKeyVerifyButtonElement;
var mailChimpListsContainerElement;
var mailChimpProcessButtonElement;
var uploadElement;
var uploadProcessButtonElement;
// set or reset temporary validation rule on all columns
window.mailpoetColumns = jQuery.map(window.mailpoetColumns, function (column, columnIndex) {
window.mailpoetColumns = jQuery.map(window.mailpoetColumns, function (column) {
var col = column;
col.validation_rule = false;
return col;
@ -63,36 +74,34 @@ define(
}
// render process button for each method
var methodProcessContainerTemplate =
methodProcessContainerTemplate =
Handlebars.compile(jQuery('#method_process_template').html());
jQuery('.mailpoet_method_process').html(methodProcessContainerTemplate());
// define reusable variables
var currentStepE = jQuery(location.hash),
methodSelectionElement = jQuery('#select_method'),
pasteInputElement = jQuery('#paste_input'),
pasteInputPlaceholderElement =
pasteInputElement.data('placeholder').replace(/\\n/g, '\n'),
pasteProcessButtonElement =
jQuery('#method_paste > div.mailpoet_method_process')
.find('a.mailpoet_process'),
mailChimpKeyInputElement = jQuery('#mailchimp_key'),
mailChimpKeyVerifyButtonElement = jQuery('#mailchimp_key_verify'),
mailChimpListsContainerElement = jQuery('#mailchimp_lists'),
mailChimpProcessButtonElement =
jQuery('#method_mailchimp > div.mailpoet_method_process')
.find('a.mailpoet_process'),
uploadElement = jQuery('#file_local'),
uploadProcessButtonElement =
jQuery('#method_file > div.mailpoet_method_process')
.find('a.mailpoet_process');
currentStepE = jQuery(location.hash);
methodSelectionElement = jQuery('#select_method');
pasteInputElement = jQuery('#paste_input');
pasteInputPlaceholderElement =
pasteInputElement.data('placeholder').replace(/\\n/g, '\n');
pasteProcessButtonElement =
jQuery('#method_paste > div.mailpoet_method_process')
.find('a.mailpoet_process');
mailChimpKeyInputElement = jQuery('#mailchimp_key');
mailChimpKeyVerifyButtonElement = jQuery('#mailchimp_key_verify');
mailChimpListsContainerElement = jQuery('#mailchimp_lists');
mailChimpProcessButtonElement = jQuery('#method_mailchimp > div.mailpoet_method_process')
.find('a.mailpoet_process');
uploadElement = jQuery('#file_local');
uploadProcessButtonElement =
jQuery('#method_file > div.mailpoet_method_process')
.find('a.mailpoet_process');
// define method change behavior
methodSelectionElement.change(function () {
var available_methods = jQuery(':radio[name="select_method"]');
var selected_method = available_methods.index(available_methods.filter(':checked'));
MailPoet.Notice.hide();
var available_methods = jQuery(':radio[name="select_method"]'),
selected_method =
available_methods.index(available_methods.filter(':checked'));
// hide all methods
currentStepE.find('.inside')
.children('div[id^="method_"]')
@ -131,9 +140,9 @@ define(
});
pasteProcessButtonElement.click(function () {
var pasteSize = encodeURI(pasteInputElement.val()).split(/%..|./).length - 1;
MailPoet.Notice.hide();
// get an approximate size of textarea paste in bytes
var pasteSize = encodeURI(pasteInputElement.val()).split(/%..|./).length - 1;
if (pasteSize > window.maxPostSizeBytes) {
MailPoet.Notice.error(MailPoet.I18n.t('maxPostSizeNotice'));
return;
@ -149,8 +158,8 @@ define(
* CSV file
*/
uploadElement.change(function () {
MailPoet.Notice.hide();
var ext = this.value.match(/\.(.+)$/);
MailPoet.Notice.hide();
if (ext === null || ext[1].toLowerCase() !== 'csv') {
this.value = '';
MailPoet.Notice.error(MailPoet.I18n.t('wrongFileFormat'));
@ -234,7 +243,7 @@ define(
api_key: mailChimpKeyInputElement.val(),
lists: mailChimpListsContainerElement.find('select').val()
}
}).always(function (response) {
}).always(function () {
MailPoet.Modal.loading(false);
}).done(function (response) {
window.importData.step1 = response.data;
@ -294,48 +303,48 @@ define(
}
function parseCSV(isFile) {
var processedSubscribers = [],
parsedEmails = [],
duplicateEmails = [],
invalidEmails = [],
emailColumnPosition = null,
columnCount = null,
isHeaderFound = false,
advancedOptionHeader = true,
advancedOptionDelimiter = '',
advancedOptionNewline = '',
advancedOptionComments = false,
// trim spaces, commas, periods,
// single/double quotes and convert to lowercase
detectAndCleanupEmail = function (emailString) {
var test;
// decode HTML entities
var email = jQuery('<div />').html(emailString).text();
email = email
.toLowerCase()
// left/right trim spaces, punctuation (e.g., " 'email@email.com'; ")
// right trim non-printable characters (e.g., "email@email.com<6F>")
.replace(/^["';.,\s]+|[^\x20-\x7E]+$|["';.,_\s]+$/g, '')
// remove spaces (e.g., "email @ email . com")
// remove urlencoded characters
.replace(/\s+|%\d+|,+/g, '');
// detect e-mails that will be otherwise rejected by email regex
test = /<(.*?)>/.exec(email);
if (test) {
// is the email inside angle brackets (e.g., 'some@email.com <some@email.com>')?
email = test[1].trim();
}
test = /mailto:(?:\s+)?(.*)/.exec(email);
if (test) {
// is the email in 'mailto:email' format?
email = test[1].trim();
}
// test for valid characters using WP's rule (https://core.trac.wordpress.org/browser/tags/4.7.3/src/wp-includes/formatting.php#L2902)
if (!/^[a-zA-Z0-9!#$%&\'*+\/=?^_`{|}~\.\-@]+$/.test(email)) {
return false;
}
return email;
};
var processedSubscribers = [];
var parsedEmails = [];
var duplicateEmails = [];
var invalidEmails = [];
var emailColumnPosition = null;
var columnCount = null;
var isHeaderFound = false;
var advancedOptionHeader = true;
var advancedOptionDelimiter = '';
var advancedOptionNewline = '';
var advancedOptionComments = false;
// trim spaces, commas, periods,
// single/double quotes and convert to lowercase
var detectAndCleanupEmail = function (emailString) {
var test;
// decode HTML entities
var email = jQuery('<div />').html(emailString).text();
email = email
.toLowerCase()
// left/right trim spaces, punctuation (e.g., " 'email@email.com'; ")
// right trim non-printable characters (e.g., "email@email.com<6F>")
.replace(/^["';.,\s]+|[^\x20-\x7E]+$|["';.,_\s]+$/g, '')
// remove spaces (e.g., "email @ email . com")
// remove urlencoded characters
.replace(/\s+|%\d+|,+/g, '');
// detect e-mails that will be otherwise rejected by email regex
test = /<(.*?)>/.exec(email);
if (test) {
// is the email inside angle brackets (e.g., 'some@email.com <some@email.com>')?
email = test[1].trim();
}
test = /mailto:(?:\s+)?(.*)/.exec(email);
if (test) {
// is the email in 'mailto:email' format?
email = test[1].trim();
}
// test for valid characters using WP's rule (https://core.trac.wordpress.org/browser/tags/4.7.3/src/wp-includes/formatting.php#L2902)
if (!/^[a-zA-Z0-9!#$%&\'*+\/=?^_`{|}~\.\-@]+$/.test(email)) {
return false;
}
return email;
};
return {
skipEmptyLines: true,
@ -347,11 +356,18 @@ define(
MailPoet.Notice.error(MailPoet.I18n.t('dataProcessingError'));
},
complete: function (CSV) {
for (var rowCount in CSV.data) {
var rowData = CSV.data[rowCount].map(function (el) {
var email;
var emailAddress;
var column;
var rowCount;
var rowData;
var rowColumnCount;
var errorNotice;
for (rowCount in CSV.data) {
rowData = CSV.data[rowCount].map(function (el) {
return el.trim();
});
var rowColumnCount = rowData.length;
rowColumnCount = rowData.length;
// set the number of row elements based on the first non-empty row
if (columnCount === null) {
columnCount = rowColumnCount;
@ -365,14 +381,14 @@ define(
// determine position of email address inside an array; this is
// done once and then email regex is run just on that element for each row
if (emailColumnPosition === null) {
for (var column in rowData) {
var email = detectAndCleanupEmail(rowData[column]);
for (column in rowData) {
emailAddress = detectAndCleanupEmail(rowData[column]);
if (emailColumnPosition === null
&& window.emailRegex.test(email)) {
&& window.emailRegex.test(emailAddress)) {
emailColumnPosition = column;
parsedEmails[email] = true; // add current e-mail to an object index
rowData[column] = email;
processedSubscribers[email] = rowData;
parsedEmails[emailAddress] = true; // add current e-mail to an object index
rowData[column] = emailAddress;
processedSubscribers[emailAddress] = rowData;
}
}
if (emailColumnPosition === null
@ -383,7 +399,7 @@ define(
}
}
else if (rowData[emailColumnPosition] !== '') {
var email = detectAndCleanupEmail(rowData[emailColumnPosition]);
email = detectAndCleanupEmail(rowData[emailColumnPosition]);
if (_.has(parsedEmails, email)) {
duplicateEmails.push(email);
}
@ -427,7 +443,7 @@ define(
}
else {
MailPoet.Modal.loading(false);
var errorNotice = MailPoet.I18n.t('noValidRecords');
errorNotice = MailPoet.I18n.t('noValidRecords');
errorNotice = errorNotice.replace('[link]', MailPoet.I18n.t('csvKBLink'));
errorNotice = errorNotice.replace('[/link]', '</a>');
MailPoet.Notice.error(errorNotice);
@ -438,37 +454,41 @@ define(
});
router.on('route:step2', function () {
var nextStepButton;
var previousStepButton;
var subscribers;
var subscribersDataTemplate;
var subscribersDataTemplatePartial;
var subscribersDataParseResultsTemplate;
var segmentSelectElement;
var maxRowsToShow;
var filler;
var fillerArray;
var fillerPosition;
var import_results;
var duplicates;
var email;
if (typeof (window.importData.step1) === 'undefined') {
router.navigate('step1', { trigger: true });
return;
}
// define reusable variables
var nextStepButton = jQuery('#step2_process'),
previousStepButton = jQuery('#return_to_step1'),
nextStepButton = jQuery('#step2_process');
previousStepButton = jQuery('#return_to_step1');
// create a copy of subscribers object for further manipulation
subscribers = jQuery.extend(true, {}, window.importData.step1),
subscribersDataTemplate =
Handlebars
.compile(jQuery('#subscribers_data_template')
.html()),
subscribersDataTemplatePartial =
Handlebars
.compile(jQuery('#subscribers_data_template_partial')
.html()),
subscribersDataParseResultsTemplate =
Handlebars
.compile(jQuery('#subscribers_data_parse_results_template')
.html()),
segmentSelectElement = jQuery('#mailpoet_segments_select'),
maxRowsToShow = 10,
filler = '. . .',
subscribers = jQuery.extend(true, {}, window.importData.step1);
subscribersDataTemplate = Handlebars.compile(jQuery('#subscribers_data_template').html());
subscribersDataTemplatePartial = Handlebars.compile(jQuery('#subscribers_data_template_partial').html());
subscribersDataParseResultsTemplate = Handlebars.compile(jQuery('#subscribers_data_parse_results_template').html());
segmentSelectElement = jQuery('#mailpoet_segments_select');
maxRowsToShow = 10;
filler = '. . .';
// create an array of filler data with the same number of
// elements as in the subscribers' data row
fillerArray = Array.apply(
null,
new Array(subscribers.subscribers[0].length)
).map(String.prototype.valueOf, filler),
fillerPosition;
fillerArray = Array.apply(
null,
new Array(subscribers.subscribers[0].length)
).map(String.prototype.valueOf, filler);
showCurrentStep();
@ -480,12 +500,12 @@ define(
if (subscribers.invalid.length || subscribers.duplicate.length) {
// count repeating e-mails inside duplicate array and present them in
// 'email (xN)' format
var duplicates = {};
duplicates = {};
subscribers.duplicate.forEach(function (email) {
duplicates[email] = (duplicates[email] || 0) + 1;
});
subscribers.duplicate = [];
for (var email in duplicates) {
for (email in duplicates) {
if (duplicates[email] > 1) {
subscribers.duplicate.push(email + ' (x' + duplicates[email] + ')');
}
@ -494,7 +514,7 @@ define(
}
}
var import_results = {
import_results = {
notice: MailPoet.I18n.t('importNoticeSkipped').replace(
'%1$s',
'<strong>' + (subscribers.invalid.length + subscribers.duplicate.length) + '</strong>'
@ -599,13 +619,14 @@ define(
description: segmentDescription
}
}).done(function (response) {
var selected_values;
window.mailpoetSegments.push({
id: response.data.id,
name: response.data.name,
subscriberCount: 0
});
var selected_values = segmentSelectElement.val();
selected_values = segmentSelectElement.val();
if (selected_values === null) {
selected_values = [response.data.id];
} else {
@ -642,20 +663,25 @@ define(
Handlebars.registerHelper(
'show_and_match_columns',
function (subscribers, options) {
var displayedColumns = [],
displayedColumnsIds = [];
var displayedColumns = [];
var displayedColumnsIds = [];
var i;
var columnData;
var columnId;
var headerName;
var headerNameMatch;
// go through all elements of the first row in subscribers data
for (var i in subscribers.subscribers[0]) {
var columnData = subscribers.subscribers[0][i],
columnId = 'ignore'; // set default column type
for (i in subscribers.subscribers[0]) {
columnData = subscribers.subscribers[0][i];
columnId = 'ignore'; // set default column type
// if the column is not undefined and has a valid e-mail, set type as email
if (columnData % 1 !== 0 && window.emailRegex.test(columnData)) {
columnId = 'email';
} else if (subscribers.header) {
var headerName = subscribers.header[i],
headerNameMatch = window.mailpoetColumns.map(function (el) {
return el.name;
}).indexOf(headerName);
headerName = subscribers.header[i];
headerNameMatch = window.mailpoetColumns.map(function (el) {
return el.name;
}).indexOf(headerName);
// set column type using header
if (headerNameMatch !== -1) {
columnId = window.mailpoetColumns[headerNameMatch].id;
@ -733,8 +759,8 @@ define(
}
})
.on('select2:selecting', function (selectEvent) {
var selectElement = this,
selectedOptionId = selectEvent.params.args.data.id;
var selectElement = this;
var selectedOptionId = selectEvent.params.args.data.id;
// CREATE CUSTOM FIELD
if (selectedOptionId === 'create') {
selectEvent.preventDefault();
@ -743,9 +769,9 @@ define(
title: MailPoet.I18n.t('addNewField'),
template: jQuery('#form_template_field_form').html()
});
jQuery('#form_field_new').parsley().on('form:submit', function (parsley) {
jQuery('#form_field_new').parsley().on('form:submit', function () {
// get data
var data = jQuery(this.$element).serializeObject();
var data = jQuery(this.$element).mailpoetSerializeObject();
// save custom field
MailPoet.Ajax.post({
@ -807,8 +833,8 @@ define(
// check for duplicate values in all select options
jQuery('select.mailpoet_subscribers_column_data_match')
.each(function () {
var element = this,
elementId = jQuery(element).val();
var element = this;
var elementId = jQuery(element).val();
// if another column has the same value and it's not an 'ignore', prompt user
if (elementId === selectedOptionId
&& elementId !== 'ignore') {
@ -824,28 +850,34 @@ define(
}
})
.on('select2:select', function (selectEvent) {
var selectElement = this,
selectedOptionId = selectEvent.params.data.id;
var selectElement = this;
var selectedOptionId = selectEvent.params.data.id;
jQuery(selectElement).data('column-id', selectedOptionId);
filterSubscribers();
});
// filter subscribers' data to detect dates, emails, etc.
function filterSubscribers() {
var subscribersClone = jQuery.extend(true, {}, subscribers);
var preventNextStep = false;
var displayedColumns;
jQuery(
'[data-id="notice_invalidEmail"], [data-id="notice_invalidDate"]')
.remove();
var subscribersClone = jQuery.extend(true, {}, subscribers),
preventNextStep = false,
displayedColumns = jQuery.map(
jQuery('.mailpoet_subscribers_column_data_match'), function (element, elementIndex) {
var columnId = jQuery(element).data('column-id');
var validationRule = jQuery(element).data('validation-rule');
jQuery(element).val(columnId).trigger('change');
return { id: columnId, index: elementIndex, validationRule: validationRule, element: element };
});
displayedColumns = jQuery.map(
jQuery('.mailpoet_subscribers_column_data_match'), function (element, elementIndex) {
var columnId = jQuery(element).data('column-id');
var validationRule = jQuery(element).data('validation-rule');
jQuery(element).val(columnId).trigger('change');
return { id: columnId, index: elementIndex, validationRule: validationRule, element: element };
});
// iterate through the object of mailpoet columns
jQuery.map(window.mailpoetColumns, function (column, columnIndex) {
jQuery.map(window.mailpoetColumns, function (column) {
var firstRowData;
var validationRule;
var testedFormat;
var format;
var allowedDateFormats;
// check if the column id matches the selected id of one of the
// subscriber's data columns
var matchedColumn = _.find(displayedColumns, function (data) { return data.id === column.id; });
@ -869,7 +901,7 @@ define(
}
// DATE filter: if column type is date, check if we can recognize it
if (column.type === 'date' && matchedColumn) {
var allowedDateFormats = [
allowedDateFormats = [
Moment.ISO_8601,
'YYYY/MM/DD',
'MM/DD/YYYY',
@ -880,8 +912,8 @@ define(
'YYYY/MM',
'YYYY'
];
var firstRowData = subscribersClone.subscribers[0][matchedColumn.index];
var validationRule = false;
firstRowData = subscribersClone.subscribers[0][matchedColumn.index];
validationRule = false;
// check if date exists
if (firstRowData.trim() === '') {
subscribersClone.subscribers[0][matchedColumn.index] =
@ -892,10 +924,10 @@ define(
preventNextStep = true;
}
else {
for (var format in allowedDateFormats) {
var testedFormat = allowedDateFormats[format];
for (format in allowedDateFormats) {
testedFormat = allowedDateFormats[format];
if (Moment(firstRowData, testedFormat, true).isValid()) {
var validationRule = (typeof (testedFormat) === 'function') ?
validationRule = (typeof (testedFormat) === 'function') ?
'datetime' :
testedFormat;
// set validation on the column element
@ -910,8 +942,8 @@ define(
jQuery.map(subscribersClone.subscribers, function (dataSubscribers, index) {
var data = dataSubscribers;
var rowData = data[matchedColumn.index];
if (index === fillerPosition || rowData.trim() === '') return;
var date = Moment(rowData, testedFormat, true);
if (index === fillerPosition || rowData.trim() === '') return;
// validate date
if (date.isValid()) {
data[matchedColumn.index] = new Handlebars.SafeString(
@ -970,33 +1002,35 @@ define(
});
nextStepButton.off().on('click', function () {
var columns = {};
var queue = new jQuery.AsyncQueue();
var batchNumber = 0;
var batchSize = 2000;
var timestamp = Date.now() / 1000;
var subscribers = [];
var importResults = {
created: 0,
updated: 0,
errors: [],
segments: []
};
var subscribers;
var splitSubscribers;
if (jQuery(this).hasClass('button-disabled')) {
return;
}
MailPoet.Modal.loading(true);
var columns = {},
queue = new jQuery.AsyncQueue(),
batchNumber = 0,
batchSize = 2000,
timestamp = Date.now() / 1000,
subscribers = [],
importResults = {
created: 0,
updated: 0,
errors: [],
segments: []
},
splitSubscribers = function (subscribers, size) {
return subscribers.reduce(function (res, item, index) {
if (index % size === 0) {
res.push([]);
}
res[res.length - 1].push(item);
return res;
}, []);
},
subscribers = splitSubscribers(window.importData.step1.subscribers, batchSize);
splitSubscribers = function (subscribers, size) {
return subscribers.reduce(function (res, item, index) {
if (index % size === 0) {
res.push([]);
}
res[res.length - 1].push(item);
return res;
}, []);
};
subscribers = splitSubscribers(window.importData.step1.subscribers, batchSize);
_.each(jQuery('select.mailpoet_subscribers_column_data_match'),
function (column, columnIndex) {
@ -1067,6 +1101,9 @@ define(
});
router.on('route:step3', function () {
var subscribersDataImportResultsTemplate;
var exportMenuElement;
var importResults;
if (typeof (window.importData.step2) === 'undefined') {
router.navigate('step2', { trigger: true });
return;
@ -1085,25 +1122,23 @@ define(
});
// display statistics
var subscribersDataImportResultsTemplate =
Handlebars
.compile(jQuery('#subscribers_data_import_results_template')
.html()),
exportMenuElement = jQuery('span.mailpoet_export'),
importResults = {
created: (window.importData.step2.created)
? MailPoet.I18n.t('subscribersCreated')
.replace('%1$s', '<strong>' + window.importData.step2.created.toLocaleString() + '</strong>')
.replace('%2$s', '"' + window.importData.step2.segments.join('", "') + '"')
: false,
updated: (window.importData.step2.updated)
? MailPoet.I18n.t('subscribersUpdated')
.replace('%1$s', '<strong>' + window.importData.step2.updated.toLocaleString() + '</strong>')
.replace('%2$s', '"' + window.importData.step2.segments.join('", "') + '"')
: false,
no_action: (!window.importData.step2.created && !window.importData.step2.updated),
added_to_segment_with_welcome_notification: window.importData.step2.added_to_segment_with_welcome_notification
};
subscribersDataImportResultsTemplate =
Handlebars.compile(jQuery('#subscribers_data_import_results_template').html());
exportMenuElement = jQuery('span.mailpoet_export');
importResults = {
created: (window.importData.step2.created)
? MailPoet.I18n.t('subscribersCreated')
.replace('%1$s', '<strong>' + window.importData.step2.created.toLocaleString() + '</strong>')
.replace('%2$s', '"' + window.importData.step2.segments.join('", "') + '"')
: false,
updated: (window.importData.step2.updated)
? MailPoet.I18n.t('subscribersUpdated')
.replace('%1$s', '<strong>' + window.importData.step2.updated.toLocaleString() + '</strong>')
.replace('%2$s', '"' + window.importData.step2.segments.join('", "') + '"')
: false,
no_action: (!window.importData.step2.created && !window.importData.step2.updated),
added_to_segment_with_welcome_notification: window.importData.step2.added_to_segment_with_welcome_notification
};
jQuery('#subscribers_data_import_results')
.html(subscribersDataImportResultsTemplate(importResults))

View File

@ -26,17 +26,27 @@ modules:
adminUsername: admin
adminPassword: password
adminPath: /wp-admin
log_js_errors: true
WPLoader:
loadOnly: true
wpRootFolder: /wp-core
dbName: wordpress
dbHost: mysql
dbUser: wordpress
dbPassword: wordpress
wpDebug: false
tablePrefix: wp_
domain: wordpress
plugins: ['mailpoet/mailpoet.php']
activatePlugins: ['mailpoet/mailpoet.php']
WPDb:
dsn: 'mysql:host=mysql;dbname=wordpress'
user: wordpress
password: wordpress
dump: tests/_data/acceptanceDump.sql
populate: true
cleanup: false
url: 'http://wordpress'
urlReplacement: true
tablePrefix: 'wp_'
WPCLI:
path: /wp-core
throw: true
coverage:
enabled: true
whitelist:

View File

@ -24,7 +24,7 @@
"codeception/verify": "^0.3.3",
"consolidation/robo": "^1.0.5",
"henrikbjorn/lurker": "^1.2",
"lucatume/wp-browser": "1.19.12",
"lucatume/wp-browser": "1.21.20",
"phpunit/phpunit": "4.8.21",
"vlucas/phpdotenv": "^2.4.0",
"umpirsky/twig-gettext-extractor": "1.1.*",

2229
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -25,6 +25,7 @@ services:
- 8080:80
environment:
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_TABLE_PREFIX: mp_
mysql:
image: mysql:5.6
@ -44,4 +45,4 @@ services:
- '4444'
- '5900:5900'
volumes:
wp-core:
wp-core:

View File

@ -14,7 +14,9 @@ done
# Make sure permissions are correct.
cd /wp-core
chown www-data:www-data wp-content/plugins
chown www-data:www-data wp-content/uploads
chmod 755 wp-content/plugins
chmod -R 777 wp-content/uploads
# Make sure WordPress is installed.
if ! $(wp-su core is-installed); then
@ -29,14 +31,27 @@ if ! $(wp-su core is-installed); then
fi
rm -rf /project/vendor_backup
mv /project/vendor /project/vendor_backup
cd /project
php composer.phar install
# Load Composer dependencies
# Set KEEP_DEPS environment flag to not redownload them on each run, only for the 1st time, useful for development.
# Example: docker-compose run -e KEEP_DEPS=1 codeception ...
# Don't forget to restore your original /vendor folder from /vendor_backup manually or by running acceptance tests without this flag.
if [[ -z "${KEEP_DEPS}" ]]; then
rm -rf /project/vendor_backup
fi
if [ ! -d "/project/vendor_backup" ]; then
mv /project/vendor /project/vendor_backup
cd /project
php composer.phar install
fi
cd /wp-core/wp-content/plugins/mailpoet
/project/vendor/bin/codecept run acceptance -c codeception.acceptance.yml $@
exitcode=$?
rm -rf /project/vendor
mv /project/vendor_backup /project/vendor
if [[ -z "${KEEP_DEPS}" ]]; then
rm -rf /project/vendor
mv /project/vendor_backup /project/vendor
fi
exit $exitcode

View File

@ -57,7 +57,7 @@ class API {
$this->setRequestData($_POST);
if($this->checkToken() === false) {
$error_message = __('Invalid API request.', 'mailpoet');
$error_message = __('Sorry, but we couldn\'t connect to the MailPoet server. Please refresh the web page and try again.', 'mailpoet');
$error_response = $this->createErrorResponse(Error::UNAUTHORIZED, $error_message, Response::STATUS_UNAUTHORIZED);
return $error_response->send();
}
@ -200,4 +200,4 @@ class API {
);
return $error_response;
}
}
}

View File

@ -9,6 +9,7 @@ abstract class Response {
const STATUS_UNAUTHORIZED = 401;
const STATUS_FORBIDDEN = 403;
const STATUS_NOT_FOUND = 404;
const STATUS_UNKNOWN = 500;
public $status;
public $meta;

View File

@ -10,6 +10,7 @@ use MailPoet\Models\Newsletter;
use MailPoet\Models\SendingQueue as SendingQueueModel;
use MailPoet\Models\Subscriber;
use MailPoet\Newsletter\Scheduler\Scheduler;
use MailPoet\Segments\SubscribersFinder;
use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit;
@ -75,10 +76,8 @@ class SendingQueue extends APIEndpoint {
$queue->count_total = $queue->count_to_process = 0;
} else {
$segments = $newsletter->segments()->findArray();
$segment_ids = array_map(function($segment) {
return $segment['id'];
}, $segments);
$subscribers = Subscriber::getSubscribedInSegments($segment_ids)->findArray();
$finder = new SubscribersFinder();
$subscribers = $finder->getSubscribersByList($segments);
$subscribers = Helpers::flattenArray($subscribers);
if(!count($subscribers)) {
return $this->errorResponse(array(

View File

@ -10,7 +10,7 @@ use MailPoet\Form\Util\FieldNameObfuscator;
use MailPoet\Models\Form;
use MailPoet\Models\StatisticsForms;
use MailPoet\Models\Subscriber;
use MailPoet\Util\Helpers;
use MailPoet\Subscription\Throttling as SubscriptionThrottling;
if(!defined('ABSPATH')) exit;
@ -98,16 +98,10 @@ class Subscribers extends APIEndpoint {
$data = array_intersect_key($data, array_flip($form_fields));
// make sure we don't allow too many subscriptions with the same ip address
$subscription_count = Subscriber::where(
'subscribed_ip',
Helpers::getIP()
)->whereRaw(
'(TIME_TO_SEC(TIMEDIFF(NOW(), created_at)) < ? OR TIME_TO_SEC(TIMEDIFF(NOW(), updated_at)) < ?)',
array(self::SUBSCRIPTION_LIMIT_COOLDOWN, self::SUBSCRIPTION_LIMIT_COOLDOWN)
)->count();
$timeout = SubscriptionThrottling::throttle();
if($subscription_count > 0) {
throw new \Exception(__('You need to wait before subscribing again.', 'mailpoet'));
if($timeout > 0) {
throw new \Exception(sprintf(__('You need to wait %d seconds before subscribing again.', 'mailpoet'), $timeout));
}
$subscriber = Subscriber::subscribe($data, $segment_ids);

View File

@ -133,6 +133,37 @@ class API {
return $new_subscriber->withCustomFields()->withSubscriptions()->asArray();
}
function addList(array $list) {
// throw exception when list name is missing
if(empty($list['name'])) {
throw new \Exception(
__('List name is required.', 'mailpoet')
);
}
// throw exception when list already exists
if(Segment::where('name', $list['name'])->findOne()) {
throw new \Exception(
__('This list already exists.', 'mailpoet')
);
}
// add list
$new_list = Segment::create();
$new_list->hydrate($list);
$new_list->save();
if($new_list->getErrors() !== false) {
throw new \Exception(
__(sprintf('Failed to add list: %s', strtolower(implode(', ', $new_list->getErrors()))), 'mailpoet')
);
}
// reload list to get the saved created|updated|delete dates/other fields
$new_list = Segment::findOne($new_list->id);
return $new_list->asArray();
}
protected function _sendConfirmationEmail(Subscriber $subscriber) {
return $subscriber->sendConfirmationEmail();
}

View File

@ -12,7 +12,7 @@ use MailPoet\Settings\Pages;
class Reporter {
function getData() {
global $wpdb, $wp_version;
$mta = Setting::getValue('mta', array());
$newsletters = Newsletter::getAnalytics();
$isCronTriggerMethodWP = Setting::getValue('cron_trigger.method') === CronTrigger::$available_methods['wordpress'];
@ -21,6 +21,16 @@ class Reporter {
return array(
'PHP version' => PHP_VERSION,
'MySQL version' => $wpdb->db_version(),
'WordPress version' => $wp_version,
'Multisite environment' => is_multisite() ? 'yes' : 'no',
'RTL' => is_rtl() ? 'yes' : 'no',
'WP_MEMORY_LIMIT' => WP_MEMORY_LIMIT,
'WP_MAX_MEMORY_LIMIT' => WP_MAX_MEMORY_LIMIT,
'PHP memory_limit' => ini_get('memory_limit'),
'PHP max_execution_time' => ini_get('max_execution_time'),
'users_can_register' => get_option('users_can_register') ? 'yes' : 'no',
'MailPoet Free version' => MAILPOET_VERSION,
'MailPoet Premium version' => (defined('MAILPOET_PREMIUM_VERSION')) ? MAILPOET_PREMIUM_VERSION : 'N/A',
'Total number of subscribers' => Subscriber::getTotalSubscribers(),

View File

@ -22,8 +22,6 @@ class AccessControl {
function __construct() {
$this->permissions = self::getDefaultPermissions();
$this->user_roles = $this->getUserRoles();
$this->user_capabilities = $this->getUserCapabilities();
}
static function getDefaultPermissions() {
@ -80,30 +78,8 @@ class AccessControl {
);
}
function getUserRoles() {
$user = wp_get_current_user();
return $user->roles;
}
function getUserCapabilities() {
$user = wp_get_current_user();
return array_keys($user->allcaps);
}
function getUserFirstCapability() {
return (!empty($this->user_capabilities)) ?
$this->user_capabilities[0] :
null;
}
function validatePermission($permission) {
if($permission === self::NO_ACCESS_RESTRICTION) return true;
foreach($this->user_roles as $role) {
$role_object = get_role($role);
if($role_object && $role_object->has_cap($permission)) {
return true;
}
}
return false;
return current_user_can($permission);
}
}

View File

@ -27,6 +27,7 @@ class Capabilities {
if(!isset($role_objects[$role])) {
$role_objects[$role] = get_role($role);
}
if(!is_object($role_objects[$role])) continue;
$role_objects[$role]->add_cap($name);
}
}
@ -40,6 +41,7 @@ class Capabilities {
if(!isset($role_objects[$role])) {
$role_objects[$role] = get_role($role);
}
if(!is_object($role_objects[$role])) continue;
$role_objects[$role]->remove_cap($name);
}
}

View File

@ -68,6 +68,7 @@ class Database {
$subscribers = Env::$db_prefix . 'subscribers';
$subscriber_segment = Env::$db_prefix . 'subscriber_segment';
$subscriber_custom_field = Env::$db_prefix . 'subscriber_custom_field';
$subscriber_ips = Env::$db_prefix . 'subscriber_ips';
$newsletter_segment = Env::$db_prefix . 'newsletter_segment';
$scheduled_tasks = Env::$db_prefix . 'scheduled_tasks';
$scheduled_task_subscribers = Env::$db_prefix . 'scheduled_task_subscribers';
@ -92,6 +93,7 @@ class Database {
define('MP_SUBSCRIBERS_TABLE', $subscribers);
define('MP_SUBSCRIBER_SEGMENT_TABLE', $subscriber_segment);
define('MP_SUBSCRIBER_CUSTOM_FIELD_TABLE', $subscriber_custom_field);
define('MP_SUBSCRIBER_IPS_TABLE', $subscriber_ips);
define('MP_SCHEDULED_TASKS_TABLE', $scheduled_tasks);
define('MP_SCHEDULED_TASK_SUBSCRIBERS_TABLE', $scheduled_task_subscribers);
define('MP_SENDING_QUEUES_TABLE', $sending_queues);

View File

@ -36,6 +36,9 @@ class Initializer {
return;
}
// load translations
$this->setupLocalizer();
try {
$this->setupDB();
} catch(\Exception $e) {
@ -124,7 +127,6 @@ class Initializer {
$this->setupInstaller();
$this->setupUpdater();
$this->setupLocalizer();
$this->setupCapabilities();
$this->setupMenu();
$this->setupShortcodes();

View File

@ -228,6 +228,8 @@ class Menu {
));
});
do_action('mailpoet_menu_after_lists');
// Settings page
add_submenu_page(
self::MAIN_PAGE_SLUG,
@ -460,7 +462,12 @@ class Menu {
$data = array();
$data['items_per_page'] = $this->getLimitPerPage('subscribers');
$data['segments'] = Segment::getSegmentsWithSubscriberCount($type = false);
$segments = Segment::getSegmentsWithSubscriberCount($type = false);
$segments = apply_filters('mailpoet_segments_with_subscriber_count', $segments);
usort($segments, function ($a, $b) {
return strcasecmp($a["name"], $b["name"]);
});
$data['segments'] = $segments;
$data['custom_fields'] = array_map(function($field) {
$field['params'] = unserialize($field['params']);
@ -514,7 +521,12 @@ class Menu {
$data = array();
$data['items_per_page'] = $this->getLimitPerPage('newsletters');
$data['segments'] = Segment::getSegmentsWithSubscriberCount($type = false);
$segments = Segment::getSegmentsWithSubscriberCount($type = false);
$segments = apply_filters('mailpoet_segments_with_subscriber_count', $segments);
usort($segments, function ($a, $b) {
return strcasecmp($a["name"], $b["name"]);
});
$data['segments'] = $segments;
$data['settings'] = Setting::getAll();
$data['roles'] = $wp_roles->get_names();
$data['roles']['mailpoet_all'] = __('In any WordPress role', 'mailpoet');
@ -529,6 +541,7 @@ class Menu {
);
$data['tracking_enabled'] = Setting::getValue('tracking.enabled');
$data['premium_plugin_active'] = License::getLicense();
wp_enqueue_script('jquery-ui');
wp_enqueue_script('jquery-ui-datepicker');
@ -677,7 +690,7 @@ class Menu {
$this->premium_key_valid = $checker->isPremiumKeyValid($show_notices);
}
private function getLimitPerPage($model = null) {
function getLimitPerPage($model = null) {
if($model === null) {
return Listing\Handler::DEFAULT_LIMIT_PER_PAGE;
}
@ -690,7 +703,7 @@ class Menu {
: Listing\Handler::DEFAULT_LIMIT_PER_PAGE;
}
private function displayPage($template, $data) {
function displayPage($template, $data) {
try {
echo $this->renderer->render($template, $data);
} catch(\Exception $e) {

View File

@ -23,6 +23,7 @@ class Migrator {
'subscribers',
'subscriber_segment',
'subscriber_custom_field',
'subscriber_ips',
'newsletters',
'newsletter_templates',
'newsletter_option_fields',
@ -207,6 +208,16 @@ class Migrator {
return $this->sqlify(__FUNCTION__, $attributes);
}
function subscriberIps() {
$attributes = array(
'ip varchar(45) NOT NULL,',
'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
'PRIMARY KEY (created_at, ip),',
'KEY ip (ip)'
);
return $this->sqlify(__FUNCTION__, $attributes);
}
function newsletters() {
$attributes = array(
'id int(11) unsigned NOT NULL AUTO_INCREMENT,',

View File

@ -56,12 +56,14 @@ class Shortcodes {
}
if(empty($segment_ids)) {
return Subscriber::filter('subscribed')->count();
return number_format_i18n(Subscriber::filter('subscribed')->count());
} else {
return SubscriberSegment::whereIn('segment_id', $segment_ids)
->select('subscriber_id')->distinct()
->filter('subscribed')
->findResultSet()->count();
return number_format_i18n(
SubscriberSegment::whereIn('segment_id', $segment_ids)
->select('subscriber_id')->distinct()
->filter('subscribed')
->findResultSet()->count()
);
}
}

View File

@ -105,7 +105,7 @@ class Widget {
wp_enqueue_script(
'mailpoet_public',
Env::$assets_url . '/js/' . $this->renderer->getJsAsset('public.js'),
array(),
array('jquery'),
Env::$version,
true
);

View File

@ -7,6 +7,7 @@ use MailPoet\Models\Newsletter;
use MailPoet\Models\SendingQueue;
use MailPoet\Models\Subscriber;
use MailPoet\Models\SubscriberSegment;
use MailPoet\Segments\SubscribersFinder;
use MailPoet\Util\Helpers;
use MailPoet\Newsletter\Scheduler\Scheduler as NewsletterScheduler;
@ -73,12 +74,11 @@ class Scheduler {
if(empty($segments)) {
return $this->deleteQueueOrUpdateNextRunDate($queue, $newsletter);
}
$segment_ids = array_map(function($segment) {
return (int)$segment['id'];
}, $segments);
// ensure that subscribers are in segments
$subscribers = Subscriber::getSubscribedInSegments($segment_ids)->findArray();
$finder = new SubscribersFinder();
$subscribers = $finder->getSubscribersByList($segments);
$subscribers = Helpers::flattenArray($subscribers);
if(empty($subscribers)) {

View File

@ -10,6 +10,7 @@ use MailPoet\Models\Newsletter as NewsletterModel;
use MailPoet\Models\SendingQueue as SendingQueueModel;
use MailPoet\Models\StatisticsNewsletters as StatisticsNewslettersModel;
use MailPoet\Models\Subscriber as SubscriberModel;
use MailPoet\Segments\SubscribersFinder;
if(!defined('ABSPATH')) exit;
@ -51,10 +52,11 @@ class SendingQueue {
foreach($subscriber_batches as $subscribers_to_process_ids) {
if(!empty($newsletter_segments_ids[0])) {
// Check that subscribers are in segments
$found_subscribers = SubscriberModel::findSubscribersInSegments(
$subscribers_to_process_ids, $newsletter_segments_ids
)->findMany();
$found_subscribers_ids = SubscriberModel::extractSubscribersIds($found_subscribers);
$finder = new SubscribersFinder();
$found_subscribers_ids = $finder->findSubscribersInSegments($subscribers_to_process_ids, $newsletter_segments_ids);
$found_subscribers = SubscriberModel::whereIn('id', $subscribers_to_process_ids)
->whereNull('deleted_at')
->findMany();
} else {
// No segments = Welcome emails
$found_subscribers = SubscriberModel::whereIn('id', $subscribers_to_process_ids)

View File

@ -26,6 +26,7 @@ abstract class Base {
if(!empty($block['params']['required'])) {
$rules['required'] = true;
$rules['required-message'] = __('This field is required.', 'mailpoet');
}
if(!empty($block['params']['validate'])) {

View File

@ -41,7 +41,7 @@ class Renderer {
static function renderBlocks($blocks = array(), $honeypot_enabled = true) {
// add honeypot for spambots
$html = ($honeypot_enabled) ?
'<label class="mailpoet_hp_email_label">' . __('Please leave this field empty', 'mailpoet') . '<input type="email" name="data[email]"></label>' :
'<label class="mailpoet_hp_email_label">' . __('Please leave this field empty', 'mailpoet') . '<input autocomplete="not-really-email" type="email" name="data[email]"></label>' :
'';
foreach($blocks as $key => $block) {
$html .= static::renderBlock($block) . PHP_EOL;

View File

@ -1,10 +1,11 @@
<?php
namespace MailPoet\Helpscout;
use MailPoet\Cron\CronHelper;
use MailPoet\Models\Subscriber;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Router\Endpoints\CronDaemon;
use MailPoet\Router\Router;
use MailPoet\Services\Bridge;
if(!defined('ABSPATH')) exit;
@ -16,11 +17,6 @@ class Beacon {
$mta = Setting::getValue('mta');
$current_theme = wp_get_theme();
$current_user = wp_get_current_user();
$cron_ping_url = Router::buildRequest(
CronDaemon::ENDPOINT,
CronDaemon::ACTION_PING
);
$cron_ping_url = str_replace(home_url(), CronHelper::getSiteUrl(), $cron_ping_url);
$premium_key = Setting::getValue(Bridge::PREMIUM_KEY_SETTING_NAME) ?: Setting::getValue(Bridge::API_KEY_SETTING_NAME);
return array(
'name' => $current_user->display_name,
@ -51,7 +47,9 @@ class Beacon {
$mta['frequency']['interval']
),
'Task Scheduler method' => Setting::getValue('cron_trigger.method'),
'Cron ping URL' => $cron_ping_url,
'Cron ping URL' => CronHelper::getCronUrl(
CronDaemon::ACTION_PING
),
'Default FROM address' => Setting::getValue('sender.address'),
'Default Reply-To address' => Setting::getValue('reply_to.address'),
'Bounce Email Address' => Setting::getValue('bounce.address'),

View File

@ -152,6 +152,7 @@ class Segment extends Model {
static function getSegmentsWithSubscriberCount($type = self::TYPE_DEFAULT) {
$query = self::selectMany(array(self::$_table.'.id', self::$_table.'.name'))
->whereIn('type', array(Segment::TYPE_WP_USERS, Segment::TYPE_DEFAULT))
->selectExpr(
self::$_table.'.*, ' .
'COUNT(IF('.
@ -209,6 +210,15 @@ class Segment extends Model {
)->findArray();
}
static function listingQuery(array $data = array()) {
$query = self::select('*');
$query->whereIn('type', array(Segment::TYPE_WP_USERS, Segment::TYPE_DEFAULT));
if(isset($data['group'])) {
$query->filter('groupBy', $data['group']);
}
return $query;
}
static function createOrUpdate($data = array()) {
$segment = false;

View File

@ -261,6 +261,7 @@ class Subscriber extends Model {
$segments = Segment::orderByAsc('name')
->whereNull('deleted_at')
->whereIn('type', array(Segment::TYPE_WP_USERS, Segment::TYPE_DEFAULT))
->findMany();
$segment_list = array();
$segment_list[] = array(

View File

@ -0,0 +1,8 @@
<?php
namespace MailPoet\Models;
if(!defined('ABSPATH')) exit;
class SubscriberIP extends Model {
public static $_table = MP_SUBSCRIBER_IPS_TABLE;
}

View File

@ -43,7 +43,7 @@ class MetaInformationManager {
// Get categories
$categories = wp_get_post_terms(
$post_id,
array('post_tag', 'category'),
array('category'),
array('fields' => 'names')
);
if(!empty($categories)) {

View File

@ -1,6 +1,7 @@
<?php
namespace MailPoet\Newsletter\Renderer\Blocks;
use MailPoet\Logger;
use MailPoet\Newsletter\Renderer\Columns\ColumnsHelper;
use MailPoet\Newsletter\Renderer\StylesHelper;
@ -9,6 +10,9 @@ class Image {
if(empty($element['src'])) {
return '';
}
if(substr($element['src'], 0, 1) == '/' && substr($element['src'], 1, 1) != '/') {
$element['src'] = get_option('siteurl') . $element['src'];
}
$element['width'] = (int)$element['width'];
$element['height'] = (int)$element['height'];
@ -20,9 +24,13 @@ class Image {
if(!empty($element['link'])) {
$image_template = '<a href="' . $element['link'] . '">' . $image_template . '</a>';
}
$align = 'center';
if(!empty($element['styles']['block']['textAlign']) && in_array($element['styles']['block']['textAlign'], array('left', 'right'))) {
$align = $element['styles']['block']['textAlign'];
}
$template = '
<tr>
<td class="mailpoet_image ' . (($element['fullWidth'] === false) ? 'mailpoet_padded_bottom mailpoet_padded_side' : '') . '" align="center" valign="top">
<td class="mailpoet_image ' . (($element['fullWidth'] === false) ? 'mailpoet_padded_bottom mailpoet_padded_side' : '') . '" align="' . $align . '" valign="top">
' . $image_template . '
</td>
</tr>';

View File

@ -23,7 +23,7 @@ class Link {
case 'subscription_unsubscribe_url':
return self::processUrl(
$action,
SubscriptionUrl::getUnsubscribeUrl($subscriber),
SubscriptionUrl::getUnsubscribeUrl($wp_user_preview ? false : $subscriber),
$queue,
$wp_user_preview
);
@ -31,7 +31,7 @@ class Link {
case 'subscription_manage_url':
return self::processUrl(
$action,
SubscriptionUrl::getManageUrl($subscriber),
SubscriptionUrl::getManageUrl($wp_user_preview ? false : $subscriber),
$queue,
$wp_user_preview
);
@ -40,7 +40,7 @@ class Link {
$url = NewsletterUrl::getViewInBrowserUrl(
$type = null,
$newsletter,
$subscriber,
$wp_user_preview ? false : $subscriber,
$queue,
$wp_user_preview
);
@ -63,7 +63,8 @@ class Link {
}
static function processUrl($action, $url, $queue, $wp_user_preview = false) {
if($wp_user_preview) return '#';
if ($wp_user_preview)
return $url;
return ($queue !== false && (boolean)Setting::getValue('tracking.enabled')) ?
self::getFullShortcode($action) :
$url;
@ -111,4 +112,4 @@ class Link {
private static function getFullShortcode($action) {
return sprintf('[link:%s]', $action);
}
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace MailPoet\Segments;
use MailPoet\Models\Segment;
use MailPoet\Models\Subscriber;
use MailPoet\WP\Hooks;
class SubscribersFinder {
function findSubscribersInSegments($subscribers_to_process_ids, $newsletter_segments_ids) {
$result = array();
foreach($newsletter_segments_ids as $segment_id) {
$segment = Segment::find_one($segment_id)->asArray();
$result = array_merge($result, $this->findSubscribersInSegment($segment, $subscribers_to_process_ids));
}
return $this->unique($result);
}
private function findSubscribersInSegment($segment, $subscribers_to_process_ids) {
if($segment['type'] === Segment::TYPE_DEFAULT || $segment['type'] === Segment::TYPE_WP_USERS) {
$subscribers = Subscriber::findSubscribersInSegments($subscribers_to_process_ids, array($segment['id']))->findMany();
return Subscriber::extractSubscribersIds($subscribers);
}
$finders = Hooks::applyFilters('mailpoet_get_subscribers_in_segment_finders', array());
foreach($finders as $finder) {
$subscribers = $finder->findSubscribersInSegment($segment, $subscribers_to_process_ids);
if($subscribers) {
return $subscribers;
}
}
return array();
}
function getSubscribersByList($segments) {
$result = array();
foreach($segments as $segment) {
$result = array_merge($result, $this->getSubscribers($segment));
}
return $this->unique($result);
}
private function getSubscribers($segment) {
if($segment['type'] === Segment::TYPE_DEFAULT || $segment['type'] === Segment::TYPE_WP_USERS) {
return Subscriber::getSubscribedInSegments(array($segment['id']))->findArray();
}
$finders = Hooks::applyFilters('mailpoet_get_subscribers_in_segment_finders', array());
foreach($finders as $finder) {
$subscribers = $finder->getSubscriberIdsInSegment($segment);
if($subscribers) {
return $subscribers;
}
}
return array();
}
private function unique($subscribers) {
$result = array();
foreach($subscribers as $subscriber) {
if(is_a($subscriber, 'MailPoet\Models\Model')) {
$result[$subscriber->id] = $subscriber;
} elseif(is_scalar($subscriber)) {
$result[$subscriber] = $subscriber;
} else {
$result[$subscriber['id']] = $subscriber;
}
}
return $result;
}
}

View File

@ -80,86 +80,77 @@ class WP {
static function synchronizeUsers() {
self::updateSubscriberWPUserIds();
self::updateSubscribersEmails();
self::insertSubscribers();
self::removeFromTrash();
self::updateFirstNames();
self::updateLastNames();
self::updateFristNameIfMissing();
self::updateFirstNameIfMissing();
self::insertUsersToSegment();
self::removeOrphanedSubscribers();
return true;
}
private static function updateSubscriberWPUserIds() {
global $wpdb;
$subscribers_table = Subscriber::$_table;
Subscriber::raw_execute(sprintf('
UPDATE IGNORE %s mps
JOIN %s as wu ON mps.email = wu.user_email
SET mps.wp_user_id = wu.ID
WHERE mps.wp_user_id IS NULL
', $subscribers_table, $wpdb->users));
}
private static function updateSubscribersEmails() {
global $wpdb;
$subscribers_table = Subscriber::$_table;
Subscriber::raw_execute(sprintf('
UPDATE IGNORE %s
JOIN %s as wu ON %s.wp_user_id = wu.id
SET email = user_email
WHERE %s.wp_user_id IS NOT NULL
', $subscribers_table, $wpdb->users, $subscribers_table, $subscribers_table));
UPDATE IGNORE %1$s
JOIN %2$s as wu ON %1$s.wp_user_id = wu.id
SET %1$s.email = wu.user_email
WHERE %1$s.wp_user_id IS NOT NULL AND wu.user_email != ""
', $subscribers_table, $wpdb->users));
}
private static function insertSubscribers() {
global $wpdb;
$subscribers_table = Subscriber::$_table;
Subscriber::raw_execute(sprintf('
INSERT IGNORE INTO %s(wp_user_id, email, status, created_at)
SELECT wu.id, wu.user_email, "subscribed", CURRENT_TIMESTAMP() FROM %s wu
LEFT JOIN %s mps ON wu.id = mps.wp_user_id
WHERE mps.wp_user_id IS NULL
', $subscribers_table, $wpdb->users, $subscribers_table));
INSERT IGNORE INTO %1$s(wp_user_id, email, status, created_at)
SELECT wu.id, wu.user_email, "subscribed", CURRENT_TIMESTAMP() FROM %2$s wu
LEFT JOIN %1$s mps ON wu.id = mps.wp_user_id
WHERE mps.wp_user_id IS NULL AND wu.user_email != ""
ON DUPLICATE KEY UPDATE wp_user_id = wu.id
', $subscribers_table, $wpdb->users));
}
private static function updateFirstNames() {
global $wpdb;
$subscribers_table = Subscriber::$_table;
Subscriber::raw_execute(sprintf('
UPDATE %s
JOIN %s as wpum ON %s.wp_user_id = wpum.user_id AND meta_key = "first_name"
SET first_name = meta_value
WHERE %s.first_name = ""
AND %s.wp_user_id IS NOT NULL
', $subscribers_table, $wpdb->usermeta, $subscribers_table, $subscribers_table, $subscribers_table));
UPDATE %1$s
JOIN %2$s as wpum ON %1$s.wp_user_id = wpum.user_id AND wpum.meta_key = "first_name"
SET %1$s.first_name = wpum.meta_value
WHERE %1$s.first_name = ""
AND %1$s.wp_user_id IS NOT NULL
AND wpum.meta_value IS NOT NULL
', $subscribers_table, $wpdb->usermeta));
}
private static function updateLastNames() {
global $wpdb;
$subscribers_table = Subscriber::$_table;
Subscriber::raw_execute(sprintf('
UPDATE %s
JOIN %s as wpum ON %s.wp_user_id = wpum.user_id AND meta_key = "last_name"
SET last_name = meta_value
WHERE %s.last_name = ""
AND %s.wp_user_id IS NOT NULL
', $subscribers_table, $wpdb->usermeta, $subscribers_table, $subscribers_table, $subscribers_table));
UPDATE %1$s
JOIN %2$s as wpum ON %1$s.wp_user_id = wpum.user_id AND wpum.meta_key = "last_name"
SET %1$s.last_name = wpum.meta_value
WHERE %1$s.last_name = ""
AND %1$s.wp_user_id IS NOT NULL
AND wpum.meta_value IS NOT NULL
', $subscribers_table, $wpdb->usermeta));
}
private static function updateFristNameIfMissing() {
private static function updateFirstNameIfMissing() {
global $wpdb;
$subscribers_table = Subscriber::$_table;
Subscriber::raw_execute(sprintf('
UPDATE %s
JOIN %s wu ON %s.wp_user_id = wu.id
SET first_name = display_name
WHERE %s.first_name = ""
AND %s.wp_user_id IS NOT NULL
', $subscribers_table, $wpdb->users, $subscribers_table, $subscribers_table, $subscribers_table));
UPDATE %1$s
JOIN %2$s wu ON %1$s.wp_user_id = wu.id
SET %1$s.first_name = wu.display_name
WHERE %1$s.first_name = ""
AND %1$s.wp_user_id IS NOT NULL
', $subscribers_table, $wpdb->users));
}
private static function insertUsersToSegment() {
@ -176,10 +167,10 @@ class WP {
private static function removeFromTrash() {
$subscribers_table = Subscriber::$_table;
Subscriber::raw_execute(sprintf('
UPDATE %s
SET deleted_at = NULL
WHERE %s.wp_user_id IS NOT NULL
', $subscribers_table, $subscribers_table));
UPDATE %1$s
SET %1$s.deleted_at = NULL
WHERE %1$s.wp_user_id IS NOT NULL
', $subscribers_table));
}
private static function removeOrphanedSubscribers() {
@ -191,7 +182,7 @@ class WP {
$wp_segment->subscribers()
->leftOuterJoin($wpdb->users, array(MP_SUBSCRIBERS_TABLE . '.wp_user_id', '=', 'wu.id'), 'wu')
->whereNull('wu.id')
->whereRaw('(wu.id IS NULL OR ' . MP_SUBSCRIBERS_TABLE . '.email = "")')
->findResultSet()
->set('wp_user_id', null)
->delete();

View File

@ -0,0 +1,55 @@
<?php
namespace MailPoet\Subscription;
use MailPoet\Models\SubscriberIP;
use MailPoet\Util\Helpers;
use MailPoet\WP\Hooks;
class Throttling {
static function throttle() {
$subscription_limit_enabled = Hooks::applyFilters('mailpoet_subscription_limit_enabled', true);
$subscription_limit_window = Hooks::applyFilters('mailpoet_subscription_limit_window', DAY_IN_SECONDS);
$subscription_limit_base = Hooks::applyFilters('mailpoet_subscription_limit_base', MINUTE_IN_SECONDS);
$subscriber_ip = Helpers::getIP();
if($subscription_limit_enabled && !is_user_logged_in()) {
if(!empty($subscriber_ip)) {
$subscription_count = SubscriberIP::where('ip', $subscriber_ip)
->whereRaw(
'(`created_at` >= NOW() - INTERVAL ? SECOND)',
array((int)$subscription_limit_window)
)->count();
if($subscription_count > 0) {
$timeout = $subscription_limit_base * pow(2, $subscription_count - 1);
$existing_user = SubscriberIP::where('ip', $subscriber_ip)
->whereRaw(
'(`created_at` >= NOW() - INTERVAL ? SECOND)',
array((int)$timeout)
)->findOne();
if(!empty($existing_user)) {
return $timeout;
}
}
}
}
$ip = SubscriberIP::create();
$ip->ip = $subscriber_ip;
$ip->save();
self::purge($subscription_limit_window);
return false;
}
static function purge($interval) {
return SubscriberIP::whereRaw(
'(`created_at` < NOW() - INTERVAL ? SECOND)',
array($interval)
)->deleteMany();
}
}

View File

@ -10,6 +10,10 @@ class Hooks {
return self::callWithFallback('apply_filters', func_get_args());
}
static function removeFilter() {
return self::callWithFallback('remove_filter', func_get_args());
}
static function addAction() {
return self::callWithFallback('add_action', func_get_args());
}
@ -18,6 +22,10 @@ class Hooks {
return self::callWithFallback('do_action', func_get_args());
}
static function removeAction() {
return self::callWithFallback('remove_action', func_get_args());
}
private static function callWithFallback($func, $args) {
$local_func = __NAMESPACE__ . '\\' . $func;
if(function_exists($local_func)) {

View File

@ -3,8 +3,8 @@
if(!defined('ABSPATH')) exit;
/*
* Plugin Name: MailPoet 3 (new)
* Version: 3.0.2
* Plugin Name: MailPoet 3 (New)
* Version: 3.1.0
* Plugin URI: http://www.mailpoet.com
* Description: Create and send newsletters, post notifications and welcome emails from your WordPress.
* Author: MailPoet
@ -20,7 +20,7 @@ if(!defined('ABSPATH')) exit;
*/
$mailpoet_plugin = array(
'version' => '3.0.2',
'version' => '3.1.0',
'filename' => __FILE__,
'path' => dirname(__FILE__),
'autoloader' => dirname(__FILE__) . '/vendor/autoload.php',

View File

@ -4,7 +4,7 @@ Tags: newsletter, email, welcome email, post notification, autoresponder, signup
Requires at least: 4.6
Tested up to: 4.8
Requires PHP: 5.3
Stable tag: 3.0.2
Stable tag: 3.1.0
License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html
@ -114,6 +114,42 @@ Stop by our [support site](https://www.mailpoet.com/support).
== Changelog ==
= 3.1.0 - 2017-11-07 =
* Added: a method to create a new list via our public API;
* Fixed: javascript files are loaded with a dependency on jquery; Thanks George!
* Fixed: WP users sync no longer chokes on NULL values for first/last names. Thanks @cartpauj!
* Fixed: superadmin users on Multisite installations can always access MailPoet on subsites. Thanks Ryan!
= 3.0.9 - 2017-10-31 =
* Improved: search forms in listings ignore preceding and trailing whitespace;
* Fixed: tags aren't shown within categories for automated latest content posts anymore. Thanks Gregor!
* Fixed: our subscription form no longer conflicts with themes/plugins that use jQuery Serialize Object function. Thanks Albert!
= 3.0.8 - 2017-10-24 =
* Improved: unsubscribe link now works in browser and email previews;
* Improved: relative URLs in images are replaced by absolute URLs. Thanks Xavier!
* Fixed: default email templates to be translated into your language. Thanks Webteam!
* Fixed: subscription form's "required field" validation message is now translatable. Thanks, Frank!
* Fixed: plugin doesn't fail to activate when WP users table contains custom columns. Thanks Brian!
= 3.0.7 - 2017-10-17 =
* Improved: subscribing from the same IP address is progressively throttled. Thanks Suyog Palav, Piyush Kumar and Bits of Freedom!
* Fixed: WordPress users without an email address will not be added as subscribers;
* Fixed: bug asking subscribers to leave the first field empty in MailPoet subscription forms;
* Fixed: plugin no longer fails to activate on sites when certain user roles do not exist. Thanks to all who reported this!
= 3.0.6 - 2017-10-10 =
* Fixed: subscription forms to not throw form validation engine errors;
= 3.0.5 - 2017-10-10 =
* Added: images can now be aligned left, center or right in email designer;
= 3.0.4 - 2017-10-05 =
* Fixed: dividers and spacers' height can be changed on mouse drag again;
= 3.0.3 - 2017-10-03 =
* Fixed: mixed collation error in WordPress user synchronization. Thanks Chris, Till, Robin, Robero, @Seph, @kaiwen and others for the reports!
= 3.0.2 - 2017-10-03 =
* Improved: plugin capabilities can be managed with Members plugin;
* Improved: removes unsightly horizontal scrollbar in some parts of the newsletter editor;

View File

@ -25,6 +25,7 @@ $models = array(
'Subscriber',
'SubscriberCustomField',
'SubscriberSegment',
'SubscriberIP',
'StatisticsOpens',
'StatisticsClicks',
'StatisticsNewsletters',

File diff suppressed because one or more lines are too long

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