This commit is contained in:
emmymayo
2025-02-05 23:15:46 +01:00
commit 7269c99357
16995 changed files with 3389680 additions and 0 deletions
@@ -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]"
/>&nbsp;' . 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]"
/>&nbsp;' . 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