Add abandoned cart block rendering in email [MAILPOET-2979]
This commit is contained in:
@@ -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();
|
||||||
|
@@ -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,
|
||||||
|
@@ -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] ?? [];
|
||||||
}
|
}
|
||||||
|
@@ -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':
|
||||||
|
@@ -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);
|
||||||
|
@@ -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();
|
||||||
|
@@ -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)) {
|
||||||
|
Reference in New Issue
Block a user