diff --git a/assets/js/src/form/fields/selection.jsx b/assets/js/src/form/fields/selection.jsx index 48fb7b1a28..f405a9dc3b 100644 --- a/assets/js/src/form/fields/selection.jsx +++ b/assets/js/src/form/fields/selection.jsx @@ -180,6 +180,7 @@ define([ + : + + }   @@ -180,6 +181,7 @@ define( diff --git a/lib/API/JSON/v1/Newsletters.php b/lib/API/JSON/v1/Newsletters.php index 05c3efcb15..282b8e5bb4 100644 --- a/lib/API/JSON/v1/Newsletters.php +++ b/lib/API/JSON/v1/Newsletters.php @@ -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 { ); } } -} \ No newline at end of file +} diff --git a/lib/Cron/Workers/SendingQueue/SendingQueue.php b/lib/Cron/Workers/SendingQueue/SendingQueue.php index 3a6aad8bb0..3cc475f37d 100644 --- a/lib/Cron/Workers/SendingQueue/SendingQueue.php +++ b/lib/Cron/Workers/SendingQueue/SendingQueue.php @@ -190,4 +190,4 @@ class SendingQueue { ->whereNull('type') ->findMany(); } -} \ No newline at end of file +} diff --git a/lib/Cron/Workers/SendingQueue/Tasks/Links.php b/lib/Cron/Workers/SendingQueue/Tasks/Links.php index 8ff2dc621d..f01780a423 100644 --- a/lib/Cron/Workers/SendingQueue/Tasks/Links.php +++ b/lib/Cron/Workers/SendingQueue/Tasks/Links.php @@ -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); diff --git a/lib/Cron/Workers/SendingQueue/Tasks/Newsletter.php b/lib/Cron/Workers/SendingQueue/Tasks/Newsletter.php index 5c7623a5de..4c3edd22f5 100644 --- a/lib/Cron/Workers/SendingQueue/Tasks/Newsletter.php +++ b/lib/Cron/Workers/SendingQueue/Tasks/Newsletter.php @@ -175,4 +175,4 @@ class Newsletter { __('There was an error processing your newsletter during sending. If possible, please contact us and report this issue.') ); } -} \ No newline at end of file +} diff --git a/lib/Newsletter/Links/Links.php b/lib/Newsletter/Links/Links.php index 990115dab1..6791b1f2a8 100644 --- a/lib/Newsletter/Links/Links.php +++ b/lib/Newsletter/Links/Links.php @@ -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; } -} \ No newline at end of file +} diff --git a/tests/unit/API/JSON/v1/NewslettersTest.php b/tests/unit/API/JSON/v1/NewslettersTest.php index 3dfa4343e8..7997da2d69 100644 --- a/tests/unit/API/JSON/v1/NewslettersTest.php +++ b/tests/unit/API/JSON/v1/NewslettersTest.php @@ -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', diff --git a/tests/unit/Cron/Workers/SendingQueue/Tasks/LinksTest.php b/tests/unit/Cron/Workers/SendingQueue/Tasks/LinksTest.php index 53b67dce1a..cdd05e235a 100644 --- a/tests/unit/Cron/Workers/SendingQueue/Tasks/LinksTest.php +++ b/tests/unit/Cron/Workers/SendingQueue/Tasks/LinksTest.php @@ -30,7 +30,7 @@ class LinksTest extends \MailPoetTest { 'html' => 'Example Link', 'text' => 'Example Link' ); - $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']) @@ -59,4 +59,4 @@ class LinksTest extends \MailPoetTest { \ORM::raw_execute('TRUNCATE ' . Newsletter::$_table); \ORM::raw_execute('TRUNCATE ' . NewsletterLink::$_table); } -} \ No newline at end of file +} diff --git a/tests/unit/Newsletter/Links/LinksTest.php b/tests/unit/Newsletter/Links/LinksTest.php index 01389a98f3..0eb8c2bbd8 100644 --- a/tests/unit/Newsletter/Links/LinksTest.php +++ b/tests/unit/Newsletter/Links/LinksTest.php @@ -24,7 +24,7 @@ class LinksTest extends \MailPoetTest { function testItOnlyHashesAndReplacesLinksInAnchorTags() { $template = ''; - $result = Links::process($template); + $result = Links::process($template, 0, 0); expect($result[0])->equals( sprintf( '', @@ -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 = ''; + $result = Links::process($template, 3, 3); + expect($result[0])->equals( + sprintf( + '', + 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 = 'some site [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 = 'some site'; - $result = Links::process($template); + $result = Links::process($template, 0, 0); expect($result[0])->equals( sprintf( 'some site', @@ -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(); diff --git a/views/newsletter/editor.html b/views/newsletter/editor.html index 36f54f4bfb..d862016647 100644 --- a/views/newsletter/editor.html +++ b/views/newsletter/editor.html @@ -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( diff --git a/views/newsletters.html b/views/newsletters.html index a8ad8e1c34..0908d63a14 100644 --- a/views/newsletters.html +++ b/views/newsletters.html @@ -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 %>