Merge pull request #1017 from mailpoet/rerender-newsletter
Rerender newsletter [MAILPOET-675]
This commit is contained in:
@ -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() }
|
||||
|
@ -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 }
|
||||
|
@ -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()}
|
||||
/>
|
||||
}
|
||||
|
||||
<input
|
||||
className="button button-secondary"
|
||||
|
@ -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()} />
|
||||
|
||||
<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} />
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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',
|
||||
|
@ -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'])
|
||||
|
@ -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();
|
||||
|
@ -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(
|
||||
|
@ -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 %>
|
||||
|
||||
|
Reference in New Issue
Block a user