Merge pull request #1017 from mailpoet/rerender-newsletter

Rerender newsletter [MAILPOET-675]
This commit is contained in:
Tautvidas Sipavičius
2017-08-17 16:19:16 +03:00
committed by GitHub
14 changed files with 215 additions and 30 deletions

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

@ -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 }

View File

@ -168,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();
@ -193,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
@ -234,6 +269,15 @@ 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) => {
if (field.name == 'segments' || field.name == 'options') {
field.disabled = isPaused;
}
return field;
});
return (
<div>
<h1>{MailPoet.I18n.t('finalNewsletterStep')}</h1>
@ -242,13 +286,21 @@ 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">
{
isPaused ?
<input
className="button button-primary"
type="button"
onClick={ this.handleResume }
value={MailPoet.I18n.t('resume')} />
:
<input
className="button button-primary"
type="button"
@ -256,6 +308,7 @@ define(
value={MailPoet.I18n.t('send')}
{...this.getSendButtonOptions()}
/>
}
&nbsp;
<input
className="button button-secondary"

View File

@ -155,6 +155,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 +181,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 +237,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>
);
@ -292,6 +296,7 @@ define(
name="scheduledAt"
value={this._getCurrentValue().scheduledAt}
onChange={this.handleValueChange}
disabled={this.props.field.disabled}
dateValidation={this.getDateValidation()} />
&nbsp;
<span>
@ -300,7 +305,6 @@ define(
</span>
);
}
return (
<div>
<input
@ -308,6 +312,7 @@ define(
type="checkbox"
value="1"
checked={this.isScheduled()}
disabled={this.props.field.disabled}
name="isScheduled"
onChange={this.handleCheckboxChange} />

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());

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

@ -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;

View File

@ -99,6 +99,7 @@ class NewslettersTest extends \MailPoetTest {
Newsletter::findOne($this->newsletter->id)
->withSegments()
->withOptions()
->withSendingQueue()
->asArray()
);
$hook_name = 'mailpoet_api_newsletters_get_after';
@ -163,6 +164,31 @@ class NewslettersTest extends \MailPoetTest {
expect($updated_newsletter->subject)->equals('My Updated Newsletter');
}
function testItCanClearRenderedQueueUponSave() {
$sending_queue = SendingQueue::create();
$sending_queue->newsletter_id = $this->newsletter->id;
$sending_queue->status = SendingQueue::STATUS_SCHEDULED;
$sending_queue->newsletter_rendered_body = 'Rendered body ...';
$sending_queue->newsletter_rendered_subject = 'Rendered subject ...';
$sending_queue->save();
$router = new Newsletters();
$newsletter_data = array(
'id' => $this->newsletter->id,
'subject' => 'My Updated Newsletter'
);
$response = $router->save($newsletter_data);
$updated_newsletter = Newsletter::findOne($this->newsletter->id)
->withSendingQueue()
->asArray();
$updated_queue = $updated_newsletter['queue'];
expect($response->status)->equals(APIResponse::STATUS_OK);
expect(unserialize($updated_queue['newsletter_rendered_body']))->equals(null);
expect(unserialize($updated_queue['newsletter_rendered_subject']))->equals(null);
}
function testItCanUpdatePostNotificationScheduleUponSave() {
$newsletter_options = array(
'intervalType',

View File

@ -30,7 +30,7 @@ class LinksTest extends \MailPoetTest {
'html' => '<a href="http://example.com">Example Link</a>',
'text' => '<a href="http://example.com">Example Link</a>'
);
$result = Links::hashAndReplaceLinks($rendered_newsletter);
$result = Links::hashAndReplaceLinks($rendered_newsletter, 0, 0);
$processed_rendered_newsletter_body = $result[0];
$processed_and_hashed_links = $result[1];
expect($processed_rendered_newsletter_body['html'])

View File

@ -24,7 +24,7 @@ class LinksTest extends \MailPoetTest {
function testItOnlyHashesAndReplacesLinksInAnchorTags() {
$template = '<a href="http://example.com"><img src="http://example.com" /></a>';
$result = Links::process($template);
$result = Links::process($template, 0, 0);
expect($result[0])->equals(
sprintf(
'<a href="%s-%s"><img src="http://example.com" /></a>',
@ -34,6 +34,25 @@ class LinksTest extends \MailPoetTest {
);
}
function testItDoesNotRehashExistingLinks() {
$link = NewsletterLink::create();
$link->newsletter_id = 3;
$link->queue_id = 3;
$link->hash = 123;
$link->url = 'http://example.com';
$link->save();
$template = '<a href="http://example.com"><img src="http://example.com" /></a>';
$result = Links::process($template, 3, 3);
expect($result[0])->equals(
sprintf(
'<a href="%s-%s"><img src="http://example.com" /></a>',
Links::DATA_TAG_CLICK,
123
)
);
}
function testItCanExtactLinkShortcodes() {
$template = '[notlink:shortcode] [link:some_link_shortcode]';
$result = Links::extract($template);
@ -48,7 +67,7 @@ class LinksTest extends \MailPoetTest {
function testItHashesAndReplacesLinks() {
$template = '<a href="http://example.com">some site</a> [link:some_link_shortcode]';
list($updated_content, $hashed_links) = Links::process($template);
list($updated_content, $hashed_links) = Links::process($template, 0, 0);
// 2 links were hashed
expect(count($hashed_links))->equals(2);
@ -63,7 +82,7 @@ class LinksTest extends \MailPoetTest {
function testItHashesAndReplacesLinksWithSpecialCharacters() {
$template = '<a href="http://сайт.cóm/彌撒時間">some site</a>';
$result = Links::process($template);
$result = Links::process($template, 0, 0);
expect($result[0])->equals(
sprintf(
'<a href="%s-%s">some site</a>',
@ -171,6 +190,28 @@ class LinksTest extends \MailPoetTest {
expect($newsltter_link->url)->equals('http://example.com');
}
function testItCanLoadLinks() {
$link = NewsletterLink::create();
$link->newsletter_id = 1;
$link->queue_id = 2;
$link->hash = 123;
$link->url = 'http://example.com';
$link->save();
$link = NewsletterLink::create();
$link->newsletter_id = 1;
$link->queue_id = 3;
$link->hash = 456;
$link->url = 'http://demo.com';
$link->save();
$links = Links::load(1, 2);
expect(is_array($links))->true();
expect(count($links))->equals(1);
expect($links['http://example.com']['hash'])->equals(123);
expect($links['http://example.com']['url'])->equals('http://example.com');
}
function testItMatchesHashedLinks() {
$regex = Links::getLinkRegex();
expect((boolean)preg_match($regex, '[some_tag]-123'))->false();

View File

@ -350,6 +350,7 @@
'unsavedChangesWillBeLost': __('There are unsaved changes which will be lost if you leave this page.'),
'selectColor': _x('Select', 'select color'),
'cancelColorSelection': _x('Cancel', 'cancel color selection'),
'newsletterIsPaused': __('Email sending has been paused.'),
}) %>
<% endblock %>
@ -1223,6 +1224,26 @@
newsletter: response.data,
config: config,
});
var queue = response.data.queue;
if (response.data.status == 'sending' && queue && queue.status === null) {
MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: 'sending_queue',
action: 'pause',
data: {
newsletter_id: response.data.id
}
}).done(response =>
MailPoet.Notice.success(MailPoet.I18n.t('newsletterIsPaused'))
).fail(function(response) {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true, static: true }
);
}
});
}
}).fail(function(response) {
if (response.errors.length > 0) {
MailPoet.Notice.error(

View File

@ -231,6 +231,7 @@
'previous': __('Previous'),
'newsletterBeingSent': __('The newsletter is being sent...'),
'newsletterHasBeenScheduled': __('The newsletter has been scheduled.'),
'newsletterSendingHasBeenResumed': __('The newsletter sending has been resumed.'),
'welcomeEmailActivated': __('Your Welcome Email is now activated!'),
'welcomeEmailActivationFailed': __('Your Welcome Email could not be activated, please check the settings.'),
'postNotificationActivated': __('Your post notification is now active!'),
@ -256,7 +257,8 @@
'mailerConnectionErrorNotice': __('Sending is paused because the following connection issue prevents MailPoet from delivering emails: %$1s'),
'mailerCheckSettingsNotice': __('Check your [link]sending method settings[/link].'),
'mailerResumeSendingButton': __('Resume sending'),
'mailerSendingResumedNotice': __('Sending has been resumed.')
'mailerSendingResumedNotice': __('Sending has been resumed.'),
'confirmEdit': __('Sending is in progress. Do you want to pause sending and edit the newsletter?')
}) %>
<% endblock %>