280 lines
9 KiB
PHP
280 lines
9 KiB
PHP
|
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||
|
|
||
|
namespace MailPoet\WooCommerce;
|
||
|
|
||
|
if (!defined('ABSPATH')) exit;
|
||
|
|
||
|
|
||
|
use MailPoet\Entities\StatisticsUnsubscribeEntity;
|
||
|
use MailPoet\Entities\SubscriberEntity;
|
||
|
use MailPoet\Segments\SegmentsRepository;
|
||
|
use MailPoet\Settings\SettingsController;
|
||
|
use MailPoet\Statistics\Track\Unsubscribes;
|
||
|
use MailPoet\Subscribers\ConfirmationEmailMailer;
|
||
|
use MailPoet\Subscribers\Source;
|
||
|
use MailPoet\Subscribers\SubscriberSegmentRepository;
|
||
|
use MailPoet\Subscribers\SubscribersRepository;
|
||
|
use MailPoet\Util\Helpers;
|
||
|
use MailPoet\WP\Functions as WPFunctions;
|
||
|
use MailPoetVendor\Carbon\Carbon;
|
||
|
|
||
|
class Subscription {
|
||
|
const CHECKOUT_OPTIN_INPUT_NAME = 'mailpoet_woocommerce_checkout_optin';
|
||
|
const CHECKOUT_OPTIN_PRESENCE_CHECK_INPUT_NAME = 'mailpoet_woocommerce_checkout_optin_present';
|
||
|
const OPTIN_ENABLED_SETTING_NAME = 'woocommerce.optin_on_checkout.enabled';
|
||
|
const OPTIN_SEGMENTS_SETTING_NAME = 'woocommerce.optin_on_checkout.segments';
|
||
|
const OPTIN_MESSAGE_SETTING_NAME = 'woocommerce.optin_on_checkout.message';
|
||
|
|
||
|
private $allowedHtml = [
|
||
|
'input' => [
|
||
|
'type' => true,
|
||
|
'name' => true,
|
||
|
'id' => true,
|
||
|
'class' => true,
|
||
|
'value' => true,
|
||
|
'checked' => true,
|
||
|
],
|
||
|
'span' => [
|
||
|
'class' => true,
|
||
|
],
|
||
|
'label' => [
|
||
|
'class' => true,
|
||
|
'data-automation-id' => true,
|
||
|
'for' => true,
|
||
|
],
|
||
|
'p' => [
|
||
|
'class' => true,
|
||
|
'id' => true,
|
||
|
'data-priority' => true,
|
||
|
],
|
||
|
];
|
||
|
|
||
|
/** @var SettingsController */
|
||
|
private $settings;
|
||
|
|
||
|
/** @var WPFunctions */
|
||
|
private $wp;
|
||
|
|
||
|
/** @var Helper */
|
||
|
private $wcHelper;
|
||
|
|
||
|
/** @var ConfirmationEmailMailer */
|
||
|
private $confirmationEmailMailer;
|
||
|
|
||
|
/** @var SubscribersRepository */
|
||
|
private $subscribersRepository;
|
||
|
|
||
|
/** @var Unsubscribes */
|
||
|
private $unsubscribesTracker;
|
||
|
|
||
|
/** @var SegmentsRepository */
|
||
|
private $segmentsRepository;
|
||
|
|
||
|
/** @var SubscriberSegmentRepository */
|
||
|
private $subscriberSegmentRepository;
|
||
|
|
||
|
public function __construct(
|
||
|
SettingsController $settings,
|
||
|
ConfirmationEmailMailer $confirmationEmailMailer,
|
||
|
WPFunctions $wp,
|
||
|
Helper $wcHelper,
|
||
|
SubscribersRepository $subscribersRepository,
|
||
|
Unsubscribes $unsubscribesTracker,
|
||
|
SegmentsRepository $segmentsRepository,
|
||
|
SubscriberSegmentRepository $subscriberSegmentRepository
|
||
|
) {
|
||
|
$this->settings = $settings;
|
||
|
$this->wp = $wp;
|
||
|
$this->wcHelper = $wcHelper;
|
||
|
$this->confirmationEmailMailer = $confirmationEmailMailer;
|
||
|
$this->subscribersRepository = $subscribersRepository;
|
||
|
$this->unsubscribesTracker = $unsubscribesTracker;
|
||
|
$this->segmentsRepository = $segmentsRepository;
|
||
|
$this->subscriberSegmentRepository = $subscriberSegmentRepository;
|
||
|
}
|
||
|
|
||
|
public function extendWooCommerceCheckoutForm() {
|
||
|
$this->hideAutomateWooOptinCheckbox();
|
||
|
$inputName = self::CHECKOUT_OPTIN_INPUT_NAME;
|
||
|
$checked = false;
|
||
|
if (!empty($_POST[self::CHECKOUT_OPTIN_INPUT_NAME])) {
|
||
|
$checked = true;
|
||
|
}
|
||
|
$labelString = $this->settings->get(self::OPTIN_MESSAGE_SETTING_NAME);
|
||
|
$template = (string)$this->wp->applyFilters(
|
||
|
'mailpoet_woocommerce_checkout_optin_template',
|
||
|
wp_kses(
|
||
|
$this->getSubscriptionField($inputName, $checked, $labelString),
|
||
|
$this->allowedHtml
|
||
|
),
|
||
|
$inputName,
|
||
|
$checked,
|
||
|
$labelString
|
||
|
);
|
||
|
// The template has been sanitized above and can be considered safe.
|
||
|
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped, WordPressDotOrg.sniffs.OutputEscaping.UnescapedOutputParameter
|
||
|
echo $template;
|
||
|
if ($template) {
|
||
|
$field = $this->getSubscriptionPresenceCheckField();
|
||
|
echo wp_kses($field, $this->allowedHtml);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private function getSubscriptionField($inputName, $checked, $labelString) {
|
||
|
$checked = checked($checked, true, false);
|
||
|
|
||
|
return '<label class="woocommerce-form__label woocommerce-form__label-for-checkbox checkbox" data-automation-id="woo-commerce-subscription-opt-in">
|
||
|
<input id="mailpoet_woocommerce_checkout_optin" class="woocommerce-form__input woocommerce-form__input-checkbox input-checkbox" ' . $checked . ' type="checkbox" name="' . $this->wp->escAttr($inputName) . '" value="1" />
|
||
|
<span>' . $this->wp->escHtml($labelString) . '</span>
|
||
|
</label>';
|
||
|
}
|
||
|
|
||
|
private function getSubscriptionPresenceCheckField() {
|
||
|
$field = $this->wcHelper->woocommerceFormField(
|
||
|
self::CHECKOUT_OPTIN_PRESENCE_CHECK_INPUT_NAME,
|
||
|
[
|
||
|
'type' => 'hidden',
|
||
|
'return' => true,
|
||
|
],
|
||
|
1
|
||
|
);
|
||
|
if ($field) {
|
||
|
return $field;
|
||
|
}
|
||
|
// Workaround for older WooCommerce versions (below 4.6.0) that don't support hidden fields
|
||
|
// We can remove it after we drop support of older WooCommerce
|
||
|
$field = $this->wcHelper->woocommerceFormField(
|
||
|
self::CHECKOUT_OPTIN_PRESENCE_CHECK_INPUT_NAME,
|
||
|
[
|
||
|
'type' => 'text',
|
||
|
'return' => true,
|
||
|
],
|
||
|
1
|
||
|
);
|
||
|
return str_replace('type="text"', 'type="hidden"', $field);
|
||
|
}
|
||
|
|
||
|
public function subscribeOnOrderPay($orderId) {
|
||
|
$wcOrder = $this->wcHelper->wcGetOrder($orderId);
|
||
|
if (!$wcOrder instanceof \WC_Order) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
$data['billing_email'] = $wcOrder->get_billing_email();
|
||
|
$this->subscribeOnCheckout($orderId, $data);
|
||
|
}
|
||
|
|
||
|
public function subscribeOnCheckout($orderId, $data) {
|
||
|
$this->triggerAutomateWooOptin();
|
||
|
if (empty($data['billing_email'])) {
|
||
|
// no email in posted order data
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
$subscriber = $this->subscribersRepository->findOneBy(
|
||
|
['email' => $data['billing_email'], 'isWoocommerceUser' => 1]
|
||
|
);
|
||
|
|
||
|
if (!$subscriber) {
|
||
|
// no subscriber: WooCommerce sync didn't work
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
$checkoutOptin = !empty($_POST[self::CHECKOUT_OPTIN_INPUT_NAME]);
|
||
|
|
||
|
return $this->handleSubscriberOptin($subscriber, $checkoutOptin);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Subscribe a subscriber.
|
||
|
*
|
||
|
* @param SubscriberEntity $subscriber Subscriber object
|
||
|
* @param bool $shouldSubscribe Whether the subscriber should be subscribed
|
||
|
*/
|
||
|
public function handleSubscriberOptin(SubscriberEntity $subscriber, bool $shouldSubscribe): bool {
|
||
|
$wcSegment = $this->segmentsRepository->getWooCommerceSegment();
|
||
|
|
||
|
$segmentIds = (array)$this->settings->get(self::OPTIN_SEGMENTS_SETTING_NAME, []);
|
||
|
$moreSegmentsToSubscribe = [];
|
||
|
if (!empty($segmentIds)) {
|
||
|
$moreSegmentsToSubscribe = $this->segmentsRepository->findBy(['id' => $segmentIds]);
|
||
|
}
|
||
|
$signupConfirmation = $this->settings->get('signup_confirmation');
|
||
|
|
||
|
if ($shouldSubscribe) {
|
||
|
$subscriber->setSource(Source::WOOCOMMERCE_CHECKOUT);
|
||
|
|
||
|
if (
|
||
|
($subscriber->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED)
|
||
|
|| ((bool)$signupConfirmation['enabled'] === false)
|
||
|
) {
|
||
|
$this->subscribe($subscriber);
|
||
|
} else {
|
||
|
$this->requireSubscriptionConfirmation($subscriber);
|
||
|
}
|
||
|
|
||
|
$this->subscriberSegmentRepository->subscribeToSegments($subscriber, array_merge([$wcSegment], $moreSegmentsToSubscribe));
|
||
|
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private function hideAutomateWooOptinCheckbox(): void {
|
||
|
if (!$this->wp->isPluginActive('automatewoo/automatewoo.php')) {
|
||
|
return;
|
||
|
}
|
||
|
// Hide AutomateWoo checkout opt-in so we won't end up with two opt-ins
|
||
|
$this->wp->removeAction(
|
||
|
'woocommerce_checkout_after_terms_and_conditions',
|
||
|
[ 'AutomateWoo\Frontend', 'output_checkout_optin_checkbox' ]
|
||
|
);
|
||
|
}
|
||
|
|
||
|
private function triggerAutomateWooOptin(): void {
|
||
|
if (
|
||
|
!$this->wp->isPluginActive('automatewoo/automatewoo.php')
|
||
|
|| empty($_POST[self::CHECKOUT_OPTIN_INPUT_NAME])
|
||
|
) {
|
||
|
return;
|
||
|
}
|
||
|
// Emulate checkout opt-in triggering for AutomateWoo
|
||
|
$_POST['automatewoo_optin'] = 'On';
|
||
|
}
|
||
|
|
||
|
private function subscribe(SubscriberEntity $subscriber) {
|
||
|
$subscriber->setStatus(SubscriberEntity::STATUS_SUBSCRIBED);
|
||
|
if (empty($subscriber->getConfirmedIp()) && empty($subscriber->getConfirmedAt())) {
|
||
|
$subscriber->setConfirmedIp(Helpers::getIP());
|
||
|
$subscriber->setConfirmedAt(new Carbon());
|
||
|
}
|
||
|
|
||
|
$this->subscribersRepository->persist($subscriber);
|
||
|
$this->subscribersRepository->flush();
|
||
|
}
|
||
|
|
||
|
private function requireSubscriptionConfirmation(SubscriberEntity $subscriber) {
|
||
|
$subscriber->setStatus(SubscriberEntity::STATUS_UNCONFIRMED);
|
||
|
$this->subscribersRepository->persist($subscriber);
|
||
|
$this->subscribersRepository->flush();
|
||
|
|
||
|
try {
|
||
|
$this->confirmationEmailMailer->sendConfirmationEmailOnce($subscriber);
|
||
|
} catch (\Exception $e) {
|
||
|
// ignore errors
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private function updateSubscriberStatus(SubscriberEntity $subscriber) {
|
||
|
$segmentsCount = $subscriber->getSubscriberSegments(SubscriberEntity::STATUS_SUBSCRIBED)->count();
|
||
|
|
||
|
if (!$segmentsCount) {
|
||
|
$subscriber->setStatus(SubscriberEntity::STATUS_UNSUBSCRIBED);
|
||
|
$this->subscribersRepository->persist($subscriber);
|
||
|
$this->subscribersRepository->flush();
|
||
|
$this->unsubscribesTracker->track((int)$subscriber->getId(), StatisticsUnsubscribeEntity::SOURCE_ORDER_CHECKOUT);
|
||
|
}
|
||
|
}
|
||
|
}
|