Compare commits

...

91 Commits

Author SHA1 Message Date
14810a22b5 Bump up release version to 3.0.0-rc.1.0.4 2017-08-22 16:26:48 +03:00
c12752403f Fix build 2017-08-22 13:40:11 +02:00
d3ff174e9f Fix inability to deactivate MSS [MAILPOET-1058] 2017-08-22 13:35:51 +02:00
03df7e723c Merge pull request #1051 from mailpoet/composer-fix
Fix composer.lock [PREMIUM-35]
2017-08-22 12:24:40 +03:00
6c8fe8413a Merge pull request #1047 from mailpoet/eslint-assignment
Eslint assignment [MAILPOET-1033]
2017-08-22 09:59:35 +02:00
89b0b51980 Merge pull request #1050 from mailpoet/new-poll
New poll [MAILPOET-1042]
2017-08-21 11:57:26 -04:00
fa1ab733f8 Fix composer.lock
[PREMIUM-35]
2017-08-21 16:34:43 +02:00
127022645e Revert "Update composer.lock"
This reverts commit b1d26b8cee.
2017-08-21 13:39:15 +02:00
b1d26b8cee Update composer.lock
[PREMIUM-35]
2017-08-21 13:26:13 +02:00
f07b90adde Merge pull request #1049 from mailpoet/safari-unsafe
This is a non-secure form. [MAILPOET-1063]
2017-08-21 13:31:50 +03:00
b3884d06a8 New poll
[MAILPOET-1042]
2017-08-21 11:02:49 +02:00
abf1d817f4 Remove form action
[MAILPOET-1063]
2017-08-21 10:37:13 +02:00
c7b7b0abad Merge pull request #1011 from mailpoet/acceptance-tests
Initial acceptance testing setup [MAILPOET-997]
2017-08-17 19:28:27 -04:00
8540e5eea9 Merge pull request #1017 from mailpoet/rerender-newsletter
Rerender newsletter [MAILPOET-675]
2017-08-17 16:19:16 +03:00
09ed3d4fa6 refactoring the code 2017-08-17 12:16:40 +00:00
b96dc8b3f7 Merge pull request #1048 from mailpoet/migration-fix
Skip migration on empty db
2017-08-17 14:04:47 +03:00
0a4dc3eb38 Skip all migration on empty db 2017-08-17 12:58:33 +02:00
a78af28943 Track newsletter UI events with MixPanel
[MAILPOET-999]
2017-08-17 12:45:12 +02:00
f035d12aaf Skip migration on empty db 2017-08-17 11:55:51 +02:00
6353075f1e Don't delete vendor
[MAILPOET-997]
2017-08-17 08:13:11 +02:00
6c91ca9d31 Reinstall vendor
[MAILPOET-997]
2017-08-17 08:13:11 +02:00
6f8634570c Replace repo directory
[MAILPOET-997]
2017-08-17 08:13:11 +02:00
0efcfad3d1 Increase docker compose timeout
[MAILPOET-997]
2017-08-17 08:13:11 +02:00
5d7b54ab22 Add circle ci configuration
[MAILPOET-997]
2017-08-17 08:13:11 +02:00
ad1f6e2a8e Fix caching problem
[MAILPOET-997]
2017-08-17 08:13:11 +02:00
d844b7e47f Initial acceptance testing setup
[MAILPOET-997]
2017-08-17 08:13:10 +02:00
36d4e3eb15 Merge pull request #1034 from mailpoet/ui-help-tooltips
Ui help tooltips [MAILPOET-976]
2017-08-16 19:23:13 -04:00
853f686dde List and scheduling inputs are disabled instead of being hidden 2017-08-16 14:44:42 +00:00
d17486bac4 Merge pull request #1046 from mailpoet/spambot-forms
Add honeypot field for spambot [MAILPOET-1014]
2017-08-16 16:47:21 +03:00
4226684c5a Make tests more obvious
[MAILPOET-1014]
2017-08-16 15:32:07 +02:00
364dd1b2a3 Move field obfuscation into own class
[MAILPOET-1014]
2017-08-16 14:58:31 +02:00
eaf10e8a96 Fix no-param-reassign in tests
[MAILPOET-1033]
2017-08-16 12:34:59 +02:00
bac494ac0d Fix no-multi-assign in tests
[MAILPOET-1033]
2017-08-16 12:25:15 +02:00
acd2b9f51e Fix no-param-reassign in ES5
[MAILPOET-1033]
2017-08-16 12:22:56 +02:00
27c6fa5ff4 Fix no-multi-assign in ES5
[MAILPOET-1033]
2017-08-16 10:44:33 +02:00
89b51b6215 Fix no-cond-assign in ES5
[MAILPOET-1033]
2017-08-16 10:39:43 +02:00
7725391eff Fix no-return-assign in ES5
[MAILPOET-1033]
2017-08-16 10:30:11 +02:00
a37117cfa3 Fix no-param-reassign in ES6
[MAILPOET-1033]
2017-08-16 10:25:18 +02:00
856331caa4 Merge pull request #1044 from mailpoet/post_excerpt_hook
Adds a hook to specify custom max post excerpt length [MAILPOET-1056]
2017-08-15 16:45:26 +02:00
9117ae1a27 Fixing more bugs. Lists and scheduling options are hidden when editing a newsletter that is being sent 2017-08-15 14:39:02 +00:00
4aae8d56e5 Tooltip improvements
[MAILPOET-976]
2017-08-15 16:08:30 +02:00
033d527db9 fix some bugs 2017-08-15 12:55:06 +00:00
b2b1f7ff71 tests fixed 2017-08-15 12:55:06 +00:00
de261d6179 Added confirmation when 'edit' is clicked 2017-08-15 12:55:05 +00:00
a587b0a966 Links are not re-hashed when re-rendering the same newsletter 2017-08-15 12:55:05 +00:00
441aa14bcb fix js typo 2017-08-15 12:55:04 +00:00
4b4b5dd556 show 'Resume' button if the Newsletter was paused. 2017-08-15 12:55:04 +00:00
df9ba7e6c8 clearing the sending queue rendered body and subject
The new body and subject would be automatically rendered when
resuming sending
2017-08-15 12:55:04 +00:00
ca4f1c9387 Pause sending queue when editting the newsletter
An ajax request is sent to pause the sending queue when the editor
is displayed. If the newsletter is still a draft or has been already
sent; the error reponse is simply ignored. Otherwise a notice is
displayed specifying that the Email sending has been paused.
2017-08-15 12:55:03 +00:00
8c151d2d11 Fix failling test
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
78fb9ba46f Fix "Unexpected trailing comma"
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
3a0669e1a2 Fix react/no-danger eslint problem
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
c466e53681 Add tooltip to image heading
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
d02aed870e Add tooltip to image full width
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
fad7ff0018 Add tooltip to preheader
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
84a3f98725 Add tooltip to subject line
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
1c3e968ec4 Add tooltip to editor styles
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
c090a8260b Add tooltip to editor previw
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
65726de7de Add tooltip to send
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
33fe302f0d Add tooltip to import a template
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
2d702dd5d3 Add simple tooltip to help
[MAILPOET-976]
2017-08-15 14:49:22 +02:00
18f208cf47 Add honeypot field for spambot
[MAILPOET-1014]
2017-08-15 14:33:45 +02:00
f7b1016e63 Release 3.0.0-rc.1.0.3 2017-08-15 11:35:44 +02:00
223fedba72 Update vendor dependencies
Bump down codeception version to 2.2.11 for code coverage compatibility
with php 5.6

[MAILPOET-1049]
2017-08-15 10:12:48 +02:00
bf7e7e414f Adds hook to modify rendered form widget 2017-08-15 09:49:38 +02:00
618d0c0c9d Explicitly sets form target to _self as default 2017-08-15 09:49:38 +02:00
49318791fc Adds hook to modify max post excerpt 2017-08-14 20:55:49 -04:00
a5abdd28e1 Removes unused constructor parameter 2017-08-14 20:55:42 -04:00
70860a676c Merge pull request #1036 from mailpoet/date_shortcode_update
Translates and updates date shortcode to display WP time [MAILPOET-1046]
2017-08-14 13:09:08 +03:00
469e9fd8e1 Update poll to "how would you rate MailPoet's reliability?"
[MAILPOET-1043]
2017-08-14 08:49:44 +02:00
715b48df8d Merge pull request #1040 from mailpoet/preview-popup
Fit newsletter browser preview modal popup to screen [MAILPOET-975]
2017-08-13 11:31:05 -04:00
27ae0a9f16 Merge pull request #1039 from mailpoet/set_time_limit_update
Conditionally uses set_time_limit() when function is not disabled [MAILPOET-1054]
2017-08-11 19:52:15 +02:00
b92329a6b5 Fix popup dimensions to the viewport and show the close button 2017-08-11 17:30:13 +00:00
6fe5b7e0c5 Conditionally uses set_time_limit() when function is not disabled 2017-08-11 12:16:31 -04:00
7e0c500e4f Uses WP's date_i18n() to localize date shortcode 2017-08-10 12:46:27 -04:00
eec35c8ab6 Merge pull request #1037 from mailpoet/build-failures
Break the build when errors happen during build steps [MAILPOET-1052]
2017-08-10 16:35:55 +02:00
4096c4b31b Break the build when errors happen during build steps [MAILPOET-1052] 2017-08-10 14:51:37 +03:00
40cbefd1f4 Uses WP time vs. system time 2017-08-09 18:59:55 -04:00
fb5d43e975 Adds helper method to translate shortcodes
Adds translations to Date shortcode
2017-08-09 18:56:33 -04:00
f35b66b3cf Release 3.0.0-rc.1.0.2 2017-08-08 17:45:04 +03:00
7900e7eb8d Merge pull request #1035 from mailpoet/new-poll
New poll [MAILPOET-1018]
2017-08-08 09:17:41 -04:00
849a24ced7 Merge pull request #1019 from mailpoet/translate
translate the DB error message [MAILPOET-1019]
2017-08-08 13:08:54 +03:00
f7e73b06be added missing argument 2017-08-08 10:02:51 +00:00
52cbb9fcb2 Merge pull request #1032 from mailpoet/css-form
Update form css [MAILPOET-593]
2017-08-08 11:48:09 +02:00
966ec0cb7a New poll
[MAILPOET-1018]
2017-08-03 13:17:37 +02:00
2ff0d40d10 Updates reference to the hook that displays IIS error 2017-08-03 08:14:46 +02:00
bb249ebe09 Removes leftover debug code 2017-08-02 10:24:35 -04:00
5a57029b38 Bumps up release version & updates changelog 2017-08-02 10:23:37 -04:00
84d427cc4c Upate poll url 2017-08-02 11:40:30 +02:00
f044db5745 Update form css
[MAILPOET-593]
2017-08-01 11:28:53 +02:00
9859df98b7 translate the DB error message 2017-07-31 08:53:30 +00:00
132 changed files with 4226 additions and 1580 deletions

View File

@ -55,6 +55,48 @@ jobs:
- store_artifacts:
path: /tmp/fake-mailer/
destination: fake-mailer
acceptance_tests:
working_directory: /home/circleci/mailpoet
machine: true
steps:
- checkout
- run:
name: "Set up virtual host"
command: echo 127.0.0.1 mailpoet.loc | sudo tee -a /etc/hosts
- restore_cache:
key: composer-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }}
- restore_cache:
key: npm-{{ checksum "package.json" }}
- run:
name: "Set up test environment"
command: |
sudo apt-get update
sudo apt-get install circleci-php-5.6.23
sudo rm /usr/bin/php
sudo ln -s /opt/circleci/php/5.6.23/bin/php /usr/bin/php
# Install NodeJS+NPM
curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get install nodejs build-essential
# install plugin dependencies
curl -sS https://getcomposer.org/installer | php
php composer.phar install
./do install
- save_cache:
key: composer-{{ checksum "composer.json" }}-{{ checksum "composer.lock" }}
paths:
- vendor
- save_cache:
key: npm-{{ checksum "package.json" }}
paths:
- node_modules
- run:
name: Run acceptance tests
command: |
docker-compose run codeception --steps --debug -vvv --html --xml
- store_artifacts:
path: ~/mailpoet/tests/acceptance-tests/_output
- store_test_results:
path: ~/mailpoet/tests/acceptance-tests/_output
php7:
working_directory: /home/circleci/mailpoet
docker:
@ -92,3 +134,4 @@ workflows:
jobs:
- qa_js_php5
- php7
- acceptance_tests

View File

@ -33,10 +33,8 @@
"no-useless-return": 0,
"array-callback-return": 0,
"new-cap": 0,
"no-return-assign": 0,
"no-continue": 0,
"no-new": 0,
"no-cond-assign": 0,
"space-unary-ops": 0,
"no-redeclare": 0,
"no-console": 0,
@ -60,7 +58,6 @@
"space-in-parens": 0,
"semi": 0,
"max-len": 0,
"no-multi-assign": 0,
"no-trailing-spaces": 0,
"global-require": 0,
"no-throw-literal": 0,
@ -85,7 +82,6 @@
"no-unused-vars": 0,
"object-shorthand": 0,
"new-parens": 0,
"no-param-reassign": 0,
"keyword-spacing": 0,
"eol-last": 0,
"dot-notation": 0,

View File

@ -80,7 +80,6 @@
"no-sequences": 0,
"no-extra-boolean-cast": 0,
"dot-notation": 0,
"no-param-reassign": 0,
"no-shadow": 0,
"one-var": 0,
"no-alert": 0,

View File

@ -22,7 +22,6 @@
"semi": 0,
"keyword-spacing": 0,
"no-bitwise": 0,
"no-multi-assign": 0,
"newline-per-chained-call": 0,
"no-spaced-func": 0,
"func-call-spacing": 0,
@ -38,7 +37,6 @@
"vars-on-top": 0,
"space-before-blocks": 0,
"object-curly-spacing": 0,
"no-param-reassign": 0,
"one-var-declaration-per-line": 0,
"func-names": 0,
"space-before-function-paren": 0

1
.gitignore vendored
View File

@ -3,7 +3,6 @@ TODO
composer.phar
/vendor
tests/_output/*
tests/acceptance.suite.yml
tests/_support/_generated/*
node_modules
.env

39
Dockerfile Normal file
View File

@ -0,0 +1,39 @@
FROM php:5.6-cli
ENV COMPOSER_ALLOW_SUPERUSER=1
RUN apt-get update && \
apt-get -y install \
git \
zlib1g-dev \
libssl-dev \
mysql-client \
sudo less \
--no-install-recommends && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
docker-php-ext-install bcmath zip mysqli pdo pdo_mysql && \
echo "date.timezone = UTC" >> /usr/local/etc/php/php.ini && \
curl -sS https://getcomposer.org/installer | php -- \
--filename=composer \
--install-dir=/usr/local/bin && \
composer global require --optimize-autoloader "hirak/prestissimo" && \
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && \
chmod +x wp-cli.phar && \
mv wp-cli.phar /usr/local/bin/wp
# Prepare application
WORKDIR /repo
# Install vendor
COPY ./composer.json /repo/composer.json
# Add source-code
COPY . /repo
WORKDIR /wp-core/wp-content/plugins/mailpoet
ENV WP_TEST_PATH=/wp-core
ADD docker-entrypoint.sh /
RUN ["chmod", "+x", "/docker-entrypoint.sh"]

View File

@ -180,3 +180,21 @@ Run 'svn copy ...' to tag the release
It's quite literal: you can review the changes to be pushed and if you're satisfied, run the suggested command to finish the release publishing process.
If you're confident, execute `./do publish --force` and your release will be published to the remote SVN repository without manual intervention (automatically). For easier authentication you might want to set `WP_SVN_USERNAME` and `WP_SVN_PASSWORD` environment variables.
# Acceptance testing
We are using Gravity Flow plugin's setup as an example for our acceptance test suite: https://www.stevenhenty.com/learn-acceptance-testing-deeply/
From the article above:
_Windows users only: enable hard drive sharing in the Docker settings._
The browser runs in a docker container. You can use a VNC client to watch the test run, follow instructions in official
repo: https://github.com/SeleniumHQ/docker-selenium
If youre on a Mac, you can open vnc://localhost:5900 in Safari to watch the tests running in Chrome. If youre on Windows, youll need a VNC client. Password: secret.
To run tests:
```sh
$ ./do test:acceptance
```

View File

@ -154,9 +154,9 @@ class RoboFile extends \Robo\Tasks {
function testUnit($opts=['file' => null, 'xml' => false]) {
$this->loadEnv();
$this->_exec('vendor/bin/codecept build');
$this->_exec('vendor/bin/codecept build -c codeception.unit.yml');
$command = 'vendor/bin/codecept run unit -f '.(($opts['file']) ? $opts['file'] : '');
$command = 'vendor/bin/codecept run unit -c codeception.unit.yml -f '.(($opts['file']) ? $opts['file'] : '');
if($opts['xml']) {
$command .= ' --xml';
@ -166,9 +166,9 @@ class RoboFile extends \Robo\Tasks {
function testCoverage($opts=['file' => null, 'xml' => false]) {
$this->loadEnv();
$this->_exec('vendor/bin/codecept build');
$this->_exec('vendor/bin/codecept build -c codeception.unit.yml');
$command = join(' ', array(
'vendor/bin/codecept run',
'vendor/bin/codecept run -c codeception.unit.yml ',
(($opts['file']) ? $opts['file'] : ''),
'--coverage',
($opts['xml']) ? '--coverage-xml' : '--coverage-html'
@ -201,9 +201,9 @@ class RoboFile extends \Robo\Tasks {
function testDebug($opts=['file' => null, 'xml' => false]) {
$this->loadEnv();
$this->_exec('vendor/bin/codecept build');
$this->_exec('vendor/bin/codecept build -c codeception.unit.yml');
$command = 'vendor/bin/codecept run unit --debug -f '.(($opts['file']) ? $opts['file'] : '');
$command = 'vendor/bin/codecept run unit -c codeception.unit.yml --debug -f '.(($opts['file']) ? $opts['file'] : '');
if($opts['xml']) {
$command .= ' --xml';
@ -211,10 +211,14 @@ class RoboFile extends \Robo\Tasks {
return $this->_exec($command);
}
function testAcceptance() {
return $this->_exec('COMPOSE_HTTP_TIMEOUT=200 docker-compose run codeception --steps --debug -vvv');
}
function testFailed() {
$this->loadEnv();
$this->_exec('vendor/bin/codecept build');
return $this->_exec('vendor/bin/codecept run -g failed');
$this->_exec('vendor/bin/codecept build -c codeception.unit.yml');
return $this->_exec('vendor/bin/codecept run -c codeception.unit.yml -g failed');
}
function qa() {
@ -295,10 +299,6 @@ class RoboFile extends \Robo\Tasks {
$plugin_dist_name = explode('-', $plugin_dist_name);
$plugin_dist_name = $plugin_dist_name[0];
$plugin_dist_file = $plugin_dist_name . '.zip';
$this->say('name: '. $plugin_dist_name);
return;
$this->say('Publishing version: ' . $plugin_version);
// Sanity checks

View File

@ -45,7 +45,6 @@ body.mailpoet_modal_opened
position: absolute
z-index: 25
top: 48px
padding-bottom: 48px
margin: 0
.mailpoet_popup_wrapper
@ -54,6 +53,7 @@ body.mailpoet_modal_opened
position: relative
width: 100%
z-index: 0
height: 96%
.mailpoet_overlay_hidden .mailpoet_popup_wrapper
border: 1px solid #333
@ -75,6 +75,7 @@ body.mailpoet_modal_opened
.mailpoet_popup_body
padding: 10px 10px 10px 10px
height: 92%
// modal panel
#mailpoet_modal_overlay.mailpoet_panel_overlay

View File

@ -179,6 +179,28 @@ select.mailpoet_font-size
width: 100%
box-sizing: border-box
.tooltip-help-designer-subject-line div, .tooltip-help-designer-preheader div
z-index: 100001
.tooltip-help-send-preview
position: absolute
right: 15px
.tooltip-help-designer-ideal-width
color: #656565
text-transform: none
margin-left: 5px
font-weight: normal
.tooltip-help-designer-styles
position: absolute
top: 40px
.tooltip-help-designer-full-width .dashicons
line-height 34px
.tooltip-help-designer-full-width span
line-height 1.4em
.mailpoet_button_primary
border-color: $button-primary-border-color
background-color: $button-primary-background-color

View File

@ -127,9 +127,6 @@ body
background-color: $primary-background-color
border: 1px solid $content-border-color
#mailpoet_modal_close
display: none
.wrap > .mailpoet_notice,
.notice
.update-nag

View File

@ -54,6 +54,9 @@
#mailpoet_mta_activate
visibility hidden
.tooltip.dashicons.dashicons-editor-help
line-height: 1.4
ul.sending-method-benefits
list-style-type: none
margin-bottom: 2em

View File

@ -12,7 +12,8 @@ function requestFailed(errorMessage, xhr) {
};
}
define('ajax', ['mailpoet', 'jquery', 'underscore'], function(MailPoet, jQuery, _) {
define('ajax', ['mailpoet', 'jquery', 'underscore'], function(mp, jQuery, _) {
var MailPoet = mp;
MailPoet.Ajax = {
version: 0.5,

View File

@ -24,7 +24,8 @@ function track(name, data){
window.mixpanel.track(name, data);
}
function exportMixpanel(MailPoet) {
function exportMixpanel(mp) {
var MailPoet = mp;
MailPoet.forceTrackEvent = track;
if (window.mailpoet_analytics_enabled) {
@ -61,7 +62,8 @@ function cacheEvent(forced, name, data) {
define(
['mailpoet', 'underscore'],
function(MailPoet, _) {
function(mp, _) {
var MailPoet = mp;
MailPoet.trackEvent = _.partial(cacheEvent, false);
MailPoet.forceTrackEvent = _.partial(cacheEvent, true);

View File

@ -4,12 +4,14 @@ define('date',
'jquery',
'moment'
], function(
MailPoet,
mp,
jQuery,
Moment
) {
'use strict';
var MailPoet = mp;
MailPoet.Date = {
version: 0.1,
options: {},
@ -17,8 +19,8 @@ define('date',
offset: 0,
format: 'F, d Y H:i:s'
},
init: function(options) {
options = options || {};
init: function (opts) {
var options = opts || {};
// set UTC offset
if (
@ -39,16 +41,16 @@ define('date',
return this;
},
format: function(date, options) {
options = options || {};
format: function(date, opts) {
var options = opts || {};
this.init(options);
var date = Moment(date, this.convertFormat(options.parseFormat));
if (options.offset === 0) date = date.utc();
return date.format(this.convertFormat(this.options.format));
var momentDate = Moment(date, this.convertFormat(options.parseFormat));
if (options.offset === 0) momentDate = momentDate.utc();
return momentDate.format(this.convertFormat(this.options.format));
},
toDate: function(date, options) {
options = options || {};
toDate: function(date, opts) {
var options = opts || {};
this.init(options);
return Moment(date, this.convertFormat(options.parseFormat)).toDate();
@ -143,7 +145,8 @@ define('date',
var convertedFormat = [];
var escapeToken = false;
for(var index = 0, token = ''; token = format.charAt(index); index++){
for(var index = 0, token = ''; format.charAt(index); index += 1){
token = format.charAt(index);
if (escapeToken === true) {
convertedFormat.push('['+token+']');
escapeToken = false;

View File

@ -28,12 +28,13 @@ define([
}
let field = false;
let dataField = data.field;
if(data.field['field'] !== undefined) {
data.field = jQuery.merge(data.field, data.field.field);
dataField = jQuery.merge(dataField, data.field.field);
}
switch(data.field.type) {
switch(dataField.type) {
case 'text':
field = (<FormFieldText {...data} />);
break;

View File

@ -180,6 +180,7 @@ define([
<select
id={ this.props.field.id || this.props.field.name }
ref="select"
disabled={this.props.field.disabled}
data-placeholder={ this.props.field.placeholder }
multiple={ this.props.field.multiple }
defaultValue={ this.getSelectedValues() }

View File

@ -11,12 +11,14 @@ Object.extend(document, (function() {
var cache = Event.cacheDelegated;
function getCacheForSelector(selector) {
return cache[selector] = cache[selector] || {};
cache[selector] = cache[selector] || {};
return cache[selector];
}
function getWrappersForSelector(selector, eventName) {
var c = getCacheForSelector(selector);
return c[eventName] = c[eventName] || [];
c[eventName] = c[eventName] || [];
return c[eventName];
}
function findWrapper(selector, eventName, handler) {
@ -79,8 +81,8 @@ Object.extend(document, (function() {
})());
var Observable = (function() {
function getEventName(name, namespace) {
name = name.substring(2);
function getEventName(nameA, namespace) {
var name = nameA.substring(2);
if(namespace) name = namespace + ':' + name;
return name.underscore().split('_').join(':');
}
@ -574,7 +576,8 @@ var WysijaForm = {
WysijaForm.locks.showingTools = false;
},
instances: {},
get: function(element, type) {
get: function(element, typ) {
var type = typ;
if(type === undefined) type = 'block';
// identify element
var id = element.identify();
@ -893,7 +896,8 @@ WysijaForm.Block = Class.create({
});
/* Invoked on item dropped */
WysijaForm.Block.create = function(block, target) {
WysijaForm.Block.create = function(createBlock, target) {
var block = createBlock;
if($('form_template_' + block.type) === null) {
return false;
}
@ -1050,7 +1054,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;
var console = window.console = {};
window.console = {};
var console = {};
while(length--) {
console[methods[length]] = noop;
}

View File

@ -125,12 +125,13 @@ define('handlebars_helpers', ['handlebars'], function(Handlebars) {
* @return {String} The truncated string.
*/
Handlebars.registerHelper('ellipsis', function (str, limit, append) {
if (append === undefined) {
append = '';
var strAppend = append;
if (strAppend === undefined) {
strAppend = '';
}
var sanitized = str.replace(/(<([^>]+)>)/g, '');
if (sanitized.length > limit) {
return sanitized.substr(0, limit - append.length) + append;
return sanitized.substr(0, limit - strAppend.length) + strAppend;
} else {
return sanitized;
}

View File

@ -0,0 +1,21 @@
define('helpTooltip', ['mailpoet', 'react', 'react-dom', 'help-tooltip.jsx'],
function (mp, React, ReactDOM, TooltipComponent) {
'use strict';
var MailPoet = mp;
MailPoet.helpTooltip = {
show: function (domContainerNode, opts) {
ReactDOM.render(React.createElement(
TooltipComponent, {
tooltip: opts.tooltip,
tooltipId: opts.tooltipId,
place: opts.place
}
), domContainerNode);
}
};
}
);

View File

@ -0,0 +1,63 @@
import React from 'react';
import ReactTooltip from 'react-tooltip';
import ReactHtmlParser from 'react-html-parser';
function Tooltip(props) {
let tooltipId = props.tooltipId;
let tooltip = props.tooltip;
// tooltip ID must be unique, defaults to tooltip text
if(!props.tooltipId && typeof props.tooltip === "string") {
tooltipId = props.tooltip;
}
if(typeof props.tooltip === "string") {
tooltip = (<span
style={{
pointerEvents: "all",
maxWidth: "400",
display: "inline-block",
}}
>
{ReactHtmlParser(props.tooltip)}
</span>);
}
return (
<span className={props.className}>
<span
style={{
cursor: "pointer",
}}
className="tooltip dashicons dashicons-editor-help"
data-event="click"
data-tip
data-for={tooltipId}
>
</span>
<ReactTooltip
globalEventOff="click"
multiline={true}
id={tooltipId}
efect="solid"
place={props.place}
>
{tooltip}
</ReactTooltip>
</span>
);
}
Tooltip.propTypes = {
tooltipId: React.PropTypes.string,
tooltip: React.PropTypes.node.isRequired,
place: React.PropTypes.string,
className: React.PropTypes.string,
};
Tooltip.defaultProps = {
tooltipId: undefined,
place: undefined,
className: undefined,
};
module.exports = Tooltip;

View File

@ -2,9 +2,10 @@ define('i18n',
[
'mailpoet'
], function(
MailPoet
mp
) {
'use strict';
var MailPoet = mp;
var translations = {};

View File

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

View File

@ -3,8 +3,9 @@ define(
'jquery'
],
function(
$
jQuery
) {
var $ = jQuery;
// Combination of jQuery.deparam and jQuery.serializeObject by Ben Alman.
/*!
* jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010
@ -74,9 +75,10 @@ define(
// * Rinse & repeat.
for ( ; i <= keys_last; i++ ) {
key = keys[i] === '' ? cur.length : keys[i];
cur = cur[key] = i < keys_last
cur[key] = i < keys_last
? cur[key] || ( keys[i+1] && isNaN( keys[i+1] ) ? {} : [] )
: val;
cur = cur[key];
}
} else {

View File

@ -10,8 +10,9 @@ const ListingHeader = React.createClass({
},
render: function () {
const columns = this.props.columns.map((column, index) => {
column.is_primary = (index === 0);
column.sorted = (this.props.sort_by === column.name)
const renderColumn = column;
renderColumn.is_primary = (index === 0);
renderColumn.sorted = (this.props.sort_by === column.name)
? this.props.sort_order
: 'desc';
return (
@ -19,7 +20,7 @@ const ListingHeader = React.createClass({
onSort={this.props.onSort}
sort_by={this.props.sort_by}
key={ 'column-' + index }
column={column} />
column={renderColumn} />
);
});

View File

@ -260,8 +260,9 @@ const ListingItems = React.createClass({
</tr>
{this.props.items.map((item, index) => {
item.id = parseInt(item.id, 10);
item.selected = (this.props.selected_ids.indexOf(item.id) !== -1);
const renderItem = item;
renderItem.id = parseInt(item.id, 10);
renderItem.selected = (this.props.selected_ids.indexOf(renderItem.id) !== -1);
return (
<ListingItem
@ -276,8 +277,8 @@ const ListingItems = React.createClass({
is_selectable={ this.props.is_selectable }
item_actions={ this.props.item_actions }
group={ this.props.group }
key={ `item-${item.id}-${index}` }
item={ item } />
key={ `item-${renderItem.id}-${index}` }
item={ renderItem } />
);
})}
</tbody>
@ -418,16 +419,17 @@ const Listing = React.createClass({
}
},
setBaseUrlParams: function (base_url) {
if (base_url.indexOf(':') !== -1) {
let ret = base_url;
if (ret.indexOf(':') !== -1) {
const params = this.getParams();
Object.keys(params).map((key) => {
if (base_url.indexOf(':'+key) !== -1) {
base_url = base_url.replace(':'+key, params[key]);
if (ret.indexOf(':'+key) !== -1) {
ret = ret.replace(':'+key, params[key]);
}
});
}
return base_url;
return ret;
},
componentDidMount: function () {
if (this.isMounted()) {

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
define('mp2migrator', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
define('mp2migrator', ['mailpoet', 'jquery'], function(mp, jQuery) {
'use strict';
var MailPoet = mp;
MailPoet.MP2Migrator = {
fatal_error: '',
@ -28,7 +29,8 @@ define('mp2migrator', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
cache: false
}).done(function (result) {
jQuery("#logger").html('');
result.split("\n").forEach(function (row) {
result.split("\n").forEach(function (resultRow) {
var row = resultRow;
if(row.substr(0, 7) === '[ERROR]' || row.substr(0, 9) === '[WARNING]' || row === MailPoet.I18n.t('import_stopped_by_user')) {
row = '<span class="error_msg">' + row + '</span>'; // Mark the errors in red
}

View File

@ -6,7 +6,8 @@ define([
'underscore',
'handlebars',
'handlebars_helpers'
], function(Backbone, Marionette, Radio, jQuery, _, Handlebars) {
], function(Backbone, Marionette, BackboneRadio, jQuery, _, Handlebars) {
var Radio = BackboneRadio;
var AppView = Marionette.View.extend({
el: '#mailpoet_editor',
@ -28,7 +29,9 @@ define([
},
getChannel: function(channel) {
if (channel === undefined) channel = 'global';
if (channel === undefined) {
return Radio.channel('global');
}
return Radio.channel(channel);
}
});

View File

@ -6,8 +6,8 @@
*/
define([
'backbone.marionette'
], function(Marionette) {
], function(BackboneMarionette) {
var Marionette = BackboneMarionette;
var BehaviorsLookup = {};
Marionette.Behaviors.behaviorsLookup = function() {
return BehaviorsLookup;

View File

@ -9,8 +9,9 @@ define([
'mailpoet',
'spectrum'
], function(Marionette, BehaviorsLookup, MailPoet, Spectrum) {
var BL = BehaviorsLookup;
BehaviorsLookup.ColorPickerBehavior = Marionette.Behavior.extend({
BL.ColorPickerBehavior = Marionette.Behavior.extend({
onRender: function() {
this.view.$('.mailpoet_color').spectrum({
clickoutFiresChange: true,

View File

@ -11,7 +11,8 @@ define([
'jquery',
'newsletter_editor/behaviors/BehaviorsLookup',
'interact'
], function(Marionette, _, jQuery, BehaviorsLookup, interact) {
], function(Marionette, _, jQuery, BL, interact) {
var BehaviorsLookup = BL;
BehaviorsLookup.ContainerDropZoneBehavior = Marionette.Behavior.extend({
defaults: {
@ -268,7 +269,7 @@ define([
// 2. Remove visual markings of drop position visualization
this.view.$('.mailpoet_drop_marker').remove();
},
getDropPosition: function(eventX, eventY, unsafe) {
getDropPosition: function(eventX, eventY, is_unsafe) {
var SPECIAL_AREA_INSERTION_WIDTH = 0.00, // Disable special insertion. Default: 0.3
element = this.view.$el,
@ -290,7 +291,7 @@ define([
insertionType, index, position, indexAndPosition;
unsafe = !!unsafe;
unsafe = !!is_unsafe;
if (this.getCollection().length === 0) {
return {

View File

@ -11,8 +11,9 @@ define([
'newsletter_editor/behaviors/BehaviorsLookup',
'interact'
], function(Marionette, _, jQuery, BehaviorsLookup, interact) {
var BL = BehaviorsLookup;
BehaviorsLookup.DraggableBehavior = Marionette.Behavior.extend({
BL.DraggableBehavior = Marionette.Behavior.extend({
defaults: {
cloneOriginal: false,
hideOriginal: false,
@ -46,7 +47,8 @@ define([
// Scroll when dragging near edges of a window
autoScroll: true,
onstart: function(event) {
onstart: function(startEvent) {
var event = startEvent;
if (that.options.cloneOriginal === true) {
// Use substitution instead of a clone
@ -89,9 +91,8 @@ define([
y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
// translate the element
target.style.webkitTransform =
target.style.transform =
'translate(' + x + 'px, ' + y + 'px)';
target.style.transform = 'translate(' + x + 'px, ' + y + 'px)';
target.style.webkitTransform = target.style.transform;
// update the posiion attributes
target.setAttribute('data-x', x);
@ -99,7 +100,8 @@ define([
},
onend: function (event) {
var target = event.target;
target.style.webkitTransform = target.style.transform = '';
target.style.transform = '';
target.style.webkitTransform = target.style.transform;
target.removeAttribute('data-x');
target.removeAttribute('data-y');
jQuery(event.interaction.element).addClass('mailpoet_droppable_active');
@ -129,7 +131,8 @@ define([
} else {
interactable.getDropModel = this.view.getDropFunc();
}
interactable.onDrop = function(options) {
interactable.onDrop = function(opts) {
var options = opts;
if (_.isObject(options)) {
// Inject Draggable behavior if possible
options.dragBehavior = that;

View File

@ -7,8 +7,9 @@ define([
'backbone.marionette',
'newsletter_editor/behaviors/BehaviorsLookup'
], function(Marionette, BehaviorsLookup) {
var BL = BehaviorsLookup;
BehaviorsLookup.HighlightEditingBehavior = Marionette.Behavior.extend({
BL.HighlightEditingBehavior = Marionette.Behavior.extend({
modelEvents: {
'startEditing': 'enableHighlight',
'stopEditing': 'disableHighlight'

View File

@ -8,8 +8,9 @@ define([
'newsletter_editor/behaviors/BehaviorsLookup',
'interact'
], function(Marionette, BehaviorsLookup, interact) {
var BL = BehaviorsLookup;
BehaviorsLookup.ResizableBehavior = Marionette.Behavior.extend({
BL.ResizableBehavior = Marionette.Behavior.extend({
defaults: {
elementSelector: null,
resizeHandleSelector: true, // true will use edges of the element itself

View File

@ -8,8 +8,9 @@ define([
'jquery',
'newsletter_editor/behaviors/BehaviorsLookup'
], function(Marionette, jQuery, BehaviorsLookup) {
var BL = BehaviorsLookup;
BehaviorsLookup.ShowSettingsBehavior = Marionette.Behavior.extend({
BL.ShowSettingsBehavior = Marionette.Behavior.extend({
defaults: {
ignoreFrom: '' // selector
},

View File

@ -8,8 +8,9 @@ define([
'underscore',
'newsletter_editor/behaviors/BehaviorsLookup'
], function(Marionette, _, BehaviorsLookup) {
var BL = BehaviorsLookup;
BehaviorsLookup.SortableBehavior = Marionette.Behavior.extend({
BL.SortableBehavior = Marionette.Behavior.extend({
onRender: function() {
var collection = this.view.collection;

View File

@ -8,8 +8,9 @@ define([
'underscore',
'newsletter_editor/behaviors/BehaviorsLookup'
], function(Marionette, _, BehaviorsLookup) {
var BL = BehaviorsLookup;
BehaviorsLookup.TextEditorBehavior = Marionette.Behavior.extend({
BL.TextEditorBehavior = Marionette.Behavior.extend({
defaults: {
selector: '.mailpoet_content',
toolbar1: "bold italic link unlink forecolor mailpoet_shortcodes",

View File

@ -392,8 +392,9 @@ define([
});
App.on('start', function(App, options) {
App._ALCSupervisor = new Module.ALCSupervisor();
App._ALCSupervisor.refresh();
var Application = App;
Application._ALCSupervisor = new Module.ALCSupervisor();
Application._ALCSupervisor.refresh();
});
return Module;

View File

@ -194,8 +194,8 @@ define([
move: true
},
getSettingsView: function() { return Module.BlockSettingsView; },
initialize: function(options) {
options = options || {};
initialize: function(opts) {
var options = opts || {};
if (!_.isUndefined(options.tools)) {
// Make a new block specific tool config object
this.tools = jQuery.extend({}, this.tools, options.tools || {});

View File

@ -4,8 +4,9 @@
define([
'newsletter_editor/App',
'newsletter_editor/blocks/base',
'underscore'
], function(App, BaseBlock, _) {
'underscore',
'mailpoet'
], function(App, BaseBlock, _, MailPoet) {
"use strict";
@ -61,6 +62,16 @@ define([
});
Module.ImageBlockSettingsView = base.BlockSettingsView.extend({
onRender: function() {
MailPoet.helpTooltip.show(document.getElementById('tooltip-designer-full-width'), {
tooltipId: 'tooltip-editor-full-width',
tooltip: MailPoet.I18n.t('helpTooltipDesignerFullWidth')
});
MailPoet.helpTooltip.show(document.getElementById('tooltip-designer-ideal-width'), {
tooltipId: 'tooltip-editor-ideal-width',
tooltip: MailPoet.I18n.t('helpTooltipDesignerIdealWidth')
});
},
getTemplate: function() { return templates.imageBlockSettings; },
events: function() {
return {
@ -266,7 +277,8 @@ define([
},
mainEmbedToolbar: function(toolbar) {
toolbar.view = new wp.media.view.Toolbar.Embed({
var tbar = toolbar;
tbar.view = new wp.media.view.Toolbar.Embed({
controller: this,
text: 'Add images'
});
@ -274,7 +286,7 @@ define([
});
var theFrame = this._mediaManager = new MediaManager({
var theFrame = new MediaManager({
id: 'mailpoet-media-manager',
frame: 'select',
title: 'Select image',
@ -289,8 +301,9 @@ define([
}
}),
that = this;
this._mediaManager = theFrame;
this._mediaManager.on('insert', function() {
this._mediaManager.on('insert', function() {
// Append media manager image selections to Images tab
var selection = theFrame.state().get('selection');
selection.each(function(attachment) {

View File

@ -23,8 +23,8 @@
}
}(this, function(Marionette, Radio, _) {
'use strict';
Marionette.Application.prototype._initChannel = function () {
var MarionetteApplication = Marionette.Application;
MarionetteApplication.prototype._initChannel = function () {
this.channelName = _.result(this, 'channelName') || 'global';
this.channel = _.result(this, 'channel') || Radio.channel(this.channelName);
};

View File

@ -25,11 +25,12 @@ define([
};
App.on('before:start', function(App, options) {
var Application = App;
// Expose config methods globally
App.getConfig = Module.getConfig;
App.setConfig = Module.setConfig;
Application.getConfig = Module.getConfig;
Application.setConfig = Module.setConfig;
App.setConfig(options.config);
Application.setConfig(options.config);
});
return Module;

View File

@ -67,7 +67,8 @@ define([
return _.filter(blocks, predicate);
};
App.on('before:start', function(App, options) {
App.on('before:start', function(Application, options) {
var App = Application;
// Expose block methods globally
App.registerBlockType = Module.registerBlockType;
App.getBlockTypeModel = Module.getBlockTypeModel;
@ -80,7 +81,8 @@ define([
Module.newsletter = new Module.NewsletterModel(_.omit(_.clone(options.newsletter), ['body']));
});
App.on('start', function(App, options) {
App.on('start', function(Application, options) {
var App = Application;
var body = options.newsletter.body;
var content = (_.has(body, 'content')) ? body.content : {};

View File

@ -30,6 +30,15 @@ define([
App.on('start', function(App, options) {
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',
tooltip: MailPoet.I18n.t('helpTooltipDesignerSubjectLine'),
place: "right"
});
MailPoet.helpTooltip.show(document.getElementById('tooltip-designer-preheader'), {
tooltipId: 'tooltip-designer-preheader-ti',
tooltip: MailPoet.I18n.t('helpTooltipDesignerPreheader')
});
});
return Module;

View File

@ -67,9 +67,10 @@ define([
// For getting a promise after triggering save event
Module.saveAndProvidePromise = function(saveResult) {
var result = saveResult;
var promise = Module.save();
if (saveResult !== undefined) {
saveResult.promise = promise;
result.promise = promise;
}
};
@ -140,6 +141,9 @@ define([
);
FileSaver.saveAs(blob, 'template.json');
MailPoet.trackEvent('Editor > Template exported', {
'MailPoet Free version': window.mailpoet_version
});
});
};
@ -225,6 +229,9 @@ define([
scroll: true
}
);
MailPoet.trackEvent('Editor > Template saved', {
'MailPoet Free version': window.mailpoet_version
});
}).fail(function() {
MailPoet.Notice.error(
MailPoet.I18n.t('templateSaveFailed'),
@ -331,10 +338,10 @@ define([
Module.beforeExitWithUnsavedChanges = function(e) {
if (saveTimeout) {
var message = MailPoet.I18n.t('unsavedChangesWillBeLost');
e = e || window.event;
var event = e || window.event;
if (e) {
e.returnValue = message;
if (event) {
event.returnValue = message;
}
return message;
@ -342,12 +349,13 @@ define([
};
App.on('before:start', function(App, options) {
App.save = Module.saveAndProvidePromise;
App.getChannel().on('autoSave', Module.autoSave);
var Application = App;
Application.save = Module.saveAndProvidePromise;
Application.getChannel().on('autoSave', Module.autoSave);
window.onbeforeunload = Module.beforeExitWithUnsavedChanges;
App.getChannel().on('save', function(saveResult) { App.save(saveResult); });
Application.getChannel().on('save', function(saveResult) { Application.save(saveResult); });
});
App.on('start', function(App, options) {

View File

@ -272,16 +272,23 @@ define([
});
var view = this.previewView.render();
this.previewView.$el.css('height', '100%');
MailPoet.Modal.popup({
template: '',
element: this.previewView.$el,
width: '95%',
height: '94%',
title: MailPoet.I18n.t('newsletterPreview'),
onCancel: function() {
this.previewView.destroy();
this.previewView = null;
}.bind(this)
});
MailPoet.trackEvent('Editor > Browser Preview', {
'MailPoet Free version': window.mailpoet_version
});
}.bind(this)).fail(function(response) {
if (response.errors.length > 0) {
MailPoet.Notice.error(
@ -323,7 +330,12 @@ define([
}).done(function(response) {
MailPoet.Notice.success(
MailPoet.I18n.t('newsletterPreviewSent'),
{ scroll: true });
{ scroll: true }
);
MailPoet.trackEvent('Editor > Preview sent', {
'MailPoet Free version': window.mailpoet_version,
'Domain name': data.subscriber.substring(data.subscriber.indexOf('@') + 1)
});
}).fail(function(response) {
if (response.errors.length > 0) {
MailPoet.Notice.error(
@ -340,8 +352,10 @@ define([
getTemplate: function() { return templates.newsletterPreview; },
initialize: function(options) {
this.previewUrl = options.previewUrl;
this.width = App.getConfig().get('newsletterPreview.width');
this.height = App.getConfig().get('newsletterPreview.height')
this.width = '100%';
this.height = '100%';
// this.width = App.getConfig().get('newsletterPreview.width');
// this.height = App.getConfig().get('newsletterPreview.height')
},
templateContext: function() {
return {
@ -353,10 +367,11 @@ define([
});
App.on('before:start', function(App, options) {
App.registerWidget = Module.registerWidget;
App.getWidgets = Module.getWidgets;
App.registerLayoutWidget = Module.registerLayoutWidget;
App.getLayoutWidgets = Module.getLayoutWidgets;
var Application = App;
Application.registerWidget = Module.registerWidget;
Application.getWidgets = Module.getWidgets;
Application.registerLayoutWidget = Module.registerLayoutWidget;
Application.getLayoutWidgets = Module.getLayoutWidgets;
});
App.on('start', function(App, options) {
@ -364,6 +379,16 @@ define([
sidebarView = new SidebarView();
App._appView.showChildView('sidebarRegion', sidebarView);
MailPoet.helpTooltip.show(document.getElementById('tooltip-send-preview'), {
tooltipId: 'tooltip-editor-send-preview',
tooltip: MailPoet.I18n.t('helpTooltipSendPreview')
});
MailPoet.helpTooltip.show(document.getElementById('tooltip-designer-styles'), {
tooltipId: 'tooltip-editor-designer-styles',
tooltip: MailPoet.I18n.t('helpTooltipDesignerStyles')
});
});
return Module;

View File

@ -69,10 +69,11 @@ define([
};
App.on('before:start', function(App, options) {
var Application = App;
// Expose style methods to global application
App.getGlobalStyles = Module.getGlobalStyles;
App.setGlobalStyles = Module.setGlobalStyles;
App.getAvailableStyles = Module.getAvailableStyles;
Application.getGlobalStyles = Module.getGlobalStyles;
Application.setGlobalStyles = Module.setGlobalStyles;
Application.getAvailableStyles = Module.getAvailableStyles;
var body = options.newsletter.body;
var globalStyles = (_.has(body, 'globalStyles')) ? body.globalStyles : {};

View File

@ -145,13 +145,14 @@ const _QueueMixin = {
const _StatisticsMixin = {
renderStatistics: function (newsletter, is_sent, current_time) {
if (is_sent === undefined) {
let sent = is_sent;
if (sent === undefined) {
// condition for standard and post notification listings
is_sent = newsletter.statistics
sent = newsletter.statistics
&& newsletter.queue
&& newsletter.queue.status !== 'scheduled';
}
if (!is_sent) {
if (!sent) {
return (
<span>{MailPoet.I18n.t('notSentYet')}</span>
);

View File

@ -97,6 +97,17 @@ const bulk_actions = [
},
];
const confirmEdit = (newsletter) => {
if(
!newsletter.queue
|| newsletter.status != 'sending'
|| newsletter.queue.status !== null
|| window.confirm(MailPoet.I18n.t('confirmEdit'))
) {
window.location.href = `?page=mailpoet-newsletter-editor&id=${ newsletter.id }`;
}
};
let newsletter_actions = [
{
name: 'view',
@ -110,13 +121,8 @@ let newsletter_actions = [
},
{
name: 'edit',
link: function (newsletter) {
return (
<a href={ `?page=mailpoet-newsletter-editor&id=${ newsletter.id }` }>
{MailPoet.I18n.t('edit')}
</a>
);
},
label: MailPoet.I18n.t('edit'),
onClick: confirmEdit,
},
{
name: 'duplicate',
@ -172,7 +178,8 @@ const NewsletterListStandard = React.createClass({
<strong>
<a
className="row-title"
href={ `?page=mailpoet-newsletter-editor&id=${ newsletter.id }` }
href="javascript:;"
onClick={() => confirmEdit(newsletter)}
>{ newsletter.queue.newsletter_rendered_subject || newsletter.subject }</a>
</strong>
{ actions }
@ -198,7 +205,14 @@ const NewsletterListStandard = React.createClass({
return (
<div>
<h1 className="title">
{MailPoet.I18n.t('pageTitle')} <Link className="page-title-action" to="/new">{MailPoet.I18n.t('new')}</Link>
{MailPoet.I18n.t('pageTitle')}
<Link className="page-title-action" to="/new"
onClick={() => MailPoet.trackEvent('Emails > Add New',
{ 'MailPoet Free version': window.mailpoet_version }
)}
>
{MailPoet.I18n.t('new')}
</Link>
</h1>
<ListingTabs tab="standard" />

View File

@ -38,6 +38,9 @@ const ListingTabs = React.createClass({
key={ 'tab-'+index }
className={ tabClasses }
to={ tab.link }
onClick={() => MailPoet.trackEvent(`Tab Emails > ${tab.name} clicked`,
{ 'MailPoet Free version': window.mailpoet_version }
)}
>{ tab.label }</Link>
);
});

View File

@ -9,6 +9,7 @@ define(
'newsletters/send/notification.jsx',
'newsletters/send/welcome.jsx',
'newsletters/breadcrumb.jsx',
'help-tooltip.jsx',
],
(
React,
@ -19,7 +20,8 @@ define(
StandardNewsletterFields,
NotificationNewsletterFields,
WelcomeNewsletterFields,
Breadcrumb
Breadcrumb,
HelpTooltip
) => {
const NewsletterSend = React.createClass({
@ -108,16 +110,25 @@ define(
}).done((response) => {
// redirect to listing based on newsletter type
this.context.router.push(`/${ this.state.item.type || '' }`);
const opts = this.state.item.options;
// display success message depending on newsletter type
if (response.data.type === 'welcome') {
MailPoet.Notice.success(
MailPoet.I18n.t('welcomeEmailActivated')
);
MailPoet.trackEvent('Emails > Welcome email activated', {
'MailPoet Free version': window.mailpoet_version,
'List type': opts.event,
'Delay': opts.afterTimeNumber + ' ' + opts.afterTimeType,
});
} else if (response.data.type === 'notification') {
MailPoet.Notice.success(
MailPoet.I18n.t('postNotificationActivated')
);
MailPoet.trackEvent('Emails > Post notifications activated', {
'MailPoet Free version': window.mailpoet_version,
'Frequency': opts.intervalType,
});
}
}).fail(this._showError);
default:
@ -136,10 +147,18 @@ define(
MailPoet.Notice.success(
MailPoet.I18n.t('newsletterHasBeenScheduled')
);
MailPoet.trackEvent('Emails > Newsletter sent', {
scheduled: true,
'MailPoet Free version': window.mailpoet_version,
});
} else {
MailPoet.Notice.success(
MailPoet.I18n.t('newsletterBeingSent')
);
MailPoet.trackEvent('Emails > Newsletter sent', {
scheduled: false,
'MailPoet Free version': window.mailpoet_version,
});
}
}).fail(this._showError);
}
@ -149,6 +168,40 @@ define(
}
return false;
},
handleResume: function (e) {
e.preventDefault();
if(!this.isValid()) {
jQuery('#mailpoet_newsletter').parsley().validate();
} else {
this._save(e).done(() => {
this.setState({ loading: true });
}).done(() => {
MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'sendingQueue',
action: 'resume',
data: {
newsletter_id: this.state.item.id,
},
}).done(() => {
this.context.router.push(`/${ this.state.item.type || '' }`);
MailPoet.Notice.success(
MailPoet.I18n.t('newsletterSendingHasBeenResumed')
);
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map((error) => { return error.message; }),
{ scroll: true }
);
}
});
}).fail(this._showError).always(() => {
this.setState({ loading: false });
});
}
return false;
},
handleSave: function (e) {
e.preventDefault();
@ -174,6 +227,7 @@ define(
},
_save: function () {
const data = this.state.item;
data.queue = undefined;
this.setState({ loading: true });
// Store only properties that can be changed on this page
@ -215,6 +269,16 @@ define(
return true;
},
render: function () {
const isPaused = this.state.item.status == 'sending'
&& this.state.item.queue
&& this.state.item.queue.status == 'paused';
const fields = this.state.fields.map((field) => {
const newField = field;
if (field.name == 'segments' || field.name == 'options') {
newField.disabled = isPaused;
}
return newField;
});
return (
<div>
<h1>{MailPoet.I18n.t('finalNewsletterStep')}</h1>
@ -223,20 +287,29 @@ define(
<Form
id="mailpoet_newsletter"
fields={ this.state.fields }
fields={ fields }
item={ this.state.item }
loading={ this.state.loading }
onChange={this.handleFormChange}
onSubmit={this.handleSave}
>
<p className="submit">
<input
{
isPaused ?
<input
className="button button-primary"
type="button"
onClick={ this.handleResume }
value={MailPoet.I18n.t('resume')} />
:
<input
className="button button-primary"
type="button"
onClick={ this.handleSend }
value={MailPoet.I18n.t('send')}
{...this.getSendButtonOptions()}
/>
}
&nbsp;
<input
className="button button-secondary"
@ -251,6 +324,10 @@ define(
{MailPoet.I18n.t('goBackToDesign')}
</a>.
</p>
<HelpTooltip
tooltip={MailPoet.I18n.t('helpTooltipSendEmail')}
tooltipId="helpTooltipSendEmail"
/>
</Form>
</div>
);

View File

@ -8,12 +8,14 @@ define(
],
(
React,
jQuery,
jq,
_,
MailPoet,
Hooks
) => {
const jQuery = jq;
const currentTime = window.mailpoet_current_time || '00:00';
const defaultDateTime = window.mailpoet_current_date + ' ' + '00:00:00';
const timeOfDayItems = window.mailpoet_schedule_time_of_day;
@ -84,12 +86,13 @@ define(
const DateText = React.createClass({
onChange: function (event) {
const changeEvent = event;
// Swap display format to storage format
const displayDate = event.target.value;
const displayDate = changeEvent.target.value;
const storageDate = this.getStorageDate(displayDate);
event.target.value = storageDate;
this.props.onChange(event);
changeEvent.target.value = storageDate;
this.props.onChange(changeEvent);
},
componentDidMount: function () {
const $element = jQuery(this.refs.dateInput);
@ -155,6 +158,7 @@ define(
name={this.getFieldName()}
value={this.getDisplayDate(this.props.value)}
readOnly={ true }
disabled={this.props.disabled}
onChange={this.onChange}
ref="dateInput"
{...this.props.validation} />
@ -180,6 +184,7 @@ define(
<select
name={this.props.name || 'time'}
value={this.props.value}
disabled={this.props.disabled}
onChange={this.props.onChange}
{...this.props.validation}
>
@ -235,11 +240,13 @@ define(
onChange={this.handleChange}
displayFormat={dateDisplayFormat}
storageFormat={dateStorageFormat}
disabled={this.props.disabled}
validation={this.props.dateValidation}/>
<TimeSelect
name="time"
value={this.state.time}
onChange={this.handleChange}
disabled={this.props.disabled}
validation={this.props.timeValidation} />
</span>
);
@ -269,8 +276,9 @@ define(
});
},
handleCheckboxChange: function (event) {
event.target.value = this.refs.isScheduled.checked ? '1' : '0';
return this.handleValueChange(event);
const changeEvent = event;
changeEvent.target.value = this.refs.isScheduled.checked ? '1' : '0';
return this.handleValueChange(changeEvent);
},
isScheduled: function () {
return this._getCurrentValue().isScheduled === '1';
@ -292,6 +300,7 @@ define(
name="scheduledAt"
value={this._getCurrentValue().scheduledAt}
onChange={this.handleValueChange}
disabled={this.props.field.disabled}
dateValidation={this.getDateValidation()} />
&nbsp;
<span>
@ -300,7 +309,6 @@ define(
</span>
);
}
return (
<div>
<input
@ -308,6 +316,7 @@ define(
type="checkbox"
value="1"
checked={this.isScheduled()}
disabled={this.props.field.disabled}
name="isScheduled"
onChange={this.handleCheckboxChange} />
@ -414,11 +423,11 @@ define(
return fields;
},
getSendButtonOptions: function (newsletter) {
newsletter = newsletter || {};
const newsletterOptions = newsletter || {};
const isScheduled = (
typeof newsletter.options === 'object'
&& newsletter.options.isScheduled === '1'
typeof newsletterOptions.options === 'object'
&& newsletterOptions.options.isScheduled === '1'
);
const options = {
value: (isScheduled
@ -426,8 +435,8 @@ define(
: MailPoet.I18n.t('send')),
};
if (newsletter.status === 'sent'
|| newsletter.status === 'sending') {
if (newsletterOptions.status === 'sent'
|| newsletterOptions.status === 'sending') {
options['disabled'] = 'disabled';
}

View File

@ -6,6 +6,7 @@ define(
'react-router',
'classnames',
'newsletters/breadcrumb.jsx',
'help-tooltip.jsx',
],
(
React,
@ -13,11 +14,13 @@ define(
MailPoet,
Router,
classNames,
Breadcrumb
Breadcrumb,
HelpTooltip
) => {
const ImportTemplate = React.createClass({
saveTemplate: function (template) {
saveTemplate: function (saveTemplate) {
const template = saveTemplate;
// Stringify to enable transmission of primitive non-string value types
if (!_.isUndefined(template.body)) {
@ -49,6 +52,7 @@ define(
if (_.size(this.refs.templateFile.files) <= 0) return false;
const file = _.first(this.refs.templateFile.files);
const reader = new FileReader();
const saveTemplate = this.saveTemplate;
@ -56,6 +60,9 @@ define(
reader.onload = (e) => {
try {
saveTemplate(JSON.parse(e.target.result));
MailPoet.trackEvent('Emails > Template imported', {
'MailPoet Free version': window.mailpoet_version,
});
} catch (err) {
MailPoet.Notice.error(MailPoet.I18n.t('templateFileMalformedError'));
}
@ -66,10 +73,13 @@ define(
render: function () {
return (
<div>
<h2>{MailPoet.I18n.t('importTemplateTitle')}</h2>
<h2>{MailPoet.I18n.t('importTemplateTitle')} <HelpTooltip
tooltip={MailPoet.I18n.t('helpTooltipTemplateUpload')}
place="right"
className="tooltip-help-import-template"
/></h2>
<form onSubmit={this.handleSubmit}>
<input type="file" placeholder={MailPoet.I18n.t('selectJsonFileToUpload')} ref="templateFile" />
<p className="submit">
<input
className="button button-primary"
@ -138,6 +148,11 @@ define(
body = JSON.stringify(body);
}
MailPoet.trackEvent('Emails > Template selected', {
'MailPoet Free version': window.mailpoet_version,
'Email name': template.name,
});
MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'newsletters',

View File

@ -20,9 +20,17 @@ define(
setupNewsletter: function (type) {
if(type !== undefined) {
this.context.router.push(`/new/${type}`);
MailPoet.trackEvent('Emails > Type selected', {
'MailPoet Free version': window.mailpoet_version,
'Email type': type,
});
}
},
createNewsletter: function (type) {
MailPoet.trackEvent('Emails > Type selected', {
'MailPoet Free version': window.mailpoet_version,
'Email type': type,
});
MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'newsletters',

View File

@ -1,4 +1,4 @@
define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
define('notice', ['mailpoet', 'jquery'], function(mp, jQuery) {
"use strict";
/*==================================================================================================
@ -22,7 +22,7 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
MailPoet.Notice.system('You need to updated ASAP!');
==================================================================================================*/
var MailPoet = mp;
MailPoet.Notice = {
version: 1.0,
// default options
@ -103,12 +103,12 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
);
},
setMessage: function(message) {
message = this.formatMessage(message);
var formattedMessage = this.formatMessage(message);
// let's sugar coat the message with a fancy <p>
message = '<p>'+message+'</p>';
formattedMessage = '<p>'+formattedMessage+'</p>';
// set message
return this.element.html(message);
return this.element.html(formattedMessage);
},
formatMessage: function(message) {
if (Array.isArray(message)) {

View File

@ -2,13 +2,14 @@ define('num',
[
'mailpoet'
], function(
MailPoet
mp
) {
'use strict';
var MailPoet = mp;
MailPoet.Num = {
toLocaleFixed: function (num, precision) {
precision = precision || 0;
toLocaleFixed: function (num, precisionOpts) {
var precision = precisionOpts || 0;
var factor = Math.pow(10, precision);
return (Math.round(num * factor) / factor)
.toLocaleString(

View File

@ -7,8 +7,9 @@ define(
function(
Backbone,
jQuery,
MailPoet
mp
) {
var MailPoet = mp;
if(jQuery('#mailpoet_settings').length === 0) {
return;
}
@ -50,9 +51,9 @@ define(
jQuery('#mailpoet_sending_method_setup').fadeIn();
}
},
tabs: function(tab, section) {
tabs: function(tabStr, section) {
// set default tab
tab = tab || 'mta';
var tab = tabStr || 'mta';
// reset all active tabs
jQuery('.nav-tab-wrapper a').removeClass('nav-tab-active');

View File

@ -52,8 +52,9 @@ define(
router.on('route:step1', function () {
// set or reset temporary validation rule on all columns
mailpoetColumns = jQuery.map(mailpoetColumns, function (column, columnIndex) {
column.validation_rule = false;
return column;
var col = column;
col.validation_rule = false;
return col;
});
if (typeof (importData.step1) !== 'undefined') {
@ -302,10 +303,10 @@ define(
advancedOptionComments = false,
// trim spaces, commas, periods,
// single/double quotes and convert to lowercase
detectAndCleanupEmail = function (email) {
detectAndCleanupEmail = function (emailString) {
var test;
// decode HTML entities
email = jQuery('<div />').html(email).text();
var email = jQuery('<div />').html(emailString).text();
email = email
.toLowerCase()
// left/right trim spaces, punctuation (e.g., " 'email@email.com'; ")
@ -315,11 +316,13 @@ define(
// remove urlencoded characters
.replace(/\s+|%\d+|,+/g, '');
// detect e-mails that will be otherwise rejected by email regex
if (test = /<(.*?)>/.exec(email)) {
test = /<(.*?)>/.exec(email);
if (test) {
// is the email inside angle brackets (e.g., 'some@email.com <some@email.com>')?
email = test[1].trim();
}
if (test = /mailto:(?:\s+)?(.*)/.exec(email)) {
test = /mailto:(?:\s+)?(.*)/.exec(email);
if (test) {
// is the email in 'mailto:email' format?
email = test[1].trim();
}
@ -534,12 +537,14 @@ define(
data: segments,
width: '20em',
templateResult: function (item) {
item.subscriberCount = parseInt(item.subscriberCount);
return item.name + ' (' + item.subscriberCount.toLocaleString() + ')';
var i = item;
i.subscriberCount = parseInt(i.subscriberCount, 10);
return i.name + ' (' + i.subscriberCount.toLocaleString() + ')';
},
templateSelection: function (item) {
item.subscriberCount = parseInt(item.subscriberCount);
return item.name + ' (' + item.subscriberCount.toLocaleString() + ')';
var i = item;
i.subscriberCount = parseInt(i.subscriberCount, 10);
return i.name + ' (' + i.subscriberCount.toLocaleString() + ')';
}
})
.change(function () {
@ -894,7 +899,8 @@ define(
}
}
}
jQuery.map(subscribersClone.subscribers, function (data, index) {
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);

View File

@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/sh -e
# Translations (npm install & composer install need to be run before)
echo '[BUILD] Generating translations'
@ -9,21 +9,22 @@ plugin_name='mailpoet'
# Remove previous build.
echo '[BUILD] Removing previous build'
rm $plugin_name.zip
test -e $plugin_name.zip && rm $plugin_name.zip
# Create temp dir.
echo '[BUILD] Creating temporary directory'
test -d $plugin_name && rm -rf $plugin_name
mkdir $plugin_name
# Production assets.
echo '[BUILD] Generating production CSS and JS assets'
rm -rf node_modules
test -d node_modules && rm -rf node_modules
npm install
./do compile:all --env production
# Production libraries.
echo '[BUILD] Fetching production libraries'
rm -rf vendor
test -d vendor && rm -rf vendor
./composer.phar install --no-dev --prefer-dist --optimize-autoloader --no-scripts
# Copy release folders.

View File

@ -0,0 +1,50 @@
actor: Tester
paths:
tests: tests
log: tests/_output
data: tests/_data
support: tests/_support
envs: tests/_envs
settings:
colors: true
memory_limit: 1024M
log: true
strict_xml: true
extensions:
enabled:
- Codeception\Extension\RunFailed
modules:
config:
WPWebDriver:
host: chrome
url: 'http://wordpress'
browser: chrome
port: 4444
window_size: '1024x768'
restart: true
wait: 0
adminUsername: admin
adminPassword: password
adminPath: /wp-admin
WPLoader:
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']
coverage:
enabled: true
whitelist:
include:
- lib/*
exclude:
- lib/Config/PopulatorData/*
- lib/Util/Sudzy/*
- lib/Util/CSS.php
- lib/Util/Helpers.php
- lib/Util/XLSXWriter.php

View File

@ -21,6 +21,28 @@ modules:
user: ''
password: ''
dump: tests/_data/dump.sql
WPLoader:
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']
WPWebDriver:
host: chrome
url: 'http://wordpress'
browser: chrome
port: 4444
window_size: '1024x768'
restart: true
wait: 0
adminUsername: admin
adminPassword: password
adminPath: /wp-admin
coverage:
enabled: true
whitelist:

View File

@ -20,10 +20,12 @@
},
"require-dev": {
"codeception/aspect-mock": "^2.0",
"codeception/codeception": "^2.2.9",
"codeception/codeception": "2.2.11",
"codeception/verify": "^0.3.3",
"consolidation/robo": "^1.0.5",
"henrikbjorn/lurker": "^1.2",
"lucatume/wp-browser": "1.19.12",
"phpunit/phpunit": "4.8.21",
"vlucas/phpdotenv": "^2.4.0",
"umpirsky/twig-gettext-extractor": "1.1.*",
"raveren/kint": "^1.0",

2794
composer.lock generated

File diff suppressed because it is too large Load Diff

47
docker-compose.yml Normal file
View File

@ -0,0 +1,47 @@
version: '2'
services:
codeception:
build: .
depends_on:
- wordpress
volumes:
- ./:/project
- wp-core:/wp-core
- ./:/wp-core/wp-content/plugins/mailpoet
entrypoint: /docker-entrypoint.sh
wordpress:
build: ./tests/wordpressDockerfile
image: wordpress:latest
depends_on:
- mysql
- chrome
volumes:
- wp-core:/var/www/html
- ./:/var/www/html/wp-content/plugins/mailpoet
- /tmp:/var/www/html/wp-content/uploads/mailpoet/cache
ports:
- 8080:80
environment:
WORDPRESS_DB_PASSWORD: wordpress
mysql:
image: mysql:5.6
environment:
MYSQL_ROOT_PASSWORD: wordpress
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
chrome:
environment:
- DBUS_SESSION_BUS_ADDRESS=/dev/null
volumes:
- /dev/shm:/dev/shm
image: selenium/standalone-chrome-debug
ports:
- '4444'
- '5900:5900'
volumes:
wp-core:

42
docker-entrypoint.sh Normal file
View File

@ -0,0 +1,42 @@
#!/bin/bash
# Allows WP CLI to run with the right permissions.
wp-su() {
sudo -E -u www-data wp "$@"
}
while ! mysqladmin ping -hmysql --silent; do
echo 'Waiting for the database'
sleep 1
done
# Make sure permissions are correct.
cd /wp-core
chown www-data:www-data wp-content/plugins
chmod 755 wp-content/plugins
# Make sure WordPress is installed.
if ! $(wp-su core is-installed); then
echo "Installing WordPress"
wp-su core install --url=wordpress --title=tests --admin_user=admin --admin_email=test@test.com
echo "Configuring WordPress"
# The development version of Gravity Flow requires SCRIPT_DEBUG
wp-su core config --dbhost=mysql --dbname=wordpress --dbuser=wordpress --dbpass=wordpress --extra-php="define( 'SCRIPT_DEBUG', true );" --force
fi
rm -rf /project/vendor_backup
mv /project/vendor /project/vendor_backup
cd /project
php composer.phar install
cd /wp-core/wp-content/plugins/mailpoet
/project/vendor/bin/codecept run acceptance -c codeception.acceptance.yml $@
rm -rf /project/vendor
mv /project/vendor_backup /project/vendor

View File

@ -33,6 +33,7 @@ class Newsletters extends APIEndpoint {
$newsletter = $newsletter
->withSegments()
->withOptions()
->withSendingQueue()
->asArray();
$newsletter = Hooks::applyFilters('mailpoet_api_newsletters_get_after', $newsletter);
return $this->successResponse($newsletter);
@ -110,6 +111,13 @@ class Newsletters extends APIEndpoint {
}
}
$queue = $newsletter->getQueue();
if($queue) {
$queue->newsletter_rendered_body = null;
$queue->newsletter_rendered_subject = null;
$queue->save();
}
Hooks::doAction('mailpoet_api_newsletters_save_after', $newsletter);
return $this->successResponse($newsletter->asArray());
@ -455,4 +463,4 @@ class Newsletters extends APIEndpoint {
);
}
}
}
}

View File

@ -4,6 +4,7 @@ use MailPoet\API\JSON\Endpoint as APIEndpoint;
use MailPoet\API\JSON\Error as APIError;
use MailPoet\API\JSON\Access as APIAccess;
use MailPoet\Form\Util\FieldNameObfuscator;
use MailPoet\Listing;
use MailPoet\Models\Subscriber;
use MailPoet\Models\Form;
@ -66,6 +67,11 @@ class Subscribers extends APIEndpoint {
APIError::BAD_REQUEST => __('Please specify a valid form ID.', 'mailpoet')
));
}
if(!empty($data['email'])) {
return $this->badRequest(array(
APIError::BAD_REQUEST => __('Please leave the first field empty.', 'mailpoet')
));
}
$segment_ids = (!empty($data['segments'])
? (array)$data['segments']
@ -74,6 +80,8 @@ class Subscribers extends APIEndpoint {
$segment_ids = $form->filterSegments($segment_ids);
unset($data['segments']);
$data = $this->deobfuscateFormPayload($data);
if(empty($segment_ids)) {
return $this->badRequest(array(
APIError::BAD_REQUEST => __('Please select a list.', 'mailpoet')
@ -115,6 +123,11 @@ class Subscribers extends APIEndpoint {
}
}
private function deobfuscateFormPayload($data) {
$obfuscator = new FieldNameObfuscator();
return $obfuscator->deobfuscateFormPayload($data);
}
function save($data = array()) {
if(empty($data['segments'])) {
$data['segments'] = array();

View File

@ -5,6 +5,7 @@ use MailPoet\API;
use MailPoet\Cron\CronTrigger;
use MailPoet\Router;
use MailPoet\Util\ConflictResolver;
use MailPoet\Util\Helpers;
use MailPoet\WP\Notice as WPNotice;
if(!defined('ABSPATH')) exit;
@ -13,7 +14,8 @@ require_once(ABSPATH . 'wp-admin/includes/plugin.php');
class Initializer {
const UNABLE_TO_CONNECT = '<strong>Mailpoet:</strong> Unable to connect to the database (the database is unable to open a file or folder), the connection is likely not configured correctly. Please read our <a href="http://beta.docs.mailpoet.com/article/200-solving-database-connection-issues">Knowledge Base article</a> for steps how to resolve it.';
const UNABLE_TO_CONNECT = 'Unable to connect to the database (the database is unable to open a file or folder), the connection is likely not configured correctly. Please read our [link] Knowledge Base article [/link] for steps how to resolve it.';
const SOLVE_DB_ISSUE_URL = 'http://beta.docs.mailpoet.com/article/200-solving-database-connection-issues';
protected $plugin_initialized = false;
@ -36,7 +38,11 @@ class Initializer {
try {
$this->setupDB();
} catch(\Exception $e) {
return WPNotice::displayWarning(self::UNABLE_TO_CONNECT);
return WPNotice::displayError(Helpers::replaceLinkTags(
__(self::UNABLE_TO_CONNECT, 'mailpoet'),
self::SOLVE_DB_ISSUE_URL,
array('target' => '_blank')
));
}
// activation function

View File

@ -2,21 +2,19 @@
namespace MailPoet\Config;
use MailPoet\Util\ProgressBar;
use MailPoet\Models\Form;
use MailPoet\Models\Setting;
use MailPoet\Models\Segment;
use MailPoet\Models\Subscriber;
use MailPoet\Models\CustomField;
use MailPoet\Models\SubscriberSegment;
use MailPoet\Models\SubscriberCustomField;
use MailPoet\Models\Form;
use MailPoet\Models\MappingToExternalEntities;
use MailPoet\Config\Activator;
use MailPoet\Models\Segment;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
use MailPoet\Models\SubscriberCustomField;
use MailPoet\Models\SubscriberSegment;
use MailPoet\Util\ProgressBar;
if(!defined('ABSPATH')) exit;
class MP2Migrator {
const IMPORT_TIMEOUT_IN_SECONDS = 7200; // Timeout = 2 hours
const CHUNK_SIZE = 10; // To import the data by batch
@ -145,7 +143,9 @@ class MP2Migrator {
* @return string Result
*/
public function import() {
set_time_limit(self::IMPORT_TIMEOUT_IN_SECONDS);
if(strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
@set_time_limit(3600);
}
ob_start();
$datetime = new \MailPoet\WP\DateTime();
$this->log(sprintf('=== ' . __('START IMPORT', 'mailpoet') . ' %s ===', $datetime->formatTime(time(), \MailPoet\WP\DateTime::DEFAULT_DATE_TIME_FORMAT)));
@ -867,10 +867,10 @@ class MP2Migrator {
'type' => $type,
'name' => $field['name'],
'id' => $field_id,
'unique' => !in_array($field['type'], array('html', 'divider', 'email', 'submit'))? "1" : "0",
'static' => in_array($field_id, array('email', 'submit'))? "1" : "0",
'unique' => !in_array($field['type'], array('html', 'divider', 'email', 'submit')) ? "1" : "0",
'static' => in_array($field_id, array('email', 'submit')) ? "1" : "0",
'params' => $params,
'position' => isset($field['position'])? $field['position'] : '',
'position' => isset($field['position']) ? $field['position'] : '',
);
}
@ -956,7 +956,7 @@ class MP2Migrator {
/**
* Import the settings
*
*
*/
private function importSettings() {
$encoded_options = get_option('wysija');
@ -988,10 +988,10 @@ class MP2Migrator {
$subscribe = Setting::getValue('subscribe');
$subscribe['on_comment']['enabled'] = isset($options['commentform']) ? $options['commentform'] : '0';
$subscribe['on_comment']['label'] = isset($options['commentform_linkname']) ? $options['commentform_linkname'] : '';
$subscribe['on_comment']['segments'] = isset($options['commentform_lists'])? $this->getMappedSegmentIds($options['commentform_lists']) : array();
$subscribe['on_comment']['segments'] = isset($options['commentform_lists']) ? $this->getMappedSegmentIds($options['commentform_lists']) : array();
$subscribe['on_register']['enabled'] = isset($options['registerform']) ? $options['registerform'] : '0';
$subscribe['on_register']['label'] = isset($options['registerform_linkname']) ? $options['registerform_linkname'] : '';
$subscribe['on_register']['segments'] = isset($options['registerform_lists'])? $this->getMappedSegmentIds($options['registerform_lists']) : array();
$subscribe['on_register']['segments'] = isset($options['registerform_lists']) ? $this->getMappedSegmentIds($options['registerform_lists']) : array();
Setting::setValue('subscribe', $subscribe);
// Subscription
@ -999,7 +999,7 @@ class MP2Migrator {
$subscription['pages']['unsubscribe'] = isset($options['unsubscribe_page']) ? $options['unsubscribe_page'] : '';
$subscription['pages']['confirmation'] = isset($options['confirmation_page']) ? $options['confirmation_page'] : '';
$subscription['pages']['manage'] = isset($options['subscriptions_page']) ? $options['subscriptions_page'] : '';
$subscription['segments'] = isset($options['manage_subscriptions_lists'])? $this->getMappedSegmentIds($options['manage_subscriptions_lists']) : array();
$subscription['segments'] = isset($options['manage_subscriptions_lists']) ? $this->getMappedSegmentIds($options['manage_subscriptions_lists']) : array();
Setting::setValue('subscription', $subscription);
// Confirmation email
@ -1047,7 +1047,7 @@ class MP2Migrator {
if($mta['method'] == 'SendGrid') {
Setting::setValue('smtp_provider', 'SendGrid');
}
// Installation date
if(isset($options['installed_time'])) {
$datetime = new \MailPoet\WP\DateTime();
@ -1139,5 +1139,4 @@ class MP2Migrator {
}
return $emails_number;
}
}
}

View File

@ -337,7 +337,7 @@ class Populator {
function convertExistingDataToUTF8() {
global $wpdb;
if(!version_compare(get_option('mailpoet_db_version'), '3.0.0-beta.32', '<=')) {
if(!version_compare(get_option('mailpoet_db_version', '3.0.0-beta.33'), '3.0.0-beta.32', '<=')) {
// Data conversion should only be performed only once, when migrating from
// older version
return false;
@ -402,7 +402,7 @@ class Populator {
global $wpdb;
// perform once for versions below 3.0.0-beta.36.2.1
if(version_compare(get_option('mailpoet_db_version'), '3.0.0-beta.36.2.1', '>=')) {
if(version_compare(get_option('mailpoet_db_version', '3.0.0-beta.36.2.3'), '3.0.0-beta.36.2.1', '>=')) {
return false;
}
@ -455,7 +455,7 @@ class Populator {
global $wpdb;
// perform once for versions below 3.0.0-beta.36.2.1
if(version_compare(get_option('mailpoet_db_version'), '3.0.0-beta.36.2.1', '>=')) {
if(version_compare(get_option('mailpoet_db_version', '3.0.0-beta.36.2.3'), '3.0.0-beta.36.2.1', '>=')) {
return false;
}

View File

@ -190,4 +190,4 @@ class SendingQueue {
->whereNull('type')
->findMany();
}
}
}

View File

@ -15,15 +15,15 @@ if(!defined('ABSPATH')) exit;
class Links {
static function process($rendered_newsletter, $newsletter, $queue) {
list($rendered_newsletter, $links) =
self::hashAndReplaceLinks($rendered_newsletter);
self::hashAndReplaceLinks($rendered_newsletter, $newsletter->id, $queue->id);
self::saveLinks($links, $newsletter, $queue);
return $rendered_newsletter;
}
static function hashAndReplaceLinks($rendered_newsletter) {
static function hashAndReplaceLinks($rendered_newsletter, $newsletter_id, $queue_id) {
// join HTML and TEXT rendered body into a text string
$content = Helpers::joinObject($rendered_newsletter);
list($content, $links) = NewsletterLinks::process($content);
list($content, $links) = NewsletterLinks::process($content, $newsletter_id, $queue_id);
// split the processed body with hashed links back to HTML and TEXT
list($rendered_newsletter['html'], $rendered_newsletter['text'])
= Helpers::splitObject($content);

View File

@ -175,4 +175,4 @@ class Newsletter {
__('There was an error processing your newsletter during sending. If possible, please contact us and report this issue.')
);
}
}
}

View File

@ -1,6 +1,8 @@
<?php
namespace MailPoet\Form\Block;
use MailPoet\Form\Util\FieldNameObfuscator;
abstract class Base {
protected static function getInputValidation($block, $extra_rules = array()) {
$rules = array();
@ -104,7 +106,8 @@ abstract class Base {
if((int)$block['id'] > 0) {
return 'cf_'.$block['id'];
} else {
return $block['id'];
$obfuscator = new FieldNameObfuscator();
return $obfuscator->obfuscate($block['id']);//obfuscate field name for spambots
}
}

View File

@ -79,7 +79,7 @@ class Date extends Base {
$html .= '</select>';
} else if($date_selector === 'MM') {
$block['selected'] = $month;
$html .= '<select class="mailpoet_date_month" ';
$html .= '<select class="mailpoet_select mailpoet_date_month" ';
$html .= static::getInputValidation($block, array(
'required-message' => __('Please select a month', 'mailpoet')
));

View File

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

View File

@ -15,6 +15,7 @@ class Renderer {
$styles = new Util\Styles(static::getStyles($form));
$html = '<style type="text/css">';
$html .= '.mailpoet_hp_email_label{position: absolute;left: -999em;}';// move honeypot field out of the sight
$html .= $styles->render($prefix);
$html .= '</style>';
@ -38,7 +39,8 @@ class Renderer {
}
static function renderBlocks($blocks = array()) {
$html = '';
// this is a honeypot for spambots
$html = '<label class="mailpoet_hp_email_label">Please leave this field empty<input type="email" name="data[email]"></label>';
foreach($blocks as $key => $block) {
$html .= static::renderBlock($block)."\n";
}

View File

@ -0,0 +1,38 @@
<?php
namespace MailPoet\Form\Util;
class FieldNameObfuscator {
const OBFUSCATED_FIELD_PREFIX = 'form_field_';
public function obfuscate($name) {
return FieldNameObfuscator::OBFUSCATED_FIELD_PREFIX.base64_encode($name);
}
public function deobfuscate($name) {
$prefixLength = strlen(FieldNameObfuscator::OBFUSCATED_FIELD_PREFIX);
return base64_decode(substr($name, $prefixLength));
}
public function deobfuscateFormPayload($data) {
$result = array();
foreach($data as $key => $value) {
$result[$this->deobfuscateField($key)] = $value;
}
return $result;
}
private function deobfuscateField($name) {
if($this->wasFieldObfuscated($name)) {
return $this->deobfuscate($name);
} else {
return $name;
}
}
private function wasFieldObfuscated($name) {
return strpos($name, FieldNameObfuscator::OBFUSCATED_FIELD_PREFIX) === 0;
}
}

View File

@ -13,10 +13,11 @@ class Styles {
/* paragraphs (label + input) */
.mailpoet_paragraph {
line-height:20px;
}
/* labels */
.mailpoet_segment_label,
.mailpoet_text_label,
.mailpoet_textarea_label,
.mailpoet_select_label,
@ -25,18 +26,35 @@ class Styles {
.mailpoet_list_label,
.mailpoet_date_label {
display:block;
font-weight:bold;
}
/* inputs */
.mailpoet_text,
.mailpoet_textarea,
.mailpoet_select,
.mailpoet_date_month,
.mailpoet_date_day,
.mailpoet_date_year,
.mailpoet_date {
display:block;
}
.mailpoet_checkbox {
.mailpoet_text,
.mailpoet_textarea {
width:200px;
}
.mailpoet_checkbox {
}
.mailpoet_submit input {
}
.mailpoet_divider {
}
.mailpoet_message {
}
.mailpoet_validate_success {

View File

@ -1,10 +1,13 @@
<?php
namespace MailPoet\Form;
use MailPoet\API\JSON\API;
use MailPoet\Config\Renderer;
use MailPoet\Models\Form;
use MailPoet\Form\Renderer as FormRenderer;
use MailPoet\Models\Form;
use MailPoet\Util\Security;
use MailPoet\WP\Hooks;
if(!defined('ABSPATH')) exit;
@ -37,7 +40,7 @@ class Widget extends \WP_Widget {
$instance = wp_parse_args(
(array)$instance,
array(
'title' => __("Subscribe to Our Newsletter", 'mailpoet')
'title' => __('Subscribe to Our Newsletter', 'mailpoet')
)
);
@ -109,7 +112,7 @@ class Widget extends \WP_Widget {
$instance = $args;
}
$title = apply_filters(
$title = Hooks::applyFilters(
'widget_title',
!empty($instance['title']) ? $instance['title'] : '',
$instance,
@ -119,7 +122,7 @@ class Widget extends \WP_Widget {
// get form
$form = Form::getPublished()->findOne($instance['form']);
// if the form was not found, return nothing.
// if the form was not found, return nothing
if($form === false) {
return '';
} else {
@ -175,6 +178,7 @@ class Widget extends \WP_Widget {
try {
$output = $renderer->render('form/widget.html', $data);
$output = do_shortcode($output);
$output = Hooks::applyFilters('mailpoet_form_widget_post_process', $output);
} catch(\Exception $e) {
$output = $e->getMessage();
}

View File

@ -1,13 +1,20 @@
<?php
namespace MailPoet\Newsletter\Editor;
use MailPoet\WP\Hooks;
if(!defined('ABSPATH')) exit;
class PostContentManager {
const MAX_EXCERPT_LENGTH = 60;
const WP_POST_CLASS = 'mailpoet_wp_post';
public $max_excerpt_length = 60;
function __construct() {
$this->max_excerpt_length = Hooks::applyFilters('mailpoet_newsletter_post_excerpt_length', $this->max_excerpt_length);
}
function getContent($post, $displayType) {
if($displayType === 'titleOnly') {
return '';
@ -57,7 +64,7 @@ class PostContentManager {
return $excerpts[0];
} else {
// Separator not present, try to shorten long posts
return wp_trim_words($content, self::MAX_EXCERPT_LENGTH, ' &hellip;');
return wp_trim_words($content, $this->max_excerpt_length, ' &hellip;');
}
}
@ -92,5 +99,4 @@ class PostContentManager {
return $content;
}
}

View File

@ -14,7 +14,7 @@ class PostTransformer {
}
function transform($post) {
$content_manager = new PostContentManager($post);
$content_manager = new PostContentManager();
$meta_manager = new MetaInformationManager();
$content = $content_manager->getContent($post, $this->args['displayType']);

View File

@ -8,8 +8,8 @@ use MailPoet\Newsletter\Shortcodes\Shortcodes;
use MailPoet\Router\Endpoints\Track as TrackEndpoint;
use MailPoet\Router\Router;
use MailPoet\Util\Helpers;
use MailPoet\Util\pQuery\pQuery as DomParser;
use MailPoet\Util\Security;
use MailPoet\Util\pQuery\pQuery as DomParser;
class Links {
const DATA_TAG_CLICK = '[mailpoet_click_data]';
@ -17,9 +17,10 @@ class Links {
const LINK_TYPE_SHORTCODE = 'shortcode';
const LINK_TYPE_URL = 'link';
static function process($content) {
static function process($content, $newsletter_id, $queue_id) {
$extracted_links = self::extract($content);
$processed_links = self::hash($extracted_links);
$saved_links = self::load($newsletter_id, $queue_id);
$processed_links = self::hash($extracted_links, $saved_links);
return self::replace($content, $processed_links);
}
@ -51,13 +52,31 @@ class Links {
return array_unique($extracted_links, SORT_REGULAR);
}
static function hash($extracted_links) {
$processed_links = array();
static function load($newsletter_id, $queue_id) {
$links = NewsletterLink::whereEqual('newsletter_id', $newsletter_id)
->whereEqual('queue_id', $queue_id)
->findMany();
$saved_links = array();
foreach($links as $link) {
$saved_links[$link->url] = $link->asArray();
}
return $saved_links;
}
static function hash($extracted_links, $saved_links) {
$processed_links = array_map(function(&$link) {
$link['type'] = Links::LINK_TYPE_URL;
$link['link'] = $link['url'];
$link['processed_link'] = self::DATA_TAG_CLICK . '-' . $link['hash'];
return $link;
}, $saved_links);
foreach($extracted_links as $extracted_link) {
$link = $extracted_link['link'];
if (array_key_exists($link, $processed_links))
continue;
$hash = Security::generateHash();
// Use URL as a key to map between extracted and processed links
// regardless of their sequential position (useful for link skips etc.)
$link = $extracted_link['link'];
$processed_links[$link] = array(
'type' => $extracted_link['type'],
'hash' => $hash,
@ -137,6 +156,8 @@ class Links {
static function save(array $links, $newsletter_id, $queue_id) {
foreach($links as $link) {
if (isset($link['id']))
continue;
if(empty($link['hash']) || empty($link['link'])) continue;
$newsletter_link = NewsletterLink::create();
$newsletter_link->newsletter_id = $newsletter_id;
@ -198,4 +219,4 @@ class Links {
$transformed_data['preview'] = (!empty($data[4])) ? $data[4] : false;
return $transformed_data;
}
}
}

View File

@ -1,4 +1,5 @@
<?php
namespace MailPoet\Newsletter\Shortcodes\Categories;
class Date {
@ -7,20 +8,19 @@ class Date {
$action_argument = false,
$action_argument_value = false
) {
$date = new \DateTime('now');
$action_formats = array(
'd' => $date->format('d'),
'dordinal' => $date->format('dS'),
'dtext' => $date->format('l'),
'm' => $date->format('m'),
'mtext' => $date->format('F'),
'y' => $date->format('Y')
$action_mapping = array(
'd' => 'd',
'dordinal' => 'dS',
'dtext' => 'l',
'm' => 'm',
'mtext' => 'F',
'y' => 'Y'
);
if(!empty($action_formats[$action])) {
return $action_formats[$action];
if(!empty($action_mapping[$action])) {
return date_i18n($action_mapping[$action], current_time('timestamp'));
}
return ($action === 'custom' && $action_argument === 'format') ?
$date->format($action_argument_value) :
date_i18n($action_argument_value, current_time('timestamp')) :
false;
}
}

View File

@ -1,4 +1,5 @@
<?php
namespace MailPoet\Newsletter\Shortcodes;
use MailPoet\Models\CustomField;
@ -115,7 +116,7 @@ class ShortcodesHelper {
static function getCustomFields() {
$custom_fields = CustomField::findMany();
if(!$custom_fields) return false;
return array_map(function ($custom_field) {
return array_map(function($custom_field) {
return array(
'text' => $custom_field->name,
'shortcode' => '[subscriber:cf_' . $custom_field->id . ']'

View File

@ -1,4 +1,5 @@
<?php
namespace MailPoet\Subscribers\ImportExport\Export;
use MailPoet\Config\Env;
@ -26,7 +27,9 @@ class Export {
public $subscriber_batch_size;
public function __construct($data) {
set_time_limit(0);
if(strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
set_time_limit(0);
}
$this->export_confirmed_option = $data['export_confirmed_option'];
$this->export_format_option = $data['export_format_option'];
$this->group_by_segment_option = $data['group_by_segment_option'];
@ -47,7 +50,7 @@ class Export {
function process() {
try {
if(is_writable($this->export_path) === false) {
throw new \Exception(__("The export file could not be saved on the server.", 'mailpoet'));
throw new \Exception(__('The export file could not be saved on the server.', 'mailpoet'));
}
if(!extension_loaded('zip')) {
throw new \Exception(__('Export requires a ZIP extension to be installed on the host.', 'mailpoet'));
@ -211,7 +214,7 @@ class Export {
} else {
// if all subscribers belong to at least one segment, select the segment name
$subscribers = $subscribers
->selectExpr('MAX('.Segment::$_table . '.name) as segment_name')
->selectExpr('MAX(' . Segment::$_table . '.name) as segment_name')
->whereIn(SubscriberSegment::$_table . '.segment_id', $this->segments);
}
if($this->group_by_segment_option) {
@ -273,4 +276,4 @@ class Export {
return $subscriber[$field];
}, $this->subscriber_fields);
}
}
}

View File

@ -4,7 +4,7 @@ if(!defined('ABSPATH')) exit;
/*
* Plugin Name: MailPoet 3 (new)
* Version: 3.0.0-rc.1.0.0
* Version: 3.0.0-rc.1.0.4
* Plugin URI: http://www.mailpoet.com
* Description: Create and send newsletters, post notifications and welcome emails from your WordPress.
* Author: MailPoet
@ -21,7 +21,7 @@ if(!defined('ABSPATH')) exit;
*/
$mailpoet_plugin = array(
'version' => '3.0.0-rc.1.0.0',
'version' => '3.0.0-rc.1.0.4',
'filename' => __FILE__,
'path' => dirname(__FILE__),
'autoloader' => dirname(__FILE__) . '/vendor/autoload.php',
@ -54,11 +54,8 @@ function mailpoet_php_version_notice() {
printf('<div class="error"><p>%1$s</p></div>', $notice);
}
if(
isset($_SERVER["SERVER_SOFTWARE"])
&& strpos(strtolower($_SERVER["SERVER_SOFTWARE"]), "microsoft-iis") !== false
) {
add_action('admin_notices', 'mailpoet_php_version_notice');
if(isset($_SERVER['SERVER_SOFTWARE']) && strpos(strtolower($_SERVER['SERVER_SOFTWARE']), 'microsoft-iis') !== false) {
add_action('admin_notices', 'mailpoet_microsoft_iis_notice');
// deactivate the plugin
add_action('admin_init', 'mailpoet_deactivate_plugin');
return;
@ -66,7 +63,7 @@ if(
// Display IIS server error notice
function mailpoet_microsoft_iis_notice() {
$notice = __('MailPoet plugin cannot run under Microsoft\'s Internet Information Services (IIS) web server. We recommend that you use a web server powered by Apache or NGINX.', 'mailpoet');
$notice = __("MailPoet plugin cannot run under Microsoft's Internet Information Services (IIS) web server. We recommend that you use a web server powered by Apache or NGINX.", 'mailpoet');
printf('<div class="error"><p>%1$s</p></div>', $notice);
}

View File

@ -3,7 +3,7 @@
"scripts": {
"lint": "npm run lint6 && npm run lint5 && npm run lint-tests",
"lint6": "eslint -c .eslintrc.es6.json --max-warnings 0 'assets/js/src/**/*.jsx'",
"lint5": "eslint -c .eslintrc.es5.json --max-warnings 0 'assets/js/src/**/*.js'",
"lint5": "eslint -c .eslintrc.es5.json --ignore-pattern helpscout.js --max-warnings 0 'assets/js/src/**/*.js'",
"lint-tests": "eslint -c .eslintrc.tests.json --max-warnings 0 'tests/javascript'"
},
"dependencies": {
@ -25,6 +25,7 @@
"parsleyjs": "^2.1.2",
"react": "~15.4.2",
"react-dom": "~15.4.2",
"react-html-parser": "^1.0.3",
"react-router": "~3.0.2",
"react-string-replace": "^0.3.2",
"react-tooltip": "^3.2.10",
@ -38,10 +39,10 @@
"amd-inject-loader": "~0.5.0",
"babel-core": "^5.8.22",
"babel-loader": "^5.3.2",
"clean-webpack-plugin": "^0.1.16",
"cross-env": "^5.0.1",
"chai": "2.2.0",
"chai-jq": "0.0.8",
"clean-webpack-plugin": "^0.1.16",
"cross-env": "^5.0.1",
"eslint": "^3.19.0",
"eslint-config-airbnb": "^15.0.1",
"eslint-plugin-import": "^2.3.0",
@ -53,6 +54,7 @@
"imports-loader": "~0.7.1",
"jquery": "2.1.4",
"jsdom": "3.1.2",
"json-loader": "^0.5.7",
"mocha": "2.2.1",
"nib": "~1.1.2",
"sinon": "1.14.1",
@ -62,4 +64,4 @@
"webpack-manifest-plugin": "^1.1.0",
"webpack-md5-hash": "0.0.5"
}
}
}

View File

@ -3,7 +3,7 @@ Contributors: mailpoet, wysija
Tags: newsletter, email, welcome email, post notification, autoresponder, signup, subscription, SMTP
Requires at least: 4.6
Tested up to: 4.8
Stable tag: 3.0.0-rc.1.0.0
Stable tag: 3.0.0-rc.1.0.4
Create and send beautiful emails and newsletters from WordPress.
== Description ==
@ -93,6 +93,25 @@ Our [support site](https://beta.docs.mailpoet.com) has plenty of articles. You c
== Changelog ==
= 3.0.0-rc.1.0.4 - 2017-08-22 =
* Added: newsletters can now be paused and edited while sending;
* Added: tooltips across the UI to quickly answer questions we often get on support;
* Added: extra measures to help prevent fake subscriptions by bots;
* Added: a hook to modify maximum post excerpt length;
* Fixed: it is possible again to switch to other sending methods after choosing MailPoet Sending Service. Thx Bastien!
= 3.0.0-rc.1.0.3 - 2017-08-15 =
* Improved: newsletter browser preview window in newsletter editor now fits correctly in any screen height;
* Improved: date shortcode displays WP time and is available to be translated into other laguages. Thanks Rik and Yves!
* Improved: rendered form body can be modified via a hook. Thanks, Vrodo;
* Fixed: subscriber export will not fail on hosts with PHP's set_time_limit() disabled. Thanks, @miguelarroyo;
= 3.0.0-rc.1.0.2 - 2017-08-08 =
* Fixed: correct error notice is displayed when using IIS server. Thanks @flauer!
= 3.0.0-rc.1.0.1 - 2017-08-02 =
* Fixed: we were so excited to come out of Beta, we forgot to include translation files. Woops :)
= 3.0.0-rc.1.0.0 - 2017-08-01 =
* Improved: MailPoet 3 is no longer in Beta!
* Improved: blockquotes in posts are now displayed in emails; Thanks @newslines!

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/bin/bash -e
echo "Getting translations from Transifex..."
tx pull -a -f

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/bin/bash -e
# Write ~/.transifexrc file if not exists
if [ ! -f ~/.transifexrc ]; then

View File

@ -29,4 +29,29 @@ class AcceptanceTester extends \Codeception\Actor {
$this->click('Log In');
$this->saveSessionSnapshot('login');
}
/**
* Define custom actions here
*/
public function logOut() {
$I = $this;
$I->amOnPage('/wp-login.php?action=logout');
$I->click('log out');
$I->wait(1);
}
/**
* Navigate to the specified Mailpoet page in the admin.
*
* @param string $page The page to visit e.g. Inbox or Status
*/
public function amOnMailpoetPage($page) {
$I = $this;
$I->amOnPage('/wp-admin');
$I->click('MailPoet');
$I->waitForText($page, 3);
$I->click($page);
$I->waitForText($page, 3);
}
}

View File

@ -0,0 +1,6 @@
actor: AcceptanceTester
modules:
enabled:
- \Helper\Acceptance
- WPLoader
- WPWebDriver

View File

@ -0,0 +1,15 @@
<?php
/*
* Test summary: Test Newsletters page works
*
* Test details:
* - Open newsletters from the menu
*/
$I = new AcceptanceTester($scenario);
$I->wantTo('Open newsletters page');
$I->loginAsAdmin();
$I->seeInCurrentUrl('/wp-admin/');
// Go to Status
$I->amOnMailpoetPage('Emails');
$I->waitForElement('#newsletters_container', 3);

View File

@ -0,0 +1 @@
<?php

View File

@ -1,9 +1,9 @@
var fs = require('fs');
module.exports = {
loadFileToContainer: function (path, window, containerTagName, options) {
loadFileToContainer: function (path, window, containerTagName, opts) {
var contents = fs.readFileSync(path),
container = window.document.createElement(containerTagName);
options = options || {};
var options = opts || {};
container.innerHTML = contents;
if (options.type) {
@ -17,9 +17,9 @@ module.exports = {
loadScript: function (scriptPath, window, options) {
this.loadFileToContainer(scriptPath, window, 'script', options);
},
loadTemplate: function (path, window, options) {
loadTemplate: function (path, window, opts) {
var w = window || global.window;
options = options || {};
var options = opts || {};
options.type = "text/x-handlebars-template";
this.loadScript("views/newsletter/templates/" + path, w, options);

View File

@ -28,7 +28,9 @@ if (!global.document || !global.window) {
}
global.testHelpers = require('./loadHelpers.js');
global.$ = global.jQuery = global.window.jQuery = require('jquery');
global.$ = require('jquery');
global.jQuery = require('jquery');
global.window.jQuery = require('jquery');
testHelpers.loadScript('tests/javascript/testBundles/vendor.js', global.window);
global.Handlebars = global.window.Handlebars;
@ -46,27 +48,28 @@ global.interact = function () {
styleCursor: global.interact
};
};
jQuery.fn.spectrum = global.spectrum = function() { return this; };
global.spectrum = function() { return this; };
jQuery.fn.spectrum = global.spectrum;
jQuery.fn.stick_in_parent = function() { return this; };
// Add global stubs for convenience
// TODO: Extract those to a separate file
global.stubChannel = function (EditorApplication, returnObject) {
EditorApplication.getChannel = sinon.stub().returns(_.defaults(returnObject || {}, {
var App = EditorApplication;
App.getChannel = sinon.stub().returns(_.defaults(returnObject || {}, {
trigger: function () {
},
on: function () {
}
}));
};
global.stubConfig = function (EditorApplication, config) {
config = config || {};
EditorApplication.getConfig = sinon.stub().returns(new Backbone.SuperModel(config));
global.stubConfig = function (EditorApplication, opts) {
var App = EditorApplication;
App.getConfig = sinon.stub().returns(new Backbone.SuperModel(opts || {}));
};
global.stubAvailableStyles = function (EditorApplication, styles) {
styles = styles || {};
EditorApplication.getAvailableStyles = sinon.stub().returns(new Backbone.SuperModel(styles));
var App = EditorApplication;
App.getAvailableStyles = sinon.stub().returns(new Backbone.SuperModel(styles || {}));
};
global.stubImage = function(defaultWidth, defaultHeight) {

View File

@ -5,12 +5,14 @@ define([
'amd-inject-loader!newsletter_editor/blocks/automatedLatestContent',
'newsletter_editor/components/communication'
], function(
EditorApplication,
App,
AutomatedLatestContentBlock,
ContainerBlock,
AutomatedLatestContentInjector,
CommunicationComponent
Communication
) {
var EditorApplication = App;
var CommunicationComponent = Communication;
describe('Automated Latest Content Supervisor', function() {
var model;

View File

@ -1,7 +1,8 @@
define([
'newsletter_editor/App',
'newsletter_editor/blocks/button'
], function(EditorApplication, ButtonBlock) {
], function(App, ButtonBlock) {
var EditorApplication = App;
describe("Button", function () {
describe("model", function () {

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