Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
8ff02878e2 | |||
82a4681cd6 | |||
7035d92815 | |||
cc5a4d0aac | |||
fd4af798f1 | |||
bf03dd105f | |||
013ab522c6 | |||
3e9be631e4 | |||
30538f9f9b | |||
428b7a1363 | |||
ddcc6c03a0 | |||
f8306635a7 | |||
8c10896bc8 | |||
8ee8498bd5 | |||
a2ce279e23 | |||
43c4a24ab3 | |||
f2382d25bd |
@ -265,20 +265,24 @@ define([
|
||||
}
|
||||
},
|
||||
validateNewsletter: function (jsonObject) {
|
||||
var body = '';
|
||||
var contents;
|
||||
if (!App._contentContainer.isValid()) {
|
||||
this.showValidationError(App._contentContainer.validationError);
|
||||
return;
|
||||
}
|
||||
|
||||
contents = JSON.stringify(jsonObject);
|
||||
if (jsonObject && jsonObject.body && jsonObject.body.content) {
|
||||
body = JSON.stringify(jsonObject.body.content);
|
||||
}
|
||||
if (App.getConfig().get('validation.validateUnsubscribeLinkPresent') &&
|
||||
contents.indexOf('[link:subscription_unsubscribe_url]') < 0 &&
|
||||
contents.indexOf('[link:subscription_unsubscribe]') < 0) {
|
||||
body.indexOf('[link:subscription_unsubscribe_url]') < 0 &&
|
||||
body.indexOf('[link:subscription_unsubscribe]') < 0) {
|
||||
this.showValidationError(MailPoet.I18n.t('unsubscribeLinkMissing'));
|
||||
return;
|
||||
}
|
||||
|
||||
contents = JSON.stringify(jsonObject);
|
||||
if ((App.getNewsletter().get('type') === 'notification') &&
|
||||
contents.indexOf('"type":"automatedLatestContent"') < 0 &&
|
||||
contents.indexOf('"type":"automatedLatestContentLayout"') < 0
|
||||
|
@ -113,6 +113,28 @@ let newsletterActions = [
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'duplicate',
|
||||
label: MailPoet.I18n.t('duplicate'),
|
||||
onClick: (newsletter, refresh) => MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletters',
|
||||
action: 'duplicate',
|
||||
data: {
|
||||
id: newsletter.id,
|
||||
},
|
||||
}).done((response) => {
|
||||
MailPoet.Notice.success((MailPoet.I18n.t('newsletterDuplicated')).replace('%$1s', response.data.subject));
|
||||
refresh();
|
||||
}).fail((response) => {
|
||||
if (response.errors.length > 0) {
|
||||
MailPoet.Notice.error(
|
||||
response.errors.map(error => error.message),
|
||||
{ scroll: true }
|
||||
);
|
||||
}
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'edit',
|
||||
link: function link(newsletter) {
|
||||
|
@ -10,6 +10,7 @@ import NewsletterTemplates from 'newsletters/templates.jsx';
|
||||
import NewsletterSend from 'newsletters/send.jsx';
|
||||
import NewsletterTypeStandard from 'newsletters/types/standard.jsx';
|
||||
import NewsletterTypeNotification from 'newsletters/types/notification/notification.jsx';
|
||||
import NewsletterTypeWelcome from 'newsletters/types/welcome/welcome.jsx';
|
||||
import AutomaticEmailEventsList from 'newsletters/types/automatic_emails/events_list.jsx';
|
||||
import NewsletterListStandard from 'newsletters/listings/standard.jsx';
|
||||
import NewsletterListWelcome from 'newsletters/listings/welcome.jsx';
|
||||
@ -73,6 +74,10 @@ if (container) {
|
||||
path: 'new/notification',
|
||||
component: NewsletterTypeNotification,
|
||||
},
|
||||
{
|
||||
path: 'new/welcome',
|
||||
component: NewsletterTypeWelcome,
|
||||
},
|
||||
/* Template selection */
|
||||
{
|
||||
name: 'template',
|
||||
|
@ -67,6 +67,7 @@ const NewsletterTypes = React.createClass({
|
||||
render: function render() {
|
||||
const createStandardNewsletter = _.partial(this.createNewsletter, 'standard');
|
||||
const createNotificationNewsletter = _.partial(this.setupNewsletter, 'notification');
|
||||
const createWelcomeNewsletter = _.partial(this.setupNewsletter, 'welcome');
|
||||
const defaultTypes = [
|
||||
{
|
||||
slug: 'standard',
|
||||
@ -93,11 +94,22 @@ const NewsletterTypes = React.createClass({
|
||||
videoGuide: 'https://beta.docs.mailpoet.com/article/254-video-guide-to-welcome-emails',
|
||||
action: (function action() {
|
||||
return (
|
||||
<div>
|
||||
<a href="?page=mailpoet-premium" target="_blank">
|
||||
{MailPoet.I18n.t('premiumFeatureLink')}
|
||||
<a
|
||||
className="button button-primary"
|
||||
onClick={createWelcomeNewsletter}
|
||||
data-automation-id="create_welcome"
|
||||
onKeyDown={(event) => {
|
||||
if ((['keydown', 'keypress'].includes(event.type) && ['Enter', ' '].includes(event.key))
|
||||
) {
|
||||
event.preventDefault();
|
||||
createWelcomeNewsletter();
|
||||
}
|
||||
}}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
{MailPoet.I18n.t('setUp')}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}()),
|
||||
},
|
||||
|
104
assets/js/src/newsletters/types/welcome/welcome.jsx
Normal file
104
assets/js/src/newsletters/types/welcome/welcome.jsx
Normal file
@ -0,0 +1,104 @@
|
||||
import React from 'react';
|
||||
import _ from 'underscore';
|
||||
import MailPoet from 'mailpoet';
|
||||
import PropTypes from 'prop-types';
|
||||
import Breadcrumb from '../../breadcrumb.jsx';
|
||||
import WelcomeScheduling from './scheduling.jsx';
|
||||
|
||||
const field = {
|
||||
name: 'options',
|
||||
label: 'Event',
|
||||
type: 'reactComponent',
|
||||
component: WelcomeScheduling,
|
||||
};
|
||||
|
||||
class NewsletterWelcome extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
let availableSegments = window.mailpoet_segments || [];
|
||||
let defaultSegment = 1;
|
||||
availableSegments = availableSegments.filter(segment => segment.type === 'default');
|
||||
|
||||
if (_.size(availableSegments) > 0) {
|
||||
defaultSegment = _.first(availableSegments).id;
|
||||
}
|
||||
|
||||
this.state = {
|
||||
options: {
|
||||
event: 'segment',
|
||||
segment: defaultSegment,
|
||||
role: 'subscriber',
|
||||
afterTimeNumber: 1,
|
||||
afterTimeType: 'immediate',
|
||||
},
|
||||
};
|
||||
|
||||
this.handleValueChange = this.handleValueChange.bind(this);
|
||||
this.handleNext = this.handleNext.bind(this);
|
||||
}
|
||||
|
||||
handleValueChange(event) {
|
||||
const { state } = this;
|
||||
state[event.target.name] = event.target.value;
|
||||
this.setState(state);
|
||||
}
|
||||
|
||||
handleNext() {
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'newsletters',
|
||||
action: 'create',
|
||||
data: _.extend({}, this.state, {
|
||||
type: 'welcome',
|
||||
subject: MailPoet.I18n.t('draftNewsletterTitle'),
|
||||
}),
|
||||
}).done((response) => {
|
||||
this.showTemplateSelection(response.data.id);
|
||||
}).fail((response) => {
|
||||
if (response.errors.length > 0) {
|
||||
MailPoet.Notice.error(
|
||||
response.errors.map(error => error.message),
|
||||
{ scroll: true }
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
showTemplateSelection(newsletterId) {
|
||||
this.props.router.push(`/template/${newsletterId}`);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>{MailPoet.I18n.t('welcomeNewsletterTypeTitle')}</h1>
|
||||
<Breadcrumb step="type" />
|
||||
|
||||
<h3>{MailPoet.I18n.t('selectEventToSendWelcomeEmail')}</h3>
|
||||
|
||||
<WelcomeScheduling
|
||||
item={this.state}
|
||||
field={field}
|
||||
onValueChange={this.handleValueChange}
|
||||
/>
|
||||
|
||||
<p className="submit">
|
||||
<input
|
||||
className="button button-primary"
|
||||
type="button"
|
||||
onClick={this.handleNext}
|
||||
value={MailPoet.I18n.t('next')}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NewsletterWelcome.propTypes = {
|
||||
router: PropTypes.shape({
|
||||
push: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default NewsletterWelcome;
|
@ -12,8 +12,9 @@ class Localizer {
|
||||
|
||||
function loadGlobalText() {
|
||||
$language_path = sprintf(
|
||||
'%s/%s.mo',
|
||||
'%s/%s-%s.mo',
|
||||
Env::$languages_path,
|
||||
Env::$plugin_name,
|
||||
$this->locale()
|
||||
);
|
||||
load_textdomain(Env::$plugin_name, $language_path);
|
||||
|
@ -40,7 +40,12 @@ class Renderer {
|
||||
$rendered_block_element = $_this->createElementFromBlockType($block, $column_count);
|
||||
if(isset($block['blocks'])) {
|
||||
$rendered_block_element = $_this->render($block, $column_count);
|
||||
// nested vertical column container is rendered as an array
|
||||
if(is_array($rendered_block_element)) {
|
||||
$rendered_block_element = implode('', $rendered_block_element);
|
||||
}
|
||||
}
|
||||
|
||||
// vertical orientation denotes column container
|
||||
if($block['type'] === 'container' && $block['orientation'] === 'vertical') {
|
||||
$column_content[] = $rendered_block_element;
|
||||
|
@ -4,7 +4,7 @@ if(!defined('ABSPATH')) exit;
|
||||
|
||||
/*
|
||||
* Plugin Name: MailPoet 3 (New)
|
||||
* Version: 3.9.1
|
||||
* Version: 3.10
|
||||
* Plugin URI: http://www.mailpoet.com
|
||||
* Description: Create and send newsletters, post notifications and welcome emails from your WordPress.
|
||||
* Author: MailPoet
|
||||
@ -18,7 +18,7 @@ if(!defined('ABSPATH')) exit;
|
||||
*/
|
||||
|
||||
$mailpoet_plugin = array(
|
||||
'version' => '3.9.1',
|
||||
'version' => '3.10',
|
||||
'filename' => __FILE__,
|
||||
'path' => dirname(__FILE__),
|
||||
'autoloader' => dirname(__FILE__) . '/vendor/autoload.php',
|
||||
|
36
readme.txt
36
readme.txt
@ -3,7 +3,7 @@ Contributors: mailpoet, wysija, kgjerstad
|
||||
Tags: newsletter, newsletter subscribers, email, welcome email, post notification, WooCommerce emails, newsletter builder, mailing list
|
||||
Requires at least: 4.7
|
||||
Tested up to: 4.9
|
||||
Stable tag: 3.9.1
|
||||
Stable tag: 3.10
|
||||
License: GPLv3
|
||||
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
@ -162,24 +162,28 @@ Stop by our [support site](https://www.mailpoet.com/support).
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 3.10 - 2018-09-11 =
|
||||
* Changed: welcome emails to new subscribers are now free for everyone!
|
||||
* Fixed: newsletter footer warning to be displayed if unsubscribe link is missing.
|
||||
|
||||
= 3.9.1 - 2018-09-04 =
|
||||
Improved: instructions for migrating from MP2 to MP3 clarified;
|
||||
Improved: minor style adjustments for migration tool;
|
||||
Improved: minor fixes to onboarding intro guide;
|
||||
Improved: template page loading times decreased;
|
||||
Fixed: resolved javascript warnings on help page status;
|
||||
Fixed: subscriber status remains persistent after migration from MP2 to MP3 without sign-up confirmation enabled;
|
||||
* Improved: instructions for migrating from MP2 to MP3 clarified;
|
||||
* Improved: minor style adjustments for migration tool;
|
||||
* Improved: minor fixes to onboarding intro guide;
|
||||
* Improved: template page loading times decreased;
|
||||
* Fixed: resolved javascript warnings on help page status;
|
||||
* Fixed: subscriber status remains persistent after migration from MP2 to MP3 without sign-up confirmation enabled;
|
||||
|
||||
= 3.9.0 - 2018-08-28 =
|
||||
Improved: email processing in sending queues is now more resilient to invalid data. Thanks Tara!
|
||||
Fixed: replaced WooCommerce image in welcome wizard;
|
||||
Fixed: swapped video in welcome wizard with an updated one;
|
||||
Fixed: welcome wizard button displays properly for all users;
|
||||
Fixed: permission error when bypassing data import after new install or reset;
|
||||
Fixed: added indexes to some foreign keys which were missing;
|
||||
Fixed: error displaying number of exported users;
|
||||
Fixed: export search function restored;
|
||||
Fixed: prevent third party APIs from adding data incorrectly via MailPoets API.
|
||||
* Improved: email processing in sending queues is now more resilient to invalid data. Thanks Tara!
|
||||
* Fixed: replaced WooCommerce image in welcome wizard;
|
||||
* Fixed: swapped video in welcome wizard with an updated one;
|
||||
* Fixed: welcome wizard button displays properly for all users;
|
||||
* Fixed: permission error when bypassing data import after new install or reset;
|
||||
* Fixed: added indexes to some foreign keys which were missing;
|
||||
* Fixed: error displaying number of exported users;
|
||||
* Fixed: export search function restored;
|
||||
* Fixed: prevent third party APIs from adding data incorrectly via MailPoets API.
|
||||
|
||||
= 3.8.6 - 2018-08-21 =
|
||||
* Improved: compatibility with caching plugins
|
||||
|
38
tests/acceptance/ScheduleNewsletterCest.php
Normal file
38
tests/acceptance/ScheduleNewsletterCest.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace MailPoet\Test\Acceptance;
|
||||
|
||||
use MailPoet\Test\DataFactories\Newsletter;
|
||||
|
||||
require_once __DIR__ . '/../DataFactories/Newsletter.php';
|
||||
|
||||
class ScheduleNewsletterCest {
|
||||
function scheduleStandardNewsletter(\AcceptanceTester $I) {
|
||||
$I->wantTo('Schedule a newsletter');
|
||||
$newsletter_title = 'Schedule Test Newsletter';
|
||||
|
||||
// step 1 - Prepare post notification data
|
||||
$newsletterFactory = new Newsletter();
|
||||
$newsletter = $newsletterFactory->withSubject($newsletter_title)
|
||||
->withType('standard')
|
||||
->create();
|
||||
|
||||
// step 2 - Go to editor
|
||||
$I->login();
|
||||
$I->amEditingNewsletter($newsletter->id);
|
||||
$I->click('Next');
|
||||
|
||||
// step 4 - Choose list and schedule
|
||||
$I->waitForElement('[data-automation-id="newsletter_send_form"]');
|
||||
$I->seeInCurrentUrl('mailpoet-newsletters#/send/');
|
||||
$search_field_element = 'input.select2-search__field';
|
||||
$I->fillField($search_field_element, 'WordPress Users');
|
||||
$I->pressKey($search_field_element, \WebDriverKeys::ENTER);
|
||||
$I->checkOption('isScheduled');
|
||||
$I->click('select[name=time]');
|
||||
$I->selectOption('form select[name=time]', '6:00');
|
||||
$I->click('Schedule');
|
||||
$I->wait(30);
|
||||
$I->seeInCurrentUrl('mailpoet-newsletters');
|
||||
|
||||
}
|
||||
}
|
35
tests/acceptance/SearchForStandardNewsletterCest.php
Normal file
35
tests/acceptance/SearchForStandardNewsletterCest.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace MailPoet\Test\Acceptance;
|
||||
|
||||
use MailPoet\Test\DataFactories\Newsletter;
|
||||
|
||||
require_once __DIR__ . '/../DataFactories/Newsletter.php';
|
||||
|
||||
class SearchForStandardNewsletterCest {
|
||||
function searchForStandardNewsletter(\AcceptanceTester $I) {
|
||||
$I->wantTo('Successfully search for an existing newsletter');
|
||||
|
||||
$newsletter_title = 'Search Test Newsletter';
|
||||
$failure_condition_newsletter = 'Not Actually Real';
|
||||
|
||||
|
||||
// step 1 - Prepare newsletter data
|
||||
$newsletterFactory = new Newsletter();
|
||||
$newsletter = $newsletterFactory->withSubject($newsletter_title)
|
||||
->withType('standard')
|
||||
->create();
|
||||
|
||||
// step 2 - Search
|
||||
$I->login();
|
||||
$I->amOnMailpoetPage('Emails');
|
||||
$I->fillField('#search_input', $failure_condition_newsletter);
|
||||
$I->click('Search');
|
||||
$I->wait(5);
|
||||
$I->dontSee($newsletter_title);
|
||||
$I->fillField('#search_input', $newsletter_title);
|
||||
$I->click('Search');
|
||||
$I->waitForText($newsletter_title, 20);
|
||||
}
|
||||
|
||||
}
|
@ -45,6 +45,9 @@ class RendererTest extends \MailPoetTest {
|
||||
expect(count($DOM('.mailpoet_cols-one')))->equals(2);
|
||||
expect(count($DOM('.mailpoet_cols-two')))->equals(2);
|
||||
expect(count($DOM('.mailpoet_cols-three')))->equals(3);
|
||||
|
||||
// nested vertical container should be rendered
|
||||
expect(count($DOM('.nested-vertical-container')))->equals(1);
|
||||
}
|
||||
|
||||
function testItRendersOneColumn() {
|
||||
|
@ -211,6 +211,21 @@
|
||||
{
|
||||
"type": "text",
|
||||
"text": "<p>Test trailing line breaks removal</p>\n<h3>Test line breaks <br/> in headings <a href=\"http://www.example.org\">haha</a></h3><br /><br><br/>"
|
||||
},
|
||||
{
|
||||
"type": "container",
|
||||
"orientation": "vertical",
|
||||
"styles": {
|
||||
"block": {
|
||||
"backgroundColor": "transparent"
|
||||
}
|
||||
},
|
||||
"blocks": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "<p class='nested-vertical-container'>Nested vertical container content</p>"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -165,13 +165,6 @@ var baseConfig = {
|
||||
'babel-loader',
|
||||
]
|
||||
},
|
||||
{
|
||||
include: path.resolve(__dirname, 'assets/js/src/newsletters/types/welcome/scheduling.jsx'),
|
||||
use: [
|
||||
'expose-loader?' + globalPrefix + '.NewsletterWelcomeNotificationScheduling',
|
||||
'babel-loader',
|
||||
]
|
||||
},
|
||||
{
|
||||
include: path.resolve(__dirname, 'assets/js/src/newsletters/breadcrumb.jsx'),
|
||||
use: [
|
||||
|
Reference in New Issue
Block a user