Add abandoned cart block rendering in email [MAILPOET-2979]

This commit is contained in:
wxa
2020-10-01 17:24:59 +03:00
committed by Veljko V
parent 791fedff96
commit 52b84296b3
7 changed files with 79 additions and 12 deletions

View File

@@ -13,6 +13,7 @@ use MailPoet\WP\Functions as WPFunctions;
class AbandonedCart { class AbandonedCart {
const SLUG = 'woocommerce_abandoned_shopping_cart'; const SLUG = 'woocommerce_abandoned_shopping_cart';
const LAST_VISIT_TIMESTAMP_OPTION_NAME = 'mailpoet_last_visit_timestamp'; const LAST_VISIT_TIMESTAMP_OPTION_NAME = 'mailpoet_last_visit_timestamp';
const TASK_META_NAME = 'cart_product_ids';
/** @var WPFunctions */ /** @var WPFunctions */
private $wp; private $wp;
@@ -130,7 +131,7 @@ class AbandonedCart {
public function handleCartChange() { public function handleCartChange() {
$cart = $this->wooCommerceHelper->WC()->cart; $cart = $this->wooCommerceHelper->WC()->cart;
if ($cart && !$cart->is_empty()) { if ($cart && !$cart->is_empty()) {
$this->scheduleAbandonedCartEmail(); $this->scheduleAbandonedCartEmail($this->getCartProductIds($cart));
} else { } else {
$this->cancelAbandonedCartEmail(); $this->cancelAbandonedCartEmail();
$this->pageVisitTracker->stopTracking(); $this->pageVisitTracker->stopTracking();
@@ -145,13 +146,19 @@ class AbandonedCart {
}); });
} }
private function scheduleAbandonedCartEmail() { private function getCartProductIds($cart) {
$cartItems = $cart->get_cart() ?: [];
return array_column($cartItems, 'product_id');
}
private function scheduleAbandonedCartEmail(array $cartProductIds = []) {
$subscriber = $this->getSubscriber(); $subscriber = $this->getSubscriber();
if (!$subscriber || $subscriber->status !== Subscriber::STATUS_SUBSCRIBED) { if (!$subscriber || $subscriber->status !== Subscriber::STATUS_SUBSCRIBED) {
return; return;
} }
$this->scheduler->scheduleOrRescheduleAutomaticEmail(WooCommerceEmail::SLUG, self::SLUG, $subscriber->id); $meta = [self::TASK_META_NAME => $cartProductIds];
$this->scheduler->scheduleOrRescheduleAutomaticEmail(WooCommerceEmail::SLUG, self::SLUG, $subscriber->id, $meta);
// start tracking page visits to detect inactivity // start tracking page visits to detect inactivity
$this->pageVisitTracker->startTracking(); $this->pageVisitTracker->startTracking();

View File

@@ -114,7 +114,7 @@ class Newsletter {
// hook to the newsletter post-processing filter and add tracking image // hook to the newsletter post-processing filter and add tracking image
$this->trackingImageInserted = OpenTracking::addTrackingImage(); $this->trackingImageInserted = OpenTracking::addTrackingImage();
// render newsletter // render newsletter
$renderedNewsletter = $this->renderer->render($newsletter); $renderedNewsletter = $this->renderer->render($newsletter, false, false, $sendingTask);
$renderedNewsletter = $this->wp->applyFilters( $renderedNewsletter = $this->wp->applyFilters(
'mailpoet_sending_newsletter_render_after', 'mailpoet_sending_newsletter_render_after',
$renderedNewsletter, $renderedNewsletter,

View File

@@ -2,13 +2,17 @@
namespace MailPoet\Newsletter\Renderer\Blocks; namespace MailPoet\Newsletter\Renderer\Blocks;
use MailPoet\AutomaticEmails\WooCommerce\Events\AbandonedCart;
use MailPoet\AutomaticEmails\WooCommerce\WooCommerce as WooCommerceEmail;
use MailPoet\Entities\NewsletterEntity; use MailPoet\Entities\NewsletterEntity;
use MailPoet\Entities\NewsletterOptionEntity;
use MailPoet\Entities\NewsletterPostEntity; use MailPoet\Entities\NewsletterPostEntity;
use MailPoet\Models\Newsletter; use MailPoet\Models\Newsletter;
use MailPoet\Newsletter\AutomatedLatestContent; use MailPoet\Newsletter\AutomatedLatestContent;
use MailPoet\Newsletter\NewsletterPostsRepository; use MailPoet\Newsletter\NewsletterPostsRepository;
use MailPoet\Newsletter\Renderer\Columns\ColumnsHelper; use MailPoet\Newsletter\Renderer\Columns\ColumnsHelper;
use MailPoet\Newsletter\Renderer\StylesHelper; use MailPoet\Newsletter\Renderer\StylesHelper;
use MailPoet\Tasks\Sending as SendingTask;
class Renderer { class Renderer {
/** /**
@@ -163,6 +167,48 @@ class Renderer {
return $this->renderBlocksInColumn($newsletter, $transformedPosts, $columnBaseWidth); return $this->renderBlocksInColumn($newsletter, $transformedPosts, $columnBaseWidth);
} }
public function abandonedCartContentTransformedProducts(
NewsletterEntity $newsletter,
array $args,
bool $preview = false,
SendingTask $sendingTask = null
): array {
if ($newsletter->getType() !== NewsletterEntity::TYPE_AUTOMATIC) {
// Do not display the block if not an automatic email
return [];
}
$groupOption = $newsletter->getOptions()->filter(function (NewsletterOptionEntity $newsletterOption) {
$optionField = $newsletterOption->getOptionField();
return $optionField && $optionField->getName() === 'group';
})->first();
$eventOption = $newsletter->getOptions()->filter(function (NewsletterOptionEntity $newsletterOption) {
$optionField = $newsletterOption->getOptionField();
return $optionField && $optionField->getName() === 'event';
})->first();
if ($groupOption->getValue() !== WooCommerceEmail::SLUG
|| $eventOption->getValue() !== AbandonedCart::SLUG
) {
// Do not display the block if not an AbandonedCart email
return [];
}
if ($preview) {
// Display latest products for preview (no 'posts' argument specified)
return $this->automatedLatestContentTransformedPosts($newsletter, $args);
}
if (!($sendingTask instanceof SendingTask)) {
// Do not display the block if we're not sending an email
return [];
}
$meta = $sendingTask->getMeta();
if (empty($meta[AbandonedCart::TASK_META_NAME])) {
// Do not display the block if a cart is empty
return [];
}
$args['amount'] = 50;
$args['posts'] = $meta[AbandonedCart::TASK_META_NAME];
return $this->automatedLatestContentTransformedPosts($newsletter, $args);
}
private function getRenderedPosts(int $newsletterId) { private function getRenderedPosts(int $newsletterId) {
return $this->renderedPostsInNewsletter[$newsletterId] ?? []; return $this->renderedPostsInNewsletter[$newsletterId] ?? [];
} }

View File

@@ -5,6 +5,7 @@ namespace MailPoet\Newsletter\Renderer;
use MailPoet\Entities\NewsletterEntity; use MailPoet\Entities\NewsletterEntity;
use MailPoet\Newsletter\Editor\LayoutHelper; use MailPoet\Newsletter\Editor\LayoutHelper;
use MailPoet\Newsletter\Renderer\Blocks\Renderer as BlocksRenderer; use MailPoet\Newsletter\Renderer\Blocks\Renderer as BlocksRenderer;
use MailPoet\Tasks\Sending as SendingTask;
use MailPoet\WooCommerce\TransactionalEmails; use MailPoet\WooCommerce\TransactionalEmails;
class Preprocessor { class Preprocessor {
@@ -36,20 +37,25 @@ class Preprocessor {
* @param NewsletterEntity $newsletter * @param NewsletterEntity $newsletter
* @return array * @return array
*/ */
public function process(NewsletterEntity $newsletter, $content) { public function process(NewsletterEntity $newsletter, $content, bool $preview = false, SendingTask $sendingTask = null) {
if (!array_key_exists('blocks', $content)) { if (!array_key_exists('blocks', $content)) {
return $content; return $content;
} }
$blocks = []; $blocks = [];
foreach ($content['blocks'] as $block) { foreach ($content['blocks'] as $block) {
$blocks = array_merge($blocks, $this->processBlock($newsletter, $block)); $processedBlock = $this->processBlock($newsletter, $block, $preview, $sendingTask);
if (!empty($processedBlock)) {
$blocks = array_merge($blocks, $processedBlock);
}
} }
$content['blocks'] = $blocks; $content['blocks'] = $blocks;
return $content; return $content;
} }
public function processBlock(NewsletterEntity $newsletter, array $block): array { public function processBlock(NewsletterEntity $newsletter, array $block, bool $preview = false, SendingTask $sendingTask = null): array {
switch ($block['type']) { switch ($block['type']) {
case 'abandonedCartContent':
return $this->blocksRenderer->abandonedCartContentTransformedProducts($newsletter, $block, $preview, $sendingTask);
case 'automatedLatestContentLayout': case 'automatedLatestContentLayout':
return $this->blocksRenderer->automatedLatestContentTransformedPosts($newsletter, $block); return $this->blocksRenderer->automatedLatestContentTransformedPosts($newsletter, $block);
case 'woocommerceHeading': case 'woocommerceHeading':

View File

@@ -9,6 +9,7 @@ use MailPoet\Newsletter\NewslettersRepository;
use MailPoet\Newsletter\Renderer\EscapeHelper as EHelper; use MailPoet\Newsletter\Renderer\EscapeHelper as EHelper;
use MailPoet\RuntimeException; use MailPoet\RuntimeException;
use MailPoet\Services\Bridge; use MailPoet\Services\Bridge;
use MailPoet\Tasks\Sending as SendingTask;
use MailPoet\Util\License\License; use MailPoet\Util\License\License;
use MailPoet\Util\pQuery\DomNode; use MailPoet\Util\pQuery\DomNode;
use MailPoet\WP\Functions as WPFunctions; use MailPoet\WP\Functions as WPFunctions;
@@ -69,7 +70,7 @@ class Renderer {
return $this->newslettersRepository->findOneById($newsletterId); return $this->newslettersRepository->findOneById($newsletterId);
} }
public function render($newsletter, $preview = false, $type = false) { public function render($newsletter, $preview = false, $type = false, SendingTask $sendingTask = null) {
$newsletter = $this->getNewsletter($newsletter); $newsletter = $this->getNewsletter($newsletter);
if (!$newsletter instanceof NewsletterEntity) { if (!$newsletter instanceof NewsletterEntity) {
throw new RuntimeException('Newsletter was not found'); throw new RuntimeException('Newsletter was not found');
@@ -92,7 +93,7 @@ class Renderer {
$content = $this->addMailpoetLogoContentBlock($content, $styles); $content = $this->addMailpoetLogoContentBlock($content, $styles);
} }
$content = $this->preprocessor->process($newsletter, $content); $content = $this->preprocessor->process($newsletter, $content, $preview, $sendingTask);
$renderedBody = $this->renderBody($newsletter, $content); $renderedBody = $this->renderBody($newsletter, $content);
$renderedStyles = $this->renderStyles($styles); $renderedStyles = $this->renderStyles($styles);
$customFontsLinks = StylesHelper::getCustomFontsLinks($styles); $customFontsLinks = StylesHelper::getCustomFontsLinks($styles);

View File

@@ -33,7 +33,7 @@ class AutomaticEmailScheduler {
// try to find existing scheduled task for given subscriber // try to find existing scheduled task for given subscriber
$task = ScheduledTask::findOneScheduledByNewsletterIdAndSubscriberId($newsletter->id, $subscriberId); $task = ScheduledTask::findOneScheduledByNewsletterIdAndSubscriberId($newsletter->id, $subscriberId);
if ($task) { if ($task) {
$this->rescheduleAutomaticEmailSendingTask($newsletter, $task); $this->rescheduleAutomaticEmailSendingTask($newsletter, $task, $meta);
} else { } else {
$this->createAutomaticEmailSendingTask($newsletter, $subscriberId, $meta); $this->createAutomaticEmailSendingTask($newsletter, $subscriberId, $meta);
} }
@@ -80,7 +80,7 @@ class AutomaticEmailScheduler {
} }
} }
public function createAutomaticEmailSendingTask($newsletter, $subscriberId, $meta) { public function createAutomaticEmailSendingTask($newsletter, $subscriberId, $meta = false) {
$sendingTask = SendingTask::create(); $sendingTask = SendingTask::create();
$sendingTask->newsletterId = $newsletter->id; $sendingTask->newsletterId = $newsletter->id;
if ($newsletter->sendTo === 'user' && $subscriberId) { if ($newsletter->sendTo === 'user' && $subscriberId) {
@@ -96,7 +96,10 @@ class AutomaticEmailScheduler {
return $sendingTask->save(); return $sendingTask->save();
} }
private function rescheduleAutomaticEmailSendingTask($newsletter, $task) { private function rescheduleAutomaticEmailSendingTask($newsletter, $task, $meta = false) {
if ($meta) {
$task->__set('meta', $meta);
}
// compute new 'scheduled_at' from now // compute new 'scheduled_at' from now
$task->scheduledAt = Scheduler::getScheduledTimeWithDelay($newsletter->afterTimeType, $newsletter->afterTimeNumber); $task->scheduledAt = Scheduler::getScheduledTimeWithDelay($newsletter->afterTimeType, $newsletter->afterTimeNumber);
$task->save(); $task->save();

View File

@@ -216,6 +216,10 @@ class Sending {
return $this->queue->validate() && $this->task->validate(); return $this->queue->validate() && $this->task->validate();
} }
public function getMeta() {
return $this->queue->getMeta();
}
public function __isset($prop) { public function __isset($prop) {
$prop = Helpers::camelCaseToUnderscore($prop); $prop = Helpers::camelCaseToUnderscore($prop);
if ($this->isQueueProperty($prop)) { if ($this->isQueueProperty($prop)) {