AbandonedCart.php
1 year ago
FirstPurchase.php
2 months ago
PurchasedInCategory.php
2 months ago
PurchasedProduct.php
2 months ago
index.php
3 years ago
FirstPurchase.php
256 lines
| 1 | <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing |
| 2 | |
| 3 | namespace MailPoet\AutomaticEmails\WooCommerce\Events; |
| 4 | |
| 5 | if (!defined('ABSPATH')) exit; |
| 6 | |
| 7 | |
| 8 | use MailPoet\AutomaticEmails\WooCommerce\WooCommerce; |
| 9 | use MailPoet\DI\ContainerWrapper; |
| 10 | use MailPoet\Entities\NewsletterEntity; |
| 11 | use MailPoet\Entities\SendingQueueEntity; |
| 12 | use MailPoet\Entities\SubscriberEntity; |
| 13 | use MailPoet\Logging\LoggerFactory; |
| 14 | use MailPoet\Newsletter\AutomaticEmailsRepository; |
| 15 | use MailPoet\Newsletter\Scheduler\AutomaticEmailScheduler; |
| 16 | use MailPoet\Subscribers\SubscribersRepository; |
| 17 | use MailPoet\WooCommerce\Helper as WCHelper; |
| 18 | use MailPoet\WP\Functions as WPFunctions; |
| 19 | |
| 20 | class FirstPurchase { |
| 21 | const SLUG = 'woocommerce_first_purchase'; |
| 22 | const ORDER_TOTAL_SHORTCODE = '[woocommerce:order_total]'; |
| 23 | const ORDER_DATE_SHORTCODE = '[woocommerce:order_date]'; |
| 24 | /** |
| 25 | * @var \MailPoet\WooCommerce\Helper |
| 26 | */ |
| 27 | private $helper; |
| 28 | |
| 29 | /** @var AutomaticEmailScheduler */ |
| 30 | private $scheduler; |
| 31 | |
| 32 | /** @var LoggerFactory */ |
| 33 | private $loggerFactory; |
| 34 | |
| 35 | /** @var AutomaticEmailsRepository */ |
| 36 | private $automaticEmailsRepository; |
| 37 | |
| 38 | /** @var SubscribersRepository */ |
| 39 | private $subscribersRepository; |
| 40 | |
| 41 | public function __construct( |
| 42 | ?WCHelper $helper = null |
| 43 | ) { |
| 44 | if ($helper === null) { |
| 45 | $helper = ContainerWrapper::getInstance()->get(WCHelper::class); |
| 46 | } |
| 47 | $this->helper = $helper; |
| 48 | $this->scheduler = ContainerWrapper::getInstance()->get(AutomaticEmailScheduler::class); |
| 49 | $this->loggerFactory = LoggerFactory::getInstance(); |
| 50 | $this->automaticEmailsRepository = ContainerWrapper::getInstance()->get(AutomaticEmailsRepository::class); |
| 51 | $this->subscribersRepository = ContainerWrapper::getInstance()->get(SubscribersRepository::class); |
| 52 | } |
| 53 | |
| 54 | public function init() { |
| 55 | WPFunctions::get()->addFilter('mailpoet_newsletter_shortcode', [ |
| 56 | $this, |
| 57 | 'handleOrderTotalShortcode', |
| 58 | ], 10, 4); |
| 59 | WPFunctions::get()->addFilter('mailpoet_newsletter_shortcode', [ |
| 60 | $this, |
| 61 | 'handleOrderDateShortcode', |
| 62 | ], 10, 4); |
| 63 | |
| 64 | // We have to use a set of states because an order state after checkout differs for different payment methods |
| 65 | $acceptedOrderStates = WPFunctions::get()->applyFilters('mailpoet_first_purchase_order_states', ['completed', 'processing']); |
| 66 | if (!is_array($acceptedOrderStates)) { |
| 67 | $acceptedOrderStates = ['completed', 'processing']; |
| 68 | } |
| 69 | |
| 70 | foreach ($acceptedOrderStates as $state) { |
| 71 | if (!is_string($state)) { |
| 72 | continue; |
| 73 | } |
| 74 | WPFunctions::get()->addAction('woocommerce_order_status_' . $state, [ |
| 75 | $this, |
| 76 | 'scheduleEmailWhenOrderIsPlaced', |
| 77 | ], 10, 1); |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | public function getEventDetails() { |
| 82 | return [ |
| 83 | 'slug' => self::SLUG, |
| 84 | 'title' => __('First Purchase', 'mailpoet'), |
| 85 | 'description' => __('Let MailPoet send an email to customers who make their first purchase.', 'mailpoet'), |
| 86 | 'listingScheduleDisplayText' => __('Email sent when a customer makes their first purchase.', 'mailpoet'), |
| 87 | 'afterDelayText' => __('after the first purchase', 'mailpoet'), |
| 88 | 'badge' => [ |
| 89 | 'text' => __('Must-have', 'mailpoet'), |
| 90 | 'style' => 'red', |
| 91 | ], |
| 92 | 'timeDelayValues' => [ |
| 93 | 'immediate' => [ |
| 94 | 'text' => __('immediately', 'mailpoet'), |
| 95 | 'displayAfterTimeNumberField' => false, |
| 96 | ], |
| 97 | 'minutes' => [ |
| 98 | 'text' => __('minute(s)', 'mailpoet'), |
| 99 | 'displayAfterTimeNumberField' => true, |
| 100 | ], |
| 101 | 'hours' => [ |
| 102 | 'text' => __('hour(s)', 'mailpoet'), |
| 103 | 'displayAfterTimeNumberField' => true, |
| 104 | ], |
| 105 | 'days' => [ |
| 106 | 'text' => __('day(s)', 'mailpoet'), |
| 107 | 'displayAfterTimeNumberField' => true, |
| 108 | ], |
| 109 | 'weeks' => [ |
| 110 | 'text' => __('week(s)', 'mailpoet'), |
| 111 | 'displayAfterTimeNumberField' => true, |
| 112 | ], |
| 113 | ], |
| 114 | 'shortcodes' => [ |
| 115 | [ |
| 116 | 'text' => __('Order amount', 'mailpoet'), |
| 117 | 'shortcode' => self::ORDER_TOTAL_SHORTCODE, |
| 118 | ], |
| 119 | [ |
| 120 | 'text' => __('Order date', 'mailpoet'), |
| 121 | 'shortcode' => self::ORDER_DATE_SHORTCODE, |
| 122 | ], |
| 123 | ], |
| 124 | ]; |
| 125 | } |
| 126 | |
| 127 | public function handleOrderDateShortcode($shortcode, $newsletter, $subscriber, $queue) { |
| 128 | $result = $shortcode; |
| 129 | if ($shortcode === self::ORDER_DATE_SHORTCODE) { |
| 130 | $defaultValue = WPFunctions::get()->dateI18n(get_option('date_format')); |
| 131 | if (!$queue) { |
| 132 | $result = $defaultValue; |
| 133 | } else { |
| 134 | $meta = $queue->getMeta(); |
| 135 | $result = (!empty($meta['order_date'])) ? WPFunctions::get()->dateI18n(get_option('date_format'), $meta['order_date']) : $defaultValue; |
| 136 | } |
| 137 | |
| 138 | $this->loggerFactory->getLogger(self::SLUG)->info( |
| 139 | 'handleOrderDateShortcode called', |
| 140 | [ |
| 141 | 'newsletter_id' => ($newsletter instanceof NewsletterEntity) ? $newsletter->getId() : null, |
| 142 | 'subscriber_id' => ($subscriber instanceof SubscriberEntity) ? $subscriber->getId() : null, |
| 143 | 'task_id' => ($queue instanceof SendingQueueEntity) ? (($task = $queue->getTask()) ? $task->getId() : null) : null, |
| 144 | 'shortcode' => $shortcode, |
| 145 | 'result' => $result, |
| 146 | ] |
| 147 | ); |
| 148 | } |
| 149 | return $result; |
| 150 | } |
| 151 | |
| 152 | public function handleOrderTotalShortcode($shortcode, $newsletter, $subscriber, $queue) { |
| 153 | $result = $shortcode; |
| 154 | if ($shortcode === self::ORDER_TOTAL_SHORTCODE) { |
| 155 | $defaultValue = $this->helper->wcPrice(0); |
| 156 | if (!$queue) { |
| 157 | $result = $defaultValue; |
| 158 | } else { |
| 159 | $meta = $queue->getMeta(); |
| 160 | $result = (!empty($meta['order_amount'])) ? $this->helper->wcPrice($meta['order_amount']) : $defaultValue; |
| 161 | } |
| 162 | |
| 163 | $this->loggerFactory->getLogger(self::SLUG)->info( |
| 164 | 'handleOrderTotalShortcode called', |
| 165 | [ |
| 166 | 'newsletter_id' => ($newsletter instanceof NewsletterEntity) ? $newsletter->getId() : null, |
| 167 | 'subscriber_id' => ($subscriber instanceof SubscriberEntity) ? $subscriber->getId() : null, |
| 168 | 'task_id' => ($queue instanceof SendingQueueEntity) ? (($task = $queue->getTask()) ? $task->getId() : null) : null, |
| 169 | 'shortcode' => $shortcode, |
| 170 | 'result' => $result, |
| 171 | ] |
| 172 | ); |
| 173 | } |
| 174 | return $result; |
| 175 | } |
| 176 | |
| 177 | public function scheduleEmailWhenOrderIsPlaced($orderId) { |
| 178 | $orderDetails = $this->helper->wcGetOrder($orderId); |
| 179 | if (!$orderDetails || !$orderDetails->get_billing_email()) { |
| 180 | $this->loggerFactory->getLogger(self::SLUG)->info( |
| 181 | 'Email not scheduled because the order customer was not found', |
| 182 | ['order_id' => $orderId] |
| 183 | ); |
| 184 | return; |
| 185 | } |
| 186 | |
| 187 | $customerEmail = $orderDetails->get_billing_email(); |
| 188 | $customerOrderCount = $this->getCustomerOrderCount($customerEmail); |
| 189 | if ($customerOrderCount > 1) { |
| 190 | $this->loggerFactory->getLogger(self::SLUG)->info( |
| 191 | 'Email not scheduled because this is not the first order of the customer', |
| 192 | [ |
| 193 | 'order_id' => $orderId, |
| 194 | 'customer_email' => $customerEmail, |
| 195 | 'order_count' => $customerOrderCount, |
| 196 | ] |
| 197 | ); |
| 198 | return; |
| 199 | } |
| 200 | |
| 201 | $meta = [ |
| 202 | 'order_amount' => $orderDetails->get_total(), |
| 203 | 'order_date' => $orderDetails->get_date_created()->getTimestamp(), |
| 204 | 'order_id' => $orderDetails->get_id(), |
| 205 | ]; |
| 206 | |
| 207 | $subscriber = $this->subscribersRepository->getWooCommerceSegmentSubscriber($customerEmail); |
| 208 | |
| 209 | if (!$subscriber instanceof SubscriberEntity) { |
| 210 | $this->loggerFactory->getLogger(self::SLUG)->info( |
| 211 | 'Email not scheduled because the customer was not found as WooCommerce list subscriber', |
| 212 | ['order_id' => $orderId, 'customer_email' => $customerEmail] |
| 213 | ); |
| 214 | return; |
| 215 | } |
| 216 | |
| 217 | $checkEmailWasNotScheduled = function (NewsletterEntity $newsletter) use ($subscriber) { |
| 218 | return !$this->automaticEmailsRepository->wasScheduledForSubscriber((int)$newsletter->getId(), (int)$subscriber->getId()); |
| 219 | }; |
| 220 | |
| 221 | $this->loggerFactory->getLogger(self::SLUG)->info( |
| 222 | 'Email scheduled', |
| 223 | [ |
| 224 | 'order_id' => $orderId, |
| 225 | 'customer_email' => $customerEmail, |
| 226 | 'subscriber_id' => $subscriber->getId(), |
| 227 | ] |
| 228 | ); |
| 229 | $this->scheduler->scheduleAutomaticEmail(WooCommerce::SLUG, self::SLUG, $checkEmailWasNotScheduled, $subscriber, $meta); |
| 230 | } |
| 231 | |
| 232 | public function getCustomerOrderCount($customerEmail) { |
| 233 | // registered user |
| 234 | $user = WPFunctions::get()->getUserBy('email', $customerEmail); |
| 235 | if ($user) { |
| 236 | return $this->helper->wcGetCustomerOrderCount($user->ID); |
| 237 | } |
| 238 | // guest user |
| 239 | return $this->getGuestCustomerOrderCountByEmail($customerEmail); |
| 240 | } |
| 241 | |
| 242 | private function getGuestCustomerOrderCountByEmail(string $customerEmail): int { |
| 243 | $ordersCount = $this->helper->wcGetOrders( |
| 244 | [ |
| 245 | 'status' => 'all', |
| 246 | 'type' => 'shop_order', |
| 247 | 'billing_email' => $customerEmail, |
| 248 | 'limit' => 1, |
| 249 | 'return' => 'ids', |
| 250 | 'paginate' => true, |
| 251 | ] |
| 252 | )->total; |
| 253 | return intval($ordersCount); |
| 254 | } |
| 255 | } |
| 256 |