init
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Subscription;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
class Blacklist {
|
||||
const SALT = 'mailpoet';
|
||||
|
||||
private $blacklistedEmails = [
|
||||
'e60c6e0e73997c92d4ceac78a6b6cbbe6249244c4106a3c31de421fc50370ecd' => 1,
|
||||
'4a7fb8fba0a800ad5cf324704d3510d019586765aef6d5081fa5aed3f93d9dce' => 1,
|
||||
'7151c278028263ace958b66616e69a438f23e5058a5df42ed734e9e6704f8332' => 1,
|
||||
];
|
||||
|
||||
private $blacklistedDomains = [
|
||||
'2ea570cf0c440b2ec7d6e1335108625a5f62162b2116a25c9c909dc5b54c213f' => 1,
|
||||
'1e10eb32b615217baa4d8f54191891e107851a2057d1128f067f1df096896e45' => 1,
|
||||
'dc2bfb04e38d3c25c8a465a5fed841a1cb1685044d12241efe01f0fc044f2182' => 1,
|
||||
'f17c13fe5a1d8cd2e78a04528377cc607881ad12b6295b6fa8b6a789d1d04c10' => 1,
|
||||
'813cbef72da3542e783470ecd62589bceb3883d15ab2435ec2486f9762602b8c' => 1,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
array $blacklistedEmails = null,
|
||||
array $blacklistedDomains = null
|
||||
) {
|
||||
if ($blacklistedEmails) {
|
||||
$this->blacklistedEmails = array_fill_keys(array_map([$this, 'hash'], $blacklistedEmails), 1);
|
||||
}
|
||||
if ($blacklistedDomains) {
|
||||
$this->blacklistedDomains = array_fill_keys(array_map([$this, 'hash'], $blacklistedDomains), 1);
|
||||
}
|
||||
}
|
||||
|
||||
public function isBlacklisted($email) {
|
||||
$hashedEmail = $this->hash($email);
|
||||
if (isset($this->blacklistedEmails[$hashedEmail])) {
|
||||
return true;
|
||||
}
|
||||
$emailParts = explode('@', $email);
|
||||
$domain = end($emailParts);
|
||||
$hashedDomain = $this->hash($domain);
|
||||
return isset($this->blacklistedDomains[$hashedDomain]);
|
||||
}
|
||||
|
||||
private function hash($key) {
|
||||
return hash('sha256', $key . self::SALT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Subscription;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Subscribers\SubscriberActions;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class Comment {
|
||||
const SPAM = 'spam';
|
||||
const APPROVED = 1;
|
||||
const PENDING_APPROVAL = 0;
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
/** @var SubscriberActions */
|
||||
private $subscriberActions;
|
||||
|
||||
public function __construct(
|
||||
SettingsController $settings,
|
||||
SubscriberActions $subscriberActions
|
||||
) {
|
||||
$this->settings = $settings;
|
||||
$this->subscriberActions = $subscriberActions;
|
||||
}
|
||||
|
||||
public function extendLoggedInForm($field) {
|
||||
$field .= $this->getSubscriptionField();
|
||||
return $field;
|
||||
}
|
||||
|
||||
public function extendLoggedOutForm() {
|
||||
// The method returns escaped content
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $this->getSubscriptionField();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns escaped HTML for the subscription field.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getSubscriptionField(): string {
|
||||
$label = $this->settings->get(
|
||||
'subscribe.on_comment.label',
|
||||
__('Yes, please add me to your mailing list.', 'mailpoet')
|
||||
);
|
||||
|
||||
return '<p class="comment-form-mailpoet">
|
||||
<label for="mailpoet_subscribe_on_comment">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="mailpoet_subscribe_on_comment"
|
||||
value="1"
|
||||
name="mailpoet[subscribe_on_comment]"
|
||||
/> ' . esc_html($label) . '
|
||||
</label>
|
||||
</p>';
|
||||
}
|
||||
|
||||
public function onSubmit($commentId, $commentStatus) {
|
||||
if ($commentStatus === Comment::SPAM) return;
|
||||
|
||||
if (
|
||||
isset($_POST['mailpoet']['subscribe_on_comment'])
|
||||
&& (bool)$_POST['mailpoet']['subscribe_on_comment'] === true
|
||||
) {
|
||||
if ($commentStatus === Comment::PENDING_APPROVAL) {
|
||||
// add a comment meta to remember to subscribe the user
|
||||
// once the comment gets approved
|
||||
WPFunctions::get()->addCommentMeta(
|
||||
$commentId,
|
||||
'mailpoet',
|
||||
'subscribe_on_comment',
|
||||
true
|
||||
);
|
||||
} else if ($commentStatus === Comment::APPROVED) {
|
||||
$this->subscribeAuthorOfComment($commentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function onStatusUpdate($commentId, $action) {
|
||||
if ($action === 'approve') {
|
||||
// check if the comment's author wants to subscribe
|
||||
$doSubscribe = (
|
||||
WPFunctions::get()->getCommentMeta(
|
||||
$commentId,
|
||||
'mailpoet',
|
||||
true
|
||||
) === 'subscribe_on_comment'
|
||||
);
|
||||
|
||||
if ($doSubscribe === true) {
|
||||
$this->subscribeAuthorOfComment($commentId);
|
||||
|
||||
WPFunctions::get()->deleteCommentMeta($commentId, 'mailpoet');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function subscribeAuthorOfComment($commentId) {
|
||||
$segmentIds = $this->settings->get('subscribe.on_comment.segments', []);
|
||||
|
||||
if (!empty($segmentIds)) {
|
||||
$comment = WPFunctions::get()->getComment($commentId);
|
||||
|
||||
$this->subscriberActions->subscribe(
|
||||
[
|
||||
'email' => $comment->comment_author_email, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
'first_name' => $comment->comment_author, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
],
|
||||
$segmentIds
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Subscription;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\JSON\API;
|
||||
use MailPoet\API\JSON\Endpoint;
|
||||
use MailPoet\API\JSON\Response as APIResponse;
|
||||
use MailPoet\Util\Url as UrlHelper;
|
||||
|
||||
class Form {
|
||||
|
||||
/** @var API */
|
||||
private $api;
|
||||
|
||||
/** @var UrlHelper */
|
||||
private $urlHelper;
|
||||
|
||||
public function __construct(
|
||||
API $api,
|
||||
UrlHelper $urlHelper
|
||||
) {
|
||||
$this->api = $api;
|
||||
$this->urlHelper = $urlHelper;
|
||||
}
|
||||
|
||||
public function onSubmit($requestData = false) {
|
||||
$requestData = ($requestData) ? $requestData : $_REQUEST;
|
||||
$this->api->setRequestData($requestData, Endpoint::TYPE_POST);
|
||||
$formId = (!empty($requestData['data']['form_id'])) ? (int)$requestData['data']['form_id'] : false;
|
||||
$response = $this->api->processRoute();
|
||||
if ($response->status !== APIResponse::STATUS_OK) {
|
||||
return (isset($response->meta['redirect_url'])) ?
|
||||
$this->urlHelper->redirectTo($response->meta['redirect_url']) :
|
||||
$this->urlHelper->redirectBack(
|
||||
[
|
||||
'mailpoet_error' => ($formId) ? $formId : true,
|
||||
'mailpoet_success' => null,
|
||||
]
|
||||
);
|
||||
} else {
|
||||
return (isset($response->meta['redirect_url'])) ?
|
||||
$this->urlHelper->redirectTo($response->meta['redirect_url']) :
|
||||
$this->urlHelper->redirectBack(
|
||||
[
|
||||
'mailpoet_success' => $formId,
|
||||
'mailpoet_error' => null,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Subscription;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\CustomFields\CustomFieldsRepository;
|
||||
use MailPoet\Entities\StatisticsUnsubscribeEntity;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Entities\SubscriberSegmentEntity;
|
||||
use MailPoet\Form\Util\FieldNameObfuscator;
|
||||
use MailPoet\Newsletter\Scheduler\WelcomeScheduler;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
use MailPoet\Statistics\Track\Unsubscribes;
|
||||
use MailPoet\Subscribers\LinkTokens;
|
||||
use MailPoet\Subscribers\NewSubscriberNotificationMailer;
|
||||
use MailPoet\Subscribers\SubscriberSaveController;
|
||||
use MailPoet\Subscribers\SubscriberSegmentRepository;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
use MailPoet\Util\Url as UrlHelper;
|
||||
|
||||
class Manage {
|
||||
|
||||
/** @var UrlHelper */
|
||||
private $urlHelper;
|
||||
|
||||
/** @var FieldNameObfuscator */
|
||||
private $fieldNameObfuscator;
|
||||
|
||||
/** @var LinkTokens */
|
||||
private $linkTokens;
|
||||
|
||||
/** @var Unsubscribes */
|
||||
private $unsubscribesTracker;
|
||||
|
||||
/** @var NewSubscriberNotificationMailer */
|
||||
private $newSubscriberNotificationMailer;
|
||||
|
||||
/** @var WelcomeScheduler */
|
||||
private $welcomeScheduler;
|
||||
|
||||
/** @var CustomFieldsRepository */
|
||||
private $customFieldsRepository;
|
||||
|
||||
/** @var SegmentsRepository */
|
||||
private $segmentsRepository;
|
||||
|
||||
/** @var SubscribersRepository */
|
||||
private $subscribersRepository;
|
||||
|
||||
/** @var SubscriberSegmentRepository */
|
||||
private $subscriberSegmentRepository;
|
||||
|
||||
/** @var SubscriberSaveController */
|
||||
private $subscriberSaveController;
|
||||
|
||||
public function __construct(
|
||||
UrlHelper $urlHelper,
|
||||
FieldNameObfuscator $fieldNameObfuscator,
|
||||
LinkTokens $linkTokens,
|
||||
Unsubscribes $unsubscribesTracker,
|
||||
NewSubscriberNotificationMailer $newSubscriberNotificationMailer,
|
||||
WelcomeScheduler $welcomeScheduler,
|
||||
CustomFieldsRepository $customFieldsRepository,
|
||||
SegmentsRepository $segmentsRepository,
|
||||
SubscribersRepository $subscribersRepository,
|
||||
SubscriberSegmentRepository $subscriberSegmentRepository,
|
||||
SubscriberSaveController $subscriberSaveController
|
||||
) {
|
||||
$this->urlHelper = $urlHelper;
|
||||
$this->fieldNameObfuscator = $fieldNameObfuscator;
|
||||
$this->unsubscribesTracker = $unsubscribesTracker;
|
||||
$this->linkTokens = $linkTokens;
|
||||
$this->newSubscriberNotificationMailer = $newSubscriberNotificationMailer;
|
||||
$this->welcomeScheduler = $welcomeScheduler;
|
||||
$this->segmentsRepository = $segmentsRepository;
|
||||
$this->subscribersRepository = $subscribersRepository;
|
||||
$this->subscriberSegmentRepository = $subscriberSegmentRepository;
|
||||
$this->customFieldsRepository = $customFieldsRepository;
|
||||
$this->subscriberSaveController = $subscriberSaveController;
|
||||
}
|
||||
|
||||
public function onSave() {
|
||||
$action = (isset($_POST['action']) ? sanitize_text_field(wp_unslash($_POST['action'])) : '');
|
||||
$token = (isset($_POST['token']) ? sanitize_text_field(wp_unslash($_POST['token'])) : '');
|
||||
|
||||
if ($action !== 'mailpoet_subscription_update' || empty($_POST['data'])) {
|
||||
$this->urlHelper->redirectBack();
|
||||
}
|
||||
|
||||
$sanitize = function ($value) {
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $k => $v) {
|
||||
$value[sanitize_text_field($k)] = sanitize_text_field($v);
|
||||
}
|
||||
return $value;
|
||||
};
|
||||
return sanitize_text_field($value);
|
||||
};
|
||||
|
||||
// custom sanitization via $sanitize
|
||||
//phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$subscriberData = array_map($sanitize, wp_unslash((array)$_POST['data']));
|
||||
$subscriberData = $this->fieldNameObfuscator->deobfuscateFormPayload($subscriberData);
|
||||
|
||||
$result = [];
|
||||
if (!empty($subscriberData['email'])) {
|
||||
$subscriber = $this->subscribersRepository->findOneBy(['email' => $subscriberData['email']]);
|
||||
|
||||
if (
|
||||
($subscriberData['status'] === SubscriberEntity::STATUS_UNSUBSCRIBED)
|
||||
&& ($subscriber instanceof SubscriberEntity)
|
||||
&& ($subscriber->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED)
|
||||
) {
|
||||
$this->unsubscribesTracker->track(
|
||||
(int)$subscriber->getId(),
|
||||
StatisticsUnsubscribeEntity::SOURCE_MANAGE
|
||||
);
|
||||
}
|
||||
|
||||
if ($subscriber && $this->linkTokens->verifyToken($subscriber, $token)) {
|
||||
if ($subscriberData['email'] !== Pages::DEMO_EMAIL) {
|
||||
$subscriber = $this->subscriberSaveController->createOrUpdate($subscriberData, $subscriber);
|
||||
$this->subscriberSaveController->updateCustomFields($this->filterOutEmptyMandatoryFields($subscriberData), $subscriber);
|
||||
$this->updateSubscriptions($subscriber, $subscriberData);
|
||||
}
|
||||
}
|
||||
$result = ['success' => true];
|
||||
}
|
||||
|
||||
$this->urlHelper->redirectBack($result);
|
||||
}
|
||||
|
||||
private function updateSubscriptions(SubscriberEntity $subscriber, array $subscriberData): void {
|
||||
$segmentsIds = [];
|
||||
if (isset($subscriberData['segments']) && is_array($subscriberData['segments'])) {
|
||||
$segmentsIds = $subscriberData['segments'];
|
||||
}
|
||||
|
||||
// Unsubscribe from all other segments already subscribed to
|
||||
// but don't change disallowed segments
|
||||
foreach ($subscriber->getSubscriberSegments() as $subscriberSegment) {
|
||||
$segment = $subscriberSegment->getSegment();
|
||||
if (!$segment) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($segment->getDisplayInManageSubscriptionPage())) {
|
||||
continue;
|
||||
}
|
||||
if (!in_array($segment->getId(), $segmentsIds)) {
|
||||
$this->subscriberSegmentRepository->createOrUpdate(
|
||||
$subscriber,
|
||||
$segment,
|
||||
SubscriberEntity::STATUS_UNSUBSCRIBED
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Store new segments for notifications
|
||||
$subscriberSegments = $this->subscriberSegmentRepository->findBy([
|
||||
'status' => SubscriberEntity::STATUS_SUBSCRIBED,
|
||||
'subscriber' => $subscriber,
|
||||
]);
|
||||
$currentSegmentIds = array_filter(array_map(function (SubscriberSegmentEntity $subscriberSegment): ?string {
|
||||
$segment = $subscriberSegment->getSegment();
|
||||
return $segment ? (string)$segment->getId() : null;
|
||||
}, $subscriberSegments));
|
||||
$newSegmentIds = array_diff($segmentsIds, $currentSegmentIds);
|
||||
|
||||
foreach ($segmentsIds as $segmentId) {
|
||||
$segment = $this->segmentsRepository->findOneById($segmentId);
|
||||
if (!$segment) {
|
||||
continue;
|
||||
}
|
||||
// Allow subscribing only to allowed segments
|
||||
if (empty($segment->getDisplayInManageSubscriptionPage())) {
|
||||
continue;
|
||||
}
|
||||
$this->subscriberSegmentRepository->createOrUpdate(
|
||||
$subscriber,
|
||||
$segment,
|
||||
SubscriberEntity::STATUS_SUBSCRIBED
|
||||
);
|
||||
}
|
||||
|
||||
if ($subscriber->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED && $newSegmentIds) {
|
||||
$newSegments = $this->segmentsRepository->findBy(['id' => $newSegmentIds]);
|
||||
$this->newSubscriberNotificationMailer->send($subscriber, $newSegments);
|
||||
$this->welcomeScheduler->scheduleSubscriberWelcomeNotification(
|
||||
$subscriber->getId(),
|
||||
$newSegmentIds
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function filterOutEmptyMandatoryFields(array $subscriberData): array {
|
||||
$mandatory = $this->getMandatory();
|
||||
foreach ($mandatory as $name) {
|
||||
if (!isset($subscriberData[$name])) {
|
||||
continue;
|
||||
}
|
||||
if (is_array($subscriberData[$name]) && count(array_filter($subscriberData[$name])) === 0) {
|
||||
unset($subscriberData[$name]);
|
||||
}
|
||||
if (is_string($subscriberData[$name]) && strlen(trim($subscriberData[$name])) === 0) {
|
||||
unset($subscriberData[$name]);
|
||||
}
|
||||
}
|
||||
return $subscriberData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getMandatory(): array {
|
||||
$mandatory = [];
|
||||
$requiredCustomFields = $this->customFieldsRepository->findAll();
|
||||
foreach ($requiredCustomFields as $customField) {
|
||||
$params = $customField->getParams();
|
||||
if (
|
||||
is_array($params)
|
||||
&& isset($params['required'])
|
||||
&& $params['required']
|
||||
) {
|
||||
$mandatory[] = 'cf_' . $customField->getId();
|
||||
}
|
||||
}
|
||||
return $mandatory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Subscription;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Config\Renderer as TemplateRenderer;
|
||||
use MailPoet\CustomFields\CustomFieldsRepository;
|
||||
use MailPoet\Entities\CustomFieldEntity;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Form\Block\Date as FormBlockDate;
|
||||
use MailPoet\Form\Renderer as FormRenderer;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
use MailPoet\Subscribers\LinkTokens;
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoet\Util\Url as UrlHelper;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoetVendor\Doctrine\Common\Collections\Criteria;
|
||||
|
||||
class ManageSubscriptionFormRenderer {
|
||||
const FORM_STATE_SUCCESS = 'success';
|
||||
const FORM_STATE_NOT_SUBMITTED = 'not_submitted';
|
||||
|
||||
/** @var UrlHelper */
|
||||
private $urlHelper;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var LinkTokens */
|
||||
private $linkTokens;
|
||||
|
||||
/** @var FormRenderer */
|
||||
private $formRenderer;
|
||||
|
||||
/** @var FormBlockDate */
|
||||
private $dateBlock;
|
||||
|
||||
/** @var TemplateRenderer */
|
||||
private $templateRenderer;
|
||||
|
||||
/** @var CustomFieldsRepository */
|
||||
private $customFieldsRepository;
|
||||
|
||||
/** @var SegmentsRepository */
|
||||
private $segmentsRepository;
|
||||
|
||||
public function __construct(
|
||||
WPFunctions $wp,
|
||||
UrlHelper $urlHelper,
|
||||
LinkTokens $linkTokens,
|
||||
FormRenderer $formRenderer,
|
||||
FormBlockDate $dateBlock,
|
||||
TemplateRenderer $templateRenderer,
|
||||
CustomFieldsRepository $customFieldsRepository,
|
||||
SegmentsRepository $segmentsRepository
|
||||
) {
|
||||
$this->wp = $wp;
|
||||
$this->urlHelper = $urlHelper;
|
||||
$this->linkTokens = $linkTokens;
|
||||
$this->formRenderer = $formRenderer;
|
||||
$this->dateBlock = $dateBlock;
|
||||
$this->templateRenderer = $templateRenderer;
|
||||
$this->customFieldsRepository = $customFieldsRepository;
|
||||
$this->segmentsRepository = $segmentsRepository;
|
||||
}
|
||||
|
||||
public function renderForm(SubscriberEntity $subscriber, string $formState = self::FORM_STATE_NOT_SUBMITTED): string {
|
||||
$basicFields = $this->getBasicFields($subscriber);
|
||||
$customFields = $this->getCustomFields($subscriber);
|
||||
$segmentField = $this->getSegmentField($subscriber);
|
||||
|
||||
$form = array_merge(
|
||||
$basicFields,
|
||||
$customFields,
|
||||
[
|
||||
$segmentField,
|
||||
[
|
||||
'id' => 'submit',
|
||||
'type' => 'submit',
|
||||
'params' => [
|
||||
'label' => __('Save', 'mailpoet'),
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$form = $this->wp->applyFilters('mailpoet_manage_subscription_page_form_fields', $form);
|
||||
|
||||
$templateData = [
|
||||
'actionUrl' => admin_url('admin-post.php'),
|
||||
'redirectUrl' => $this->urlHelper->getCurrentUrl(),
|
||||
'email' => $subscriber->getEmail(),
|
||||
'token' => $this->linkTokens->getToken($subscriber),
|
||||
'editEmailInfo' => __('Need to change your email address? Unsubscribe using the form below, then simply sign up again.', 'mailpoet'),
|
||||
'formHtml' => $this->formRenderer->renderBlocks($form, [], null, $honeypot = false, $captcha = false),
|
||||
'formState' => $formState,
|
||||
];
|
||||
|
||||
if ($subscriber->isWPUser() || $subscriber->getIsWoocommerceUser()) {
|
||||
$wpCurrentUser = $this->wp->wpGetCurrentUser();
|
||||
if ($wpCurrentUser->user_email === $subscriber->getEmail()) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$templateData['editEmailInfo'] = Helpers::replaceLinkTags(
|
||||
__('[link]Edit your profile[/link] to update your email.', 'mailpoet'),
|
||||
$this->wp->getEditProfileUrl(),
|
||||
['target' => '_blank']
|
||||
);
|
||||
} else {
|
||||
$templateData['editEmailInfo'] = Helpers::replaceLinkTags(
|
||||
__('[link]Log in to your account[/link] to update your email.', 'mailpoet'),
|
||||
$this->wp->wpLoginUrl(),
|
||||
['target' => '_blank']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->templateRenderer->render('subscription/manage_subscription.html', $templateData);
|
||||
}
|
||||
|
||||
private function getCustomFields(SubscriberEntity $subscriber): array {
|
||||
$customFieldValues = [];
|
||||
foreach ($subscriber->getSubscriberCustomFields() as $subscriberCustomField) {
|
||||
$customField = $subscriberCustomField->getCustomField();
|
||||
if (!$customField) continue;
|
||||
|
||||
$customFieldValues[$customField->getId()] = $subscriberCustomField->getValue();
|
||||
}
|
||||
return array_map(function (CustomFieldEntity $customFieldEntity) use ($customFieldValues) {
|
||||
$customField = [
|
||||
'id' => 'cf_' . $customFieldEntity->getId(),
|
||||
'name' => $customFieldEntity->getName(),
|
||||
'type' => $customFieldEntity->getType(),
|
||||
'params' => $customFieldEntity->getParams(),
|
||||
];
|
||||
|
||||
$customField['params']['value'] = $customFieldValues[$customFieldEntity->getId()] ?? null;
|
||||
|
||||
if ($customField['type'] === 'date') {
|
||||
$dateFormats = $this->dateBlock->getDateFormats();
|
||||
$customField['params']['date_format'] = array_shift(
|
||||
$dateFormats[$customField['params']['date_type']]
|
||||
);
|
||||
}
|
||||
if (!isset($customField['params']['label'])) {
|
||||
$customField['params']['label'] = $customField['name'];
|
||||
}
|
||||
|
||||
return $customField;
|
||||
}, $this->customFieldsRepository->findAll());
|
||||
}
|
||||
|
||||
private function getBasicFields(SubscriberEntity $subscriber): array {
|
||||
return [
|
||||
[
|
||||
'id' => 'first_name',
|
||||
'type' => 'text',
|
||||
'params' => [
|
||||
'label' => __('First name', 'mailpoet'),
|
||||
'value' => $subscriber->getFirstName(),
|
||||
'disabled' => ($subscriber->isWPUser() || $subscriber->getIsWoocommerceUser()),
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'last_name',
|
||||
'type' => 'text',
|
||||
'params' => [
|
||||
'label' => __('Last name', 'mailpoet'),
|
||||
'value' => $subscriber->getLastName(),
|
||||
'disabled' => ($subscriber->isWPUser() || $subscriber->getIsWoocommerceUser()),
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'status',
|
||||
'type' => 'select',
|
||||
'params' => [
|
||||
'required' => true,
|
||||
'label' => __('Status', 'mailpoet'),
|
||||
'values' => [
|
||||
[
|
||||
'value' => [
|
||||
SubscriberEntity::STATUS_SUBSCRIBED => __('Subscribed', 'mailpoet'),
|
||||
],
|
||||
'is_checked' => (
|
||||
$subscriber->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED
|
||||
),
|
||||
],
|
||||
[
|
||||
'value' => [
|
||||
SubscriberEntity::STATUS_UNSUBSCRIBED => __('Unsubscribed', 'mailpoet'),
|
||||
],
|
||||
'is_checked' => (
|
||||
$subscriber->getStatus() === SubscriberEntity::STATUS_UNSUBSCRIBED
|
||||
),
|
||||
],
|
||||
[
|
||||
'value' => [
|
||||
SubscriberEntity::STATUS_BOUNCED => __('Bounced', 'mailpoet'),
|
||||
],
|
||||
'is_checked' => (
|
||||
$subscriber->getStatus() === SubscriberEntity::STATUS_BOUNCED
|
||||
),
|
||||
'is_disabled' => true,
|
||||
'is_hidden' => (
|
||||
$subscriber->getStatus() !== SubscriberEntity::STATUS_BOUNCED
|
||||
),
|
||||
],
|
||||
[
|
||||
'value' => [
|
||||
SubscriberEntity::STATUS_INACTIVE => __('Inactive', 'mailpoet'),
|
||||
],
|
||||
'is_checked' => (
|
||||
$subscriber->getStatus() === SubscriberEntity::STATUS_INACTIVE
|
||||
),
|
||||
'is_hidden' => (
|
||||
$subscriber->getStatus() !== SubscriberEntity::STATUS_INACTIVE
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function getSegmentField(SubscriberEntity $subscriber): array {
|
||||
// Get default segments
|
||||
$criteria = [
|
||||
'type' => SegmentEntity::TYPE_DEFAULT,
|
||||
'deletedAt' => null,
|
||||
'displayInManageSubscriptionPage' => true,
|
||||
];
|
||||
$segments = $this->segmentsRepository->findBy($criteria, ['name' => Criteria::ASC]);
|
||||
|
||||
$subscribedSegmentIds = [];
|
||||
foreach ($subscriber->getSubscriberSegments() as $subscriberSegment) {
|
||||
$segment = $subscriberSegment->getSegment();
|
||||
if (!$segment) continue;
|
||||
|
||||
if ($subscriberSegment->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED) {
|
||||
$subscribedSegmentIds[] = $segment->getId();
|
||||
}
|
||||
}
|
||||
|
||||
$segments = array_map(function (SegmentEntity $segment) use ($subscribedSegmentIds) {
|
||||
return [
|
||||
'id' => $segment->getId(),
|
||||
'name' => $segment->getName(),
|
||||
'is_checked' => in_array($segment->getId(), $subscribedSegmentIds),
|
||||
];
|
||||
}, $segments);
|
||||
|
||||
return [
|
||||
'id' => 'segments',
|
||||
'type' => 'segment',
|
||||
'params' => [
|
||||
'label' => __('Your lists', 'mailpoet'),
|
||||
'values' => $segments,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,508 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Subscription;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Config\Renderer as TemplateRenderer;
|
||||
use MailPoet\Cron\Workers\StatsNotifications\NewsletterLinkRepository;
|
||||
use MailPoet\Entities\NewsletterLinkEntity;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Entities\StatisticsUnsubscribeEntity;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Form\AssetsController;
|
||||
use MailPoet\Newsletter\Scheduler\WelcomeScheduler;
|
||||
use MailPoet\Newsletter\Sending\SendingQueuesRepository;
|
||||
use MailPoet\Settings\TrackingConfig;
|
||||
use MailPoet\Statistics\StatisticsClicksRepository;
|
||||
use MailPoet\Statistics\Track\SubscriberHandler;
|
||||
use MailPoet\Statistics\Track\Unsubscribes;
|
||||
use MailPoet\Subscribers\LinkTokens;
|
||||
use MailPoet\Subscribers\NewSubscriberNotificationMailer;
|
||||
use MailPoet\Subscribers\SubscriberSaveController;
|
||||
use MailPoet\Subscribers\SubscriberSegmentRepository;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
use MailPoetVendor\Doctrine\ORM\EntityManager;
|
||||
|
||||
class Pages {
|
||||
const DEMO_EMAIL = 'demo@mailpoet.com';
|
||||
const ACTION_CONFIRM = 'confirm';
|
||||
const ACTION_CONFIRM_UNSUBSCRIBE = 'confirm_unsubscribe';
|
||||
const ACTION_MANAGE = 'manage';
|
||||
const ACTION_UNSUBSCRIBE = 'unsubscribe';
|
||||
const ACTION_RE_ENGAGEMENT = 're_engagement';
|
||||
|
||||
private $action;
|
||||
private $data;
|
||||
private $subscriber;
|
||||
|
||||
/** @var NewSubscriberNotificationMailer */
|
||||
private $newSubscriberNotificationSender;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var WelcomeScheduler */
|
||||
private $welcomeScheduler;
|
||||
|
||||
/** @var LinkTokens */
|
||||
private $linkTokens;
|
||||
|
||||
/** @var SubscriptionUrlFactory */
|
||||
private $subscriptionUrlFactory;
|
||||
|
||||
/** @var AssetsController */
|
||||
private $assetsController;
|
||||
|
||||
/** @var TemplateRenderer */
|
||||
private $templateRenderer;
|
||||
|
||||
/** @var Unsubscribes */
|
||||
private $unsubscribesTracker;
|
||||
|
||||
/** @var ManageSubscriptionFormRenderer */
|
||||
private $manageSubscriptionFormRenderer;
|
||||
|
||||
/** @var SubscriberHandler */
|
||||
private $subscriberHandler;
|
||||
|
||||
/** @var SubscribersRepository */
|
||||
private $subscribersRepository;
|
||||
|
||||
/** @var TrackingConfig */
|
||||
private $trackingConfig;
|
||||
|
||||
/** @var EntityManager */
|
||||
private $entityManager;
|
||||
|
||||
/** @var SubscriberSaveController */
|
||||
private $subscriberSaveController;
|
||||
|
||||
/** @var SubscriberSegmentRepository */
|
||||
private $subscriberSegmentRepository;
|
||||
|
||||
/*** @var NewsletterLinkRepository */
|
||||
private $newsletterLinkRepository;
|
||||
|
||||
/*** @var StatisticsClicksRepository */
|
||||
private $statisticsClicksRepository;
|
||||
|
||||
/*** @var SendingQueuesRepository */
|
||||
private $sendingQueuesRepository;
|
||||
|
||||
public function __construct(
|
||||
NewSubscriberNotificationMailer $newSubscriberNotificationSender,
|
||||
WPFunctions $wp,
|
||||
WelcomeScheduler $welcomeScheduler,
|
||||
LinkTokens $linkTokens,
|
||||
SubscriptionUrlFactory $subscriptionUrlFactory,
|
||||
AssetsController $assetsController,
|
||||
TemplateRenderer $templateRenderer,
|
||||
Unsubscribes $unsubscribesTracker,
|
||||
ManageSubscriptionFormRenderer $manageSubscriptionFormRenderer,
|
||||
SubscriberHandler $subscriberHandler,
|
||||
SubscribersRepository $subscribersRepository,
|
||||
TrackingConfig $trackingConfig,
|
||||
EntityManager $entityManager,
|
||||
SubscriberSaveController $subscriberSaveController,
|
||||
SubscriberSegmentRepository $subscriberSegmentRepository,
|
||||
NewsletterLinkRepository $newsletterLinkRepository,
|
||||
StatisticsClicksRepository $statisticsClicksRepository,
|
||||
SendingQueuesRepository $sendingQueuesRepository
|
||||
) {
|
||||
$this->wp = $wp;
|
||||
$this->newSubscriberNotificationSender = $newSubscriberNotificationSender;
|
||||
$this->welcomeScheduler = $welcomeScheduler;
|
||||
$this->linkTokens = $linkTokens;
|
||||
$this->subscriptionUrlFactory = $subscriptionUrlFactory;
|
||||
$this->assetsController = $assetsController;
|
||||
$this->templateRenderer = $templateRenderer;
|
||||
$this->unsubscribesTracker = $unsubscribesTracker;
|
||||
$this->manageSubscriptionFormRenderer = $manageSubscriptionFormRenderer;
|
||||
$this->subscriberHandler = $subscriberHandler;
|
||||
$this->subscribersRepository = $subscribersRepository;
|
||||
$this->trackingConfig = $trackingConfig;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->subscriberSaveController = $subscriberSaveController;
|
||||
$this->subscriberSegmentRepository = $subscriberSegmentRepository;
|
||||
$this->newsletterLinkRepository = $newsletterLinkRepository;
|
||||
$this->statisticsClicksRepository = $statisticsClicksRepository;
|
||||
$this->sendingQueuesRepository = $sendingQueuesRepository;
|
||||
}
|
||||
|
||||
public function init($action = false, $data = [], $initShortcodes = false, $initPageFilters = false) {
|
||||
$this->action = $action;
|
||||
$this->data = $data;
|
||||
$this->subscriber = $this->getSubscriber();
|
||||
if ($initPageFilters) $this->initPageFilters();
|
||||
if ($initShortcodes) $this->initShortcodes();
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function isPreview() {
|
||||
return (array_key_exists('preview', $_GET) || array_key_exists('preview', $this->data));
|
||||
}
|
||||
|
||||
public function initPageFilters() {
|
||||
$this->wp->addFilter('wp_title', [$this, 'setWindowTitle'], 10, 3);
|
||||
$this->wp->addFilter('document_title_parts', [$this, 'setWindowTitleParts'], 10, 1);
|
||||
$this->wp->addFilter('the_title', [$this, 'setPageTitle'], 10, 1);
|
||||
$this->wp->addFilter('the_content', [$this, 'setPageContent'], 10, 1);
|
||||
$this->wp->removeAction('wp_head', 'noindex', 1);
|
||||
$this->wp->addAction('wp_head', [$this, 'setMetaRobots'], 1);
|
||||
}
|
||||
|
||||
public function initShortcodes() {
|
||||
$this->wp->addShortcode('mailpoet_manage', [$this, 'getManageLink']);
|
||||
$this->wp->addShortcode('mailpoet_manage_subscription', [$this, 'getManageContent']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SubscriberEntity|null
|
||||
*/
|
||||
private function getSubscriber() {
|
||||
if (!is_null($this->subscriber)) {
|
||||
return $this->subscriber;
|
||||
}
|
||||
|
||||
$token = (isset($this->data['token'])) ? $this->data['token'] : null;
|
||||
$email = (isset($this->data['email'])) ? $this->data['email'] : null;
|
||||
|
||||
if (!$email) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$subscriber = $this->subscribersRepository->findOneBy(['email' => $email]);
|
||||
return ($subscriber instanceof SubscriberEntity && $this->linkTokens->verifyToken($subscriber, $token)) ? $subscriber : null;
|
||||
}
|
||||
|
||||
public function confirm() {
|
||||
$this->subscriber = $this->getSubscriber();
|
||||
if ($this->subscriber === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$subscriberData = json_decode((string)$this->subscriber->getUnconfirmedData(), true);
|
||||
$originalStatus = $this->subscriber->getStatus();
|
||||
|
||||
$this->subscriber->setStatus(SubscriberEntity::STATUS_SUBSCRIBED);
|
||||
$this->subscriber->setConfirmedIp(Helpers::getIP());
|
||||
$this->subscriber->setConfirmedAt(Carbon::now()->millisecond(0));
|
||||
$this->subscriber->setLastSubscribedAt(Carbon::now()->millisecond(0));
|
||||
$this->subscriber->setUnconfirmedData(null);
|
||||
|
||||
try {
|
||||
$this->entityManager->persist($this->subscriber);
|
||||
$this->entityManager->flush();
|
||||
|
||||
// start subscriber tracking
|
||||
$this->subscriberHandler->identifyByEmail($this->subscriber->getEmail());
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Schedule welcome emails
|
||||
$subscriberSegments = $this->subscriber->getSegments()->toArray();
|
||||
if ($subscriberSegments) {
|
||||
$this->welcomeScheduler->scheduleSubscriberWelcomeNotification(
|
||||
$this->subscriber->getId(),
|
||||
array_map(function (SegmentEntity $segment) {
|
||||
return $segment->getId();
|
||||
}, $subscriberSegments)
|
||||
);
|
||||
}
|
||||
|
||||
// when global status changes to subscribed, fire subscribed hook for all subscribed segments
|
||||
$segments = $this->subscriber->getSubscriberSegments();
|
||||
if ($originalStatus !== SubscriberEntity::STATUS_SUBSCRIBED) {
|
||||
foreach ($segments as $subscriberSegment) {
|
||||
if ($subscriberSegment->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED) {
|
||||
$this->wp->doAction('mailpoet_segment_subscribed', $subscriberSegment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send new subscriber notification only when status changes to subscribed or there are unconfirmed data to avoid spamming
|
||||
if ($originalStatus !== SubscriberEntity::STATUS_SUBSCRIBED || $subscriberData !== null) {
|
||||
$this->newSubscriberNotificationSender->send($this->subscriber, $subscriberSegments);
|
||||
}
|
||||
|
||||
// Update subscriber from stored data after confirmation
|
||||
if (!empty($subscriberData)) {
|
||||
$this->subscriberSaveController->createOrUpdate((array)$subscriberData, $this->subscriber);
|
||||
$this->subscriberSaveController->updateCustomFields((array)$subscriberData, $this->subscriber);
|
||||
}
|
||||
}
|
||||
|
||||
public function unsubscribe(string $method): void {
|
||||
if (
|
||||
!$this->isPreview()
|
||||
&& (!is_null($this->subscriber))
|
||||
&& ($this->subscriber->getStatus() !== SubscriberEntity::STATUS_UNSUBSCRIBED)
|
||||
) {
|
||||
if ($this->trackingConfig->isEmailTrackingEnabled() && isset($this->data['queueId'])) {
|
||||
$queueId = (int)$this->data['queueId'];
|
||||
|
||||
if ($method === StatisticsUnsubscribeEntity::METHOD_ONE_CLICK) {
|
||||
/**
|
||||
* With 1-click method, redirect shouldn't happen that's why the click state should be directly recorded
|
||||
*/
|
||||
$this->updateClickStatistics($queueId);
|
||||
}
|
||||
|
||||
$this->unsubscribesTracker->track(
|
||||
(int)$this->subscriber->getId(),
|
||||
StatisticsUnsubscribeEntity::SOURCE_NEWSLETTER,
|
||||
$queueId,
|
||||
null,
|
||||
$method
|
||||
);
|
||||
}
|
||||
$this->subscriber->setStatus(SubscriberEntity::STATUS_UNSUBSCRIBED);
|
||||
$this->subscribersRepository->persist($this->subscriber);
|
||||
$this->subscribersRepository->flush();
|
||||
|
||||
$this->subscriberSegmentRepository->unsubscribeFromSegments($this->subscriber);
|
||||
}
|
||||
}
|
||||
|
||||
public function setMetaRobots() {
|
||||
echo '<meta name="robots" content="noindex,nofollow">';
|
||||
}
|
||||
|
||||
public function setPageTitle($pageTitle = '') {
|
||||
global $post;
|
||||
|
||||
if (
|
||||
(!isset($post))
|
||||
||
|
||||
($post->post_title !== __('MailPoet Page', 'mailpoet')) // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
||
|
||||
($pageTitle !== $this->wp->singlePostTitle('', false))
|
||||
) {
|
||||
// when it's a custom page, just return the original page title
|
||||
return $pageTitle;
|
||||
} elseif ($this->isPreview() === false && $this->subscriber === null) {
|
||||
return __("Hmmm... we don't have a record of you.", 'mailpoet');
|
||||
} else {
|
||||
// when it's our own page, generate page title based on requested action
|
||||
switch ($this->action) {
|
||||
case self::ACTION_CONFIRM:
|
||||
return $this->getConfirmTitle();
|
||||
|
||||
case self::ACTION_CONFIRM_UNSUBSCRIBE:
|
||||
return $this->getConfirmUnsubscribeTitle();
|
||||
|
||||
case self::ACTION_MANAGE:
|
||||
return $this->getManageTitle();
|
||||
|
||||
case self::ACTION_UNSUBSCRIBE:
|
||||
return $this->getUnsubscribeTitle();
|
||||
|
||||
case self::ACTION_RE_ENGAGEMENT:
|
||||
return $this->getReEngagementTitle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function setPageContent($pageContent = '[mailpoet_page]') {
|
||||
if ($this->isPreview() === false && $this->subscriber === null) {
|
||||
return __("Your email address doesn't appear in our lists anymore. Sign up again or contact us if this appears to be a mistake.", 'mailpoet');
|
||||
}
|
||||
|
||||
$this->assetsController->setupFrontEndDependencies();
|
||||
|
||||
if (strpos($pageContent, '[mailpoet_page]') !== false) {
|
||||
$content = '';
|
||||
|
||||
switch ($this->action) {
|
||||
case self::ACTION_CONFIRM:
|
||||
$content = $this->getConfirmContent();
|
||||
break;
|
||||
case self::ACTION_CONFIRM_UNSUBSCRIBE:
|
||||
$content = $this->getConfirmUnsubscribeContent();
|
||||
break;
|
||||
case self::ACTION_MANAGE:
|
||||
$content = $this->getManageContent();
|
||||
break;
|
||||
case self::ACTION_UNSUBSCRIBE:
|
||||
$content = $this->getUnsubscribeContent();
|
||||
break;
|
||||
case self::ACTION_RE_ENGAGEMENT:
|
||||
$content = $this->getReEngagementContent();
|
||||
break;
|
||||
}
|
||||
return str_replace('[mailpoet_page]', trim($content), $pageContent);
|
||||
} else {
|
||||
return $pageContent;
|
||||
}
|
||||
}
|
||||
|
||||
public function setWindowTitle($title, $separator, $separatorLocation = 'right') {
|
||||
$titleParts = explode(" $separator ", $title);
|
||||
if (!is_array($titleParts)) {
|
||||
return $title;
|
||||
}
|
||||
if ($separatorLocation === 'right') {
|
||||
// first part
|
||||
$titleParts[0] = $this->setPageTitle($titleParts[0]);
|
||||
} else {
|
||||
// last part
|
||||
$lastIndex = count($titleParts) - 1;
|
||||
$titleParts[$lastIndex] = $this->setPageTitle($titleParts[$lastIndex]);
|
||||
}
|
||||
return implode(" $separator ", $titleParts);
|
||||
}
|
||||
|
||||
public function setWindowTitleParts($meta = []) {
|
||||
$meta['title'] = $this->setPageTitle($meta['title']);
|
||||
return $meta;
|
||||
}
|
||||
|
||||
private function getConfirmTitle(): string {
|
||||
$wpSiteTitle = $this->wp->getBloginfo('name');
|
||||
|
||||
if (empty($wpSiteTitle)) {
|
||||
$title = __("You are now subscribed!", 'mailpoet');
|
||||
} else {
|
||||
$title = sprintf(
|
||||
// translators: %s is the website title or website name.
|
||||
__("You have subscribed to %s", 'mailpoet'),
|
||||
$wpSiteTitle
|
||||
);
|
||||
}
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
private function getManageTitle() {
|
||||
if ($this->isPreview() || $this->subscriber !== null) {
|
||||
return __("Manage your subscription", 'mailpoet');
|
||||
}
|
||||
}
|
||||
|
||||
private function getUnsubscribeTitle() {
|
||||
if ($this->isPreview() || $this->subscriber !== null) {
|
||||
return __("You are now unsubscribed.", 'mailpoet');
|
||||
}
|
||||
}
|
||||
|
||||
private function getReEngagementTitle() {
|
||||
if ($this->isPreview() || $this->subscriber !== null) {
|
||||
return __('Thank you for letting us know!', 'mailpoet');
|
||||
}
|
||||
}
|
||||
|
||||
private function getConfirmUnsubscribeTitle() {
|
||||
if ($this->isPreview() || $this->subscriber !== null) {
|
||||
return __('Confirm you want to unsubscribe', 'mailpoet');
|
||||
}
|
||||
}
|
||||
|
||||
private function getConfirmContent() {
|
||||
if ($this->isPreview() || $this->subscriber !== null) {
|
||||
return __("Yup, we've added you to our email list. You'll hear from us shortly.", 'mailpoet');
|
||||
}
|
||||
}
|
||||
|
||||
public function getManageContent() {
|
||||
if ($this->isPreview()) {
|
||||
$subscriber = new SubscriberEntity();
|
||||
$subscriber->setEmail(self::DEMO_EMAIL);
|
||||
$subscriber->setFirstName('John');
|
||||
$subscriber->setLastName('Doe');
|
||||
$subscriber->setLinkToken('bfd0889dbc7f081e171fa0cee7401df2');
|
||||
} else if ($this->subscriber !== null) {
|
||||
$subscriber = $this->subscriber;
|
||||
} else if ($this->wp->getCurrentUserId() && $this->subscribersRepository->findOneBy(['wpUserId' => $this->wp->getCurrentUserId()])) {
|
||||
$subscriber = $this->subscribersRepository->findOneBy(['wpUserId' => $this->wp->getCurrentUserId()]);
|
||||
} else {
|
||||
return __('Subscription management form is only available to mailing lists subscribers.', 'mailpoet');
|
||||
}
|
||||
|
||||
$formStatus = isset($_GET['success']) && absint(wp_unslash($_GET['success']))
|
||||
? ManageSubscriptionFormRenderer::FORM_STATE_SUCCESS
|
||||
: ManageSubscriptionFormRenderer::FORM_STATE_NOT_SUBMITTED;
|
||||
|
||||
return $this->wp->applyFilters(
|
||||
'mailpoet_manage_subscription_page',
|
||||
$this->manageSubscriptionFormRenderer->renderForm($subscriber, $formStatus)
|
||||
);
|
||||
}
|
||||
|
||||
private function getUnsubscribeContent() {
|
||||
$content = '';
|
||||
if ($this->isPreview() || $this->subscriber !== null) {
|
||||
$content .= '<p>' . __('Accidentally unsubscribed?', 'mailpoet') . ' <strong>';
|
||||
$content .= '[mailpoet_manage]';
|
||||
$content .= '</strong></p>';
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function getReEngagementContent() {
|
||||
$content = '';
|
||||
if ($this->isPreview() || $this->subscriber !== null) {
|
||||
$content .= '<p>' . __('We appreciate your continued interest in our updates. Expect to hear from us again soon!', 'mailpoet') . '</p>';
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function getConfirmUnsubscribeContent() {
|
||||
if (!$this->isPreview() && $this->subscriber === null) {
|
||||
return '';
|
||||
}
|
||||
$queueId = isset($this->data['queueId']) ? (int)$this->data['queueId'] : null;
|
||||
$unsubscribeUrl = $this->subscriptionUrlFactory->getUnsubscribeUrl($this->subscriber, $queueId);
|
||||
$templateData = [
|
||||
'unsubscribeUrl' => $unsubscribeUrl,
|
||||
];
|
||||
return $this->wp->applyFilters(
|
||||
'mailpoet_unsubscribe_confirmation_page',
|
||||
$this->templateRenderer->render('subscription/confirm_unsubscribe.html', $templateData),
|
||||
$unsubscribeUrl
|
||||
);
|
||||
}
|
||||
|
||||
public function getManageLink($params) {
|
||||
$subscriber = $this->subscriber;
|
||||
if (!$subscriber && $this->subscribersRepository->findOneBy(['wpUserId' => $this->wp->getCurrentUserId()])) {
|
||||
$subscriber = $this->subscribersRepository->findOneBy(['wpUserId' => $this->wp->getCurrentUserId()]);
|
||||
}
|
||||
if (!$subscriber instanceof SubscriberEntity) return __('Link to subscription management page is only available to mailing lists subscribers.', 'mailpoet');
|
||||
|
||||
// get label or display default label
|
||||
$text = isset($params['text'])
|
||||
? htmlspecialchars($params['text'])
|
||||
: __('Manage your subscription', 'mailpoet');
|
||||
|
||||
return '<a href="' . $this->subscriptionUrlFactory->getManageUrl($subscriber) . '">' . $text . '</a>';
|
||||
}
|
||||
|
||||
private function updateClickStatistics(int $queueId): void {
|
||||
$queue = $this->sendingQueuesRepository->findOneById($queueId);
|
||||
if ($queue) {
|
||||
$newsletter = $queue->getNewsletter();
|
||||
$link = $this->newsletterLinkRepository->findOneBy([
|
||||
'url' => NewsletterLinkEntity::INSTANT_UNSUBSCRIBE_LINK_SHORT_CODE,
|
||||
'queue' => $queueId,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
if ($queue && isset($link, $newsletter)) {
|
||||
$this->statisticsClicksRepository->createOrUpdateClickCount(
|
||||
$link,
|
||||
$this->subscriber,
|
||||
$newsletter,
|
||||
$queue,
|
||||
null
|
||||
);
|
||||
$this->statisticsClicksRepository->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Subscription;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Statistics\Track\SubscriberHandler;
|
||||
use MailPoet\Subscribers\SubscriberActions;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class Registration {
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
/** @var SubscriberActions */
|
||||
private $subscriberActions;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var SubscriberHandler */
|
||||
private $subscriberHandler;
|
||||
|
||||
public function __construct(
|
||||
SettingsController $settings,
|
||||
WPFunctions $wp,
|
||||
SubscriberActions $subscriberActions,
|
||||
SubscriberHandler $subscriberHandler
|
||||
) {
|
||||
$this->settings = $settings;
|
||||
$this->subscriberActions = $subscriberActions;
|
||||
$this->wp = $wp;
|
||||
$this->subscriberHandler = $subscriberHandler;
|
||||
}
|
||||
|
||||
public function extendForm() {
|
||||
$label = $this->settings->get(
|
||||
'subscribe.on_register.label',
|
||||
__('Yes, please add me to your mailing list.', 'mailpoet')
|
||||
);
|
||||
|
||||
$form = '<p class="registration-form-mailpoet">
|
||||
<label for="mailpoet_subscribe_on_register">
|
||||
<input
|
||||
type="hidden"
|
||||
id="mailpoet_subscribe_on_register_active"
|
||||
value="1"
|
||||
name="mailpoet[subscribe_on_register_active]"
|
||||
/>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="mailpoet_subscribe_on_register"
|
||||
value="1"
|
||||
name="mailpoet[subscribe_on_register]"
|
||||
/> ' . esc_attr($label) . '
|
||||
</label>
|
||||
</p>';
|
||||
|
||||
$form = (string)$this->wp->applyFilters('mailpoet_register_form_extend', $form);
|
||||
|
||||
// We control the template and $form can be considered safe.
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
print $form;
|
||||
}
|
||||
|
||||
public function onMultiSiteRegister($result) {
|
||||
if (empty($result['errors']->errors)) {
|
||||
if (
|
||||
isset($_POST['mailpoet']['subscribe_on_register'])
|
||||
&& (bool)$_POST['mailpoet']['subscribe_on_register'] === true
|
||||
&& !empty($result['user_email'])
|
||||
) {
|
||||
$this->subscribeNewUser(
|
||||
$result['user_name'],
|
||||
$result['user_email']
|
||||
);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function onRegister(
|
||||
$errors,
|
||||
$userLogin,
|
||||
$userEmail = null
|
||||
) {
|
||||
if (
|
||||
empty($errors->errors)
|
||||
&& isset($_POST['mailpoet']['subscribe_on_register'])
|
||||
&& (bool)$_POST['mailpoet']['subscribe_on_register'] === true
|
||||
&& !empty($userEmail)
|
||||
) {
|
||||
$this->subscribeNewUser(
|
||||
$userLogin,
|
||||
$userEmail
|
||||
);
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
private function subscribeNewUser($name, $email) {
|
||||
$segmentIds = $this->settings->get(
|
||||
'subscribe.on_register.segments',
|
||||
[]
|
||||
);
|
||||
$this->subscriberActions->subscribe(
|
||||
[
|
||||
'email' => $email,
|
||||
'first_name' => $name,
|
||||
],
|
||||
$segmentIds
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* On multisite headers are already sent at this point, tracking will start
|
||||
* once the user has activated his account at a later stage.
|
||||
**/
|
||||
if (!headers_sent()) {
|
||||
// start subscriber tracking (by email, we don't have WP user ID yet)
|
||||
$this->subscriberHandler->identifyByEmail($email);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Subscription;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\DI\ContainerWrapper;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Router\Endpoints\Subscription as SubscriptionEndpoint;
|
||||
use MailPoet\Router\Router;
|
||||
use MailPoet\Settings\Pages as SettingsPages;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Subscribers\LinkTokens;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class SubscriptionUrlFactory {
|
||||
|
||||
/** @var SubscriptionUrlFactory */
|
||||
private static $instance;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var SettingsController */
|
||||
private $settings;
|
||||
|
||||
/** @var LinkTokens */
|
||||
private $linkTokens;
|
||||
|
||||
public function __construct(
|
||||
WPFunctions $wp,
|
||||
SettingsController $settings,
|
||||
LinkTokens $linkTokens
|
||||
) {
|
||||
$this->wp = $wp;
|
||||
$this->settings = $settings;
|
||||
$this->linkTokens = $linkTokens;
|
||||
}
|
||||
|
||||
public function getConfirmationUrl(SubscriberEntity $subscriber = null) {
|
||||
$post = $this->getPost($this->settings->get('subscription.pages.confirmation'));
|
||||
return $this->getSubscriptionUrl($post, 'confirm', $subscriber);
|
||||
}
|
||||
|
||||
public function getConfirmUnsubscribeUrl(SubscriberEntity $subscriber = null, int $queueId = null) {
|
||||
$post = $this->getPost($this->settings->get('subscription.pages.confirm_unsubscribe'));
|
||||
$data = $queueId && $subscriber ? ['queueId' => $queueId] : null;
|
||||
return $this->getSubscriptionUrl($post, 'confirm_unsubscribe', $subscriber, $data);
|
||||
}
|
||||
|
||||
public function getManageUrl(SubscriberEntity $subscriber = null) {
|
||||
$post = $this->getPost($this->settings->get('subscription.pages.manage'));
|
||||
return $this->getSubscriptionUrl($post, 'manage', $subscriber);
|
||||
}
|
||||
|
||||
public function getUnsubscribeUrl(SubscriberEntity $subscriber = null, int $queueId = null) {
|
||||
$post = $this->getPost($this->settings->get('subscription.pages.unsubscribe'));
|
||||
$data = $queueId && $subscriber ? ['queueId' => $queueId] : null;
|
||||
return $this->getSubscriptionUrl($post, 'unsubscribe', $subscriber, $data);
|
||||
}
|
||||
|
||||
public function getReEngagementUrl(SubscriberEntity $subscriber = null) {
|
||||
$reEngagementSetting = $this->settings->get('reEngagement');
|
||||
$postId = $reEngagementSetting['page'] ?? null;
|
||||
|
||||
$post = $this->getPost($postId);
|
||||
return $this->getSubscriptionUrl($post, 're_engagement', $subscriber);
|
||||
}
|
||||
|
||||
public function getSubscriptionUrl(
|
||||
$post = null,
|
||||
$action = null,
|
||||
SubscriberEntity $subscriber = null,
|
||||
$data = null
|
||||
) {
|
||||
if ($post === null || $action === null) return;
|
||||
|
||||
$url = $this->wp->getPermalink($post);
|
||||
if ($subscriber !== null) {
|
||||
$subscriberData = [
|
||||
'token' => $this->linkTokens->getToken($subscriber),
|
||||
'email' => $subscriber->getEmail(),
|
||||
];
|
||||
$data = array_merge($data ?? [], $subscriberData);
|
||||
} elseif (is_null($data)) {
|
||||
$data = [
|
||||
'preview' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
$params = [
|
||||
Router::NAME,
|
||||
'endpoint=' . SubscriptionEndpoint::ENDPOINT,
|
||||
'action=' . $action,
|
||||
'data=' . Router::encodeRequestData($data),
|
||||
];
|
||||
|
||||
// add parameters
|
||||
$url .= (parse_url($url, PHP_URL_QUERY) ? '&' : '?') . join('&', $params);
|
||||
|
||||
$urlParams = parse_url($url);
|
||||
if (!is_array($urlParams) || empty($urlParams['scheme'])) {
|
||||
$url = $this->wp->getBloginfo('url') . $url;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SubscriptionUrlFactory
|
||||
*/
|
||||
public static function getInstance() {
|
||||
if (!self::$instance instanceof SubscriptionUrlFactory) {
|
||||
$linkTokens = ContainerWrapper::getInstance()->get(LinkTokens::class);
|
||||
self::$instance = new SubscriptionUrlFactory(new WPFunctions, SettingsController::getInstance(), $linkTokens);
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
private function getPost($post = null) {
|
||||
if ($post) {
|
||||
$postObject = $this->wp->getPost($post);
|
||||
if ($postObject) {
|
||||
return $postObject;
|
||||
}
|
||||
}
|
||||
// Resort to a default MailPoet page if no page is selected
|
||||
$pages = SettingsPages::getMailPoetPages();
|
||||
return reset($pages);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
||||
|
||||
namespace MailPoet\Subscription;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Entities\SubscriberIPEntity;
|
||||
use MailPoet\Subscribers\SubscriberIPsRepository;
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class Throttling {
|
||||
/** @var SubscriberIPsRepository */
|
||||
private $subscriberIPsRepository;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
public function __construct(
|
||||
SubscriberIPsRepository $subscriberIPsRepository,
|
||||
WPFunctions $wp
|
||||
) {
|
||||
$this->wp = $wp;
|
||||
$this->subscriberIPsRepository = $subscriberIPsRepository;
|
||||
}
|
||||
|
||||
public function throttle() {
|
||||
$subscriptionLimitEnabled = $this->wp->applyFilters('mailpoet_subscription_limit_enabled', true);
|
||||
|
||||
$subscriptionLimitWindow = (int)$this->wp->applyFilters('mailpoet_subscription_limit_window', DAY_IN_SECONDS);
|
||||
$subscriptionLimitBase = (int)$this->wp->applyFilters('mailpoet_subscription_limit_base', MINUTE_IN_SECONDS);
|
||||
|
||||
$subscriberIp = Helpers::getIP();
|
||||
|
||||
if ($subscriptionLimitEnabled && !$this->isUserExemptFromThrottling()) {
|
||||
if (!empty($subscriberIp)) {
|
||||
$subscriptionCount = $this->subscriberIPsRepository->getCountByIPAndCreatedAtAfterTimeInSeconds($subscriberIp, $subscriptionLimitWindow);
|
||||
if ($subscriptionCount > 0) {
|
||||
$timeout = $subscriptionLimitBase * pow(2, $subscriptionCount - 1);
|
||||
// Cap timeout and avoid float numbers
|
||||
$timeout = min($timeout, $subscriptionLimitWindow);
|
||||
$existingUser = $this->subscriberIPsRepository->findOneByIPAndCreatedAtAfterTimeInSeconds($subscriberIp, $timeout);
|
||||
if (!empty($existingUser)) {
|
||||
return $timeout;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($subscriberIp !== null) {
|
||||
$ip = new SubscriberIPEntity($subscriberIp);
|
||||
$existingIp = $this->subscriberIPsRepository->findOneBy(['ip' => $ip->getIP(), 'createdAt' => $ip->getCreatedAt()]);
|
||||
if (!$existingIp) {
|
||||
$this->subscriberIPsRepository->persist($ip);
|
||||
$this->subscriberIPsRepository->flush();
|
||||
}
|
||||
}
|
||||
|
||||
$this->purge();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function purge(): void {
|
||||
$interval = $this->wp->applyFilters('mailpoet_subscription_purge_window', MONTH_IN_SECONDS);
|
||||
$this->subscriberIPsRepository->deleteCreatedAtBeforeTimeInSeconds($interval);
|
||||
}
|
||||
|
||||
public function secondsToTimeString($seconds): string {
|
||||
$hrs = floor($seconds / 3600);
|
||||
$min = floor($seconds % 3600 / 60);
|
||||
$sec = $seconds % 3600 % 60;
|
||||
$result = [
|
||||
// translators: %s is the number of hours
|
||||
'hours' => $hrs ? sprintf(__('%d hours', 'mailpoet'), $hrs) : '',
|
||||
// translators: %s is the number of minutes
|
||||
'minutes' => $min ? sprintf(__('%d minutes', 'mailpoet'), $min) : '',
|
||||
// translators: %s is the number of seconds
|
||||
'seconds' => $sec ? sprintf(__('%d seconds', 'mailpoet'), $sec) : '',
|
||||
];
|
||||
return join(' ', array_filter($result));
|
||||
}
|
||||
|
||||
private function isUserExemptFromThrottling(): bool {
|
||||
if (!$this->wp->isUserLoggedIn()) {
|
||||
return false;
|
||||
}
|
||||
$user = $this->wp->wpGetCurrentUser();
|
||||
$roles = $this->wp->applyFilters('mailpoet_subscription_throttling_exclude_roles', ['administrator', 'editor']);
|
||||
return !empty(array_intersect($roles, (array)$user->roles));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
Reference in New Issue
Block a user