init
This commit is contained in:
+515
@@ -0,0 +1,515 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Actions;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\AutomaticEmails\WooCommerce\Events\AbandonedCart;
|
||||
use MailPoet\Automation\Engine\Control\AutomationController;
|
||||
use MailPoet\Automation\Engine\Control\StepRunController;
|
||||
use MailPoet\Automation\Engine\Data\Automation;
|
||||
use MailPoet\Automation\Engine\Data\Step;
|
||||
use MailPoet\Automation\Engine\Data\StepRunArgs;
|
||||
use MailPoet\Automation\Engine\Data\StepValidationArgs;
|
||||
use MailPoet\Automation\Engine\Exceptions\NotFoundException;
|
||||
use MailPoet\Automation\Engine\Integration\Action;
|
||||
use MailPoet\Automation\Engine\Integration\ValidationException;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Payloads\SegmentPayload;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Payloads\SubscriberPayload;
|
||||
use MailPoet\Automation\Integrations\WooCommerce\Payloads\AbandonedCartPayload;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Entities\NewsletterOptionEntity;
|
||||
use MailPoet\Entities\NewsletterOptionFieldEntity;
|
||||
use MailPoet\Entities\ScheduledTaskSubscriberEntity;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\InvalidStateException;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Newsletter\Options\NewsletterOptionFieldsRepository;
|
||||
use MailPoet\Newsletter\Options\NewsletterOptionsRepository;
|
||||
use MailPoet\Newsletter\Scheduler\AutomationEmailScheduler;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Subscribers\SubscriberSegmentRepository;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
use MailPoet\Validator\Builder;
|
||||
use MailPoet\Validator\Schema\ObjectSchema;
|
||||
use Throwable;
|
||||
|
||||
class SendEmailAction implements Action {
|
||||
const KEY = 'mailpoet:send-email';
|
||||
|
||||
// Intervals to poll for email status after sending. These are only
|
||||
// used when immediate status sync fails or the email is never sent.
|
||||
private const POLL_INTERVALS = [
|
||||
5 * MINUTE_IN_SECONDS, // ~5 minutes
|
||||
10 * MINUTE_IN_SECONDS, // ~15 minutes
|
||||
45 * MINUTE_IN_SECONDS, // ~1 hour
|
||||
4 * HOUR_IN_SECONDS, // ~5 hours ...from email scheduling
|
||||
19 * HOUR_IN_SECONDS, // ~1 day
|
||||
4 * DAY_IN_SECONDS, // ~5 days
|
||||
25 * DAY_IN_SECONDS, // ~1 month
|
||||
];
|
||||
|
||||
// Retry intervals for sending. These are used when the email address
|
||||
// is not confirmed, and we need send non-transactional emails.
|
||||
private const OPTIN_RETRY_INTERVALS = [
|
||||
1 * MINUTE_IN_SECONDS, // ~1 minute
|
||||
5 * MINUTE_IN_SECONDS, // ~5 minutes
|
||||
20 * MINUTE_IN_SECONDS, // ~20 minutes
|
||||
1 * HOUR_IN_SECONDS, // ~1 hour
|
||||
12 * HOUR_IN_SECONDS, // ~12 hours
|
||||
1 * DAY_IN_SECONDS, // ~1 day
|
||||
];
|
||||
private const WAIT_OPTIN = 'wait_optin';
|
||||
private const OPTIN_RETRIES = 'optin_retries';
|
||||
|
||||
private const TRANSACTIONAL_TRIGGERS = [
|
||||
'woocommerce:order-status-changed',
|
||||
'woocommerce:order-created',
|
||||
'woocommerce:order-completed',
|
||||
'woocommerce:order-cancelled',
|
||||
'woocommerce:abandoned-cart',
|
||||
'woocommerce-subscriptions:subscription-created',
|
||||
'woocommerce-subscriptions:subscription-expired',
|
||||
'woocommerce-subscriptions:subscription-payment-failed',
|
||||
'woocommerce-subscriptions:subscription-renewed',
|
||||
'woocommerce-subscriptions:subscription-status-changed',
|
||||
'woocommerce-subscriptions:trial-ended',
|
||||
'woocommerce-subscriptions:trial-started',
|
||||
'woocommerce:buys-from-a-tag',
|
||||
'woocommerce:buys-from-a-category',
|
||||
'woocommerce:buys-a-product',
|
||||
];
|
||||
|
||||
private AutomationController $automationController;
|
||||
|
||||
private SettingsController $settings;
|
||||
|
||||
private NewslettersRepository $newslettersRepository;
|
||||
|
||||
private SubscriberSegmentRepository $subscriberSegmentRepository;
|
||||
|
||||
private SubscribersRepository $subscribersRepository;
|
||||
|
||||
private SegmentsRepository $segmentsRepository;
|
||||
|
||||
private AutomationEmailScheduler $automationEmailScheduler;
|
||||
|
||||
private NewsletterOptionsRepository $newsletterOptionsRepository;
|
||||
|
||||
private NewsletterOptionFieldsRepository $newsletterOptionFieldsRepository;
|
||||
|
||||
public function __construct(
|
||||
AutomationController $automationController,
|
||||
SettingsController $settings,
|
||||
NewslettersRepository $newslettersRepository,
|
||||
SubscriberSegmentRepository $subscriberSegmentRepository,
|
||||
SubscribersRepository $subscribersRepository,
|
||||
SegmentsRepository $segmentsRepository,
|
||||
AutomationEmailScheduler $automationEmailScheduler,
|
||||
NewsletterOptionsRepository $newsletterOptionsRepository,
|
||||
NewsletterOptionFieldsRepository $newsletterOptionFieldsRepository
|
||||
) {
|
||||
$this->automationController = $automationController;
|
||||
$this->settings = $settings;
|
||||
$this->newslettersRepository = $newslettersRepository;
|
||||
$this->subscriberSegmentRepository = $subscriberSegmentRepository;
|
||||
$this->subscribersRepository = $subscribersRepository;
|
||||
$this->segmentsRepository = $segmentsRepository;
|
||||
$this->automationEmailScheduler = $automationEmailScheduler;
|
||||
$this->newsletterOptionsRepository = $newsletterOptionsRepository;
|
||||
$this->newsletterOptionFieldsRepository = $newsletterOptionFieldsRepository;
|
||||
}
|
||||
|
||||
public function getKey(): string {
|
||||
return self::KEY;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
// translators: automation action title
|
||||
return __('Send email', 'mailpoet');
|
||||
}
|
||||
|
||||
public function getArgsSchema(): ObjectSchema {
|
||||
$nameDefault = $this->settings->get('sender.name');
|
||||
$addressDefault = $this->settings->get('sender.address');
|
||||
$replyToNameDefault = $this->settings->get('reply_to.name');
|
||||
$replyToAddressDefault = $this->settings->get('reply_to.address');
|
||||
|
||||
$nonEmptyString = Builder::string()->required()->minLength(1);
|
||||
return Builder::object([
|
||||
// required fields
|
||||
'email_id' => Builder::integer()->required(),
|
||||
'name' => $nonEmptyString->default(__('Send email', 'mailpoet')),
|
||||
'subject' => $nonEmptyString->default(__('Subject', 'mailpoet')),
|
||||
'preheader' => Builder::string()->required()->default(''),
|
||||
'sender_name' => $nonEmptyString->default($nameDefault),
|
||||
'sender_address' => $nonEmptyString->formatEmail()->default($addressDefault),
|
||||
|
||||
// optional fields
|
||||
'reply_to_name' => ($replyToNameDefault && $replyToNameDefault !== $nameDefault)
|
||||
? Builder::string()->minLength(1)->default($replyToNameDefault)
|
||||
: Builder::string()->minLength(1),
|
||||
'reply_to_address' => ($replyToAddressDefault && $replyToAddressDefault !== $addressDefault)
|
||||
? Builder::string()->formatEmail()->default($replyToAddressDefault)
|
||||
: Builder::string()->formatEmail(),
|
||||
'ga_campaign' => Builder::string()->minLength(1),
|
||||
]);
|
||||
}
|
||||
|
||||
public function getSubjectKeys(): array {
|
||||
return [
|
||||
'mailpoet:subscriber',
|
||||
];
|
||||
}
|
||||
|
||||
public function validate(StepValidationArgs $args): void {
|
||||
try {
|
||||
$this->getEmailForStep($args->getStep());
|
||||
} catch (InvalidStateException $exception) {
|
||||
$exception = ValidationException::create()
|
||||
->withMessage(__('Cannot send the email because it was not found. Please, go to the automation editor and update the email contents.', 'mailpoet'));
|
||||
|
||||
$emailId = $args->getStep()->getArgs()['email_id'] ?? '';
|
||||
if (empty($emailId)) {
|
||||
$exception->withError('email_id', __("Automation email not found.", 'mailpoet'));
|
||||
} else {
|
||||
$exception->withError(
|
||||
'email_id',
|
||||
// translators: %s is the ID of email.
|
||||
sprintf(__("Automation email with ID '%s' not found.", 'mailpoet'), $emailId)
|
||||
);
|
||||
}
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
public function run(StepRunArgs $args, StepRunController $controller): void {
|
||||
$newsletter = $this->getEmailForStep($args->getStep());
|
||||
$subscriber = $this->getSubscriber($args);
|
||||
$state = null;
|
||||
|
||||
if ($args->isFirstRun()) {
|
||||
$subscriberStatus = $subscriber->getStatus();
|
||||
if ($subscriberStatus === SubscriberEntity::STATUS_BOUNCED) {
|
||||
// translators: %s is the subscriber's status.
|
||||
throw InvalidStateException::create()->withMessage(sprintf(__("Cannot send the email because the subscriber's status is '%s'.", 'mailpoet'), $subscriberStatus));
|
||||
}
|
||||
|
||||
if ($this->isOptInRequired($newsletter, $subscriber)) {
|
||||
$controller->getRunLog()->saveLogData([self::WAIT_OPTIN => 1]);
|
||||
$this->rerunLater($args->getRunNumber(), $controller, $newsletter, $subscriber);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->scheduleEmail($args, $newsletter, $subscriber);
|
||||
} else {
|
||||
// Re-running for opt-in?
|
||||
$state = $this->getRunLogData($controller);
|
||||
|
||||
if (array_key_exists(self::WAIT_OPTIN, $state) && $state[self::WAIT_OPTIN] === 1) {
|
||||
if ($this->isOptInRequired($newsletter, $subscriber)) {
|
||||
$this->rerunLater($args->getRunNumber(), $controller, $newsletter, $subscriber);
|
||||
return;
|
||||
}
|
||||
|
||||
// Subscriber is now confirmed, so we can schedule an email.
|
||||
$controller->getRunLog()->saveLogData([
|
||||
self::WAIT_OPTIN => 0,
|
||||
self::OPTIN_RETRIES => $args->getRunNumber(),
|
||||
]);
|
||||
$this->scheduleEmail($args, $newsletter, $subscriber);
|
||||
}
|
||||
|
||||
// Check/sync sending status with the automation step
|
||||
$success = $this->checkSendingStatus($args, $newsletter, $subscriber);
|
||||
if ($success) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, we're re-running to check sending status. We need
|
||||
// to offset opt-in reruns count from sending reruns.
|
||||
$runNumber = $args->getRunNumber();
|
||||
$state = $state ?? $this->getRunLogData($controller);
|
||||
$optinRetryCount = $state[self::OPTIN_RETRIES] ?? 0;
|
||||
$runNumber -= $optinRetryCount;
|
||||
$this->rerunLater($runNumber, $controller, $newsletter, $subscriber);
|
||||
}
|
||||
|
||||
private function scheduleEmail(StepRunArgs $args, NewsletterEntity $newsletter, SubscriberEntity $subscriber): void {
|
||||
$meta = $this->getNewsletterMeta($args);
|
||||
try {
|
||||
$this->automationEmailScheduler->createSendingTask($newsletter, $subscriber, $meta);
|
||||
} catch (Throwable $e) {
|
||||
throw InvalidStateException::create()->withMessage(__('Could not create sending task.', 'mailpoet'));
|
||||
}
|
||||
}
|
||||
|
||||
private function getRunLogData(StepRunController $controller): array {
|
||||
$runLog = $controller->getRunLog()->getLog();
|
||||
return $runLog->getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a progress run to sync the email sending status to the automation step.
|
||||
* Normally, a progress run is executed immediately after sending; we're scheduling
|
||||
* these runs to poll for the status if sync fails or email never sends (timeout),
|
||||
* or if we need to wait for subscriber opt-in.
|
||||
*/
|
||||
private function rerunLater(int $runNumber, StepRunController $controller, NewsletterEntity $newsletter, SubscriberEntity $subscriber): void {
|
||||
$nextInterval = self::POLL_INTERVALS[$runNumber - 1] ?? 0;
|
||||
|
||||
// Use different intervals when retrying for opt-in.
|
||||
if ($this->isOptInRequired($newsletter, $subscriber)) {
|
||||
if ($runNumber > count(self::OPTIN_RETRY_INTERVALS)) {
|
||||
$subscriberStatus = $subscriber->getStatus();
|
||||
// translators: %s is the subscriber's status.
|
||||
throw InvalidStateException::create()->withMessage(sprintf(__("Cannot send the email because the subscriber's status is '%s'.", 'mailpoet'), $subscriberStatus));
|
||||
}
|
||||
$nextInterval = self::OPTIN_RETRY_INTERVALS[$runNumber - 1];
|
||||
}
|
||||
|
||||
$controller->scheduleProgress(time() + $nextInterval);
|
||||
}
|
||||
|
||||
private function isOptInRequired(NewsletterEntity $newsletter, SubscriberEntity $subscriber): bool {
|
||||
$subscriberStatus = $subscriber->getStatus();
|
||||
if ($newsletter->getType() === NewsletterEntity::TYPE_AUTOMATION_TRANSACTIONAL) return false;
|
||||
return $subscriberStatus !== SubscriberEntity::STATUS_SUBSCRIBED;
|
||||
}
|
||||
|
||||
/** @param mixed $data */
|
||||
public function handleEmailSent($data): void {
|
||||
if (!is_array($data)) {
|
||||
throw InvalidStateException::create()->withMessage(
|
||||
// translators: %s is the type of $data.
|
||||
sprintf(__('Invalid automation step data. Array expected, got: %s', 'mailpoet'), gettype($data))
|
||||
);
|
||||
}
|
||||
|
||||
$runId = $data['run_id'] ?? null;
|
||||
if (!is_int($runId)) {
|
||||
throw InvalidStateException::create()->withMessage(
|
||||
// translators: %s is the type of $runId.
|
||||
sprintf(__("Invalid automation step data. Expected 'run_id' to be an integer, got: %s", 'mailpoet'), gettype($runId))
|
||||
);
|
||||
}
|
||||
|
||||
$stepId = $data['step_id'] ?? null;
|
||||
if (!is_string($stepId)) {
|
||||
throw InvalidStateException::create()->withMessage(
|
||||
// translators: %s is the type of $runId.
|
||||
sprintf(__("Invalid automation step data. Expected 'step_id' to be a string, got: %s", 'mailpoet'), gettype($runId))
|
||||
);
|
||||
}
|
||||
|
||||
$this->automationController->enqueueProgress($runId, $stepId);
|
||||
}
|
||||
|
||||
private function checkSendingStatus(StepRunArgs $args, NewsletterEntity $newsletter, SubscriberEntity $subscriber): bool {
|
||||
$scheduledTaskSubscriber = $this->automationEmailScheduler->getScheduledTaskSubscriber($newsletter, $subscriber, $args->getAutomationRun());
|
||||
if (!$scheduledTaskSubscriber) {
|
||||
throw InvalidStateException::create()->withMessage(__('Email failed to schedule.', 'mailpoet'));
|
||||
}
|
||||
|
||||
// email sending failed
|
||||
if ($scheduledTaskSubscriber->getFailed() === ScheduledTaskSubscriberEntity::FAIL_STATUS_FAILED) {
|
||||
throw InvalidStateException::create()->withMessage(
|
||||
// translators: %s is the error message.
|
||||
sprintf(__('Email failed to send. Error: %s', 'mailpoet'), $scheduledTaskSubscriber->getError() ?: 'Unknown error')
|
||||
);
|
||||
}
|
||||
|
||||
$wasSent = $scheduledTaskSubscriber->getProcessed() === ScheduledTaskSubscriberEntity::STATUS_PROCESSED;
|
||||
$isLastRun = $args->getRunNumber() >= 1 + count(self::POLL_INTERVALS);
|
||||
|
||||
// email was never sent
|
||||
if (!$wasSent && $isLastRun) {
|
||||
$error = __('Email sending process timed out.', 'mailpoet');
|
||||
$this->automationEmailScheduler->saveError($scheduledTaskSubscriber, $error);
|
||||
throw InvalidStateException::create()->withMessage($error);
|
||||
}
|
||||
|
||||
return $wasSent;
|
||||
}
|
||||
|
||||
private function getNewsletterMeta(StepRunArgs $args): array {
|
||||
$meta = [
|
||||
'automation' => [
|
||||
'id' => $args->getAutomation()->getId(),
|
||||
'run_id' => $args->getAutomationRun()->getId(),
|
||||
'step_id' => $args->getStep()->getId(),
|
||||
'run_number' => $args->getRunNumber(),
|
||||
],
|
||||
];
|
||||
|
||||
if ($this->automationHasAbandonedCartTrigger($args->getAutomation())) {
|
||||
$payload = $args->getSinglePayloadByClass(AbandonedCartPayload::class);
|
||||
$meta[AbandonedCart::TASK_META_NAME] = $payload->getProductIds();
|
||||
}
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
||||
private function getSubscriber(StepRunArgs $args): SubscriberEntity {
|
||||
$subscriberId = $args->getSinglePayloadByClass(SubscriberPayload::class)->getId();
|
||||
try {
|
||||
$segmentId = $args->getSinglePayloadByClass(SegmentPayload::class)->getId();
|
||||
} catch (NotFoundException $e) {
|
||||
$segmentId = null;
|
||||
}
|
||||
|
||||
// Without segment, fetch subscriber by ID (needed e.g. for "mailpoet:custom-trigger").
|
||||
// Transactional emails don't need to be checked against segment, no matter if it's set.
|
||||
if (!$segmentId || $this->isTransactional($args->getStep(), $args->getAutomation())) {
|
||||
$subscriber = $this->subscribersRepository->findOneById($subscriberId);
|
||||
if (!$subscriber) {
|
||||
throw InvalidStateException::create();
|
||||
}
|
||||
return $subscriber;
|
||||
}
|
||||
|
||||
// With segment, fetch subscriber segment and check if they are subscribed.
|
||||
$subscriberSegment = $this->subscriberSegmentRepository->findOneBy([
|
||||
'subscriber' => $subscriberId,
|
||||
'segment' => $segmentId,
|
||||
'status' => SubscriberEntity::STATUS_SUBSCRIBED,
|
||||
]);
|
||||
|
||||
if (!$subscriberSegment) {
|
||||
$segment = $this->segmentsRepository->findOneById($segmentId);
|
||||
if (!$segment) { // This state should not happen because it is checked in the validation.
|
||||
throw InvalidStateException::create()->withMessage(__('Cannot send the email because the list was not found.', 'mailpoet'));
|
||||
}
|
||||
// translators: %s is the name of the list.
|
||||
throw InvalidStateException::create()->withMessage(sprintf(__("Cannot send the email because the subscriber is not subscribed to the '%s' list.", 'mailpoet'), $segment->getName()));
|
||||
}
|
||||
|
||||
$subscriber = $subscriberSegment->getSubscriber();
|
||||
if (!$subscriber) {
|
||||
throw InvalidStateException::create();
|
||||
}
|
||||
return $subscriber;
|
||||
}
|
||||
|
||||
public function saveEmailSettings(Step $step, Automation $automation): void {
|
||||
$args = $step->getArgs();
|
||||
if (!isset($args['email_id']) || !$args['email_id']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$email = $this->getEmailForStep($step);
|
||||
$email->setType($this->isTransactional($step, $automation) ? NewsletterEntity::TYPE_AUTOMATION_TRANSACTIONAL : NewsletterEntity::TYPE_AUTOMATION);
|
||||
$email->setStatus(NewsletterEntity::STATUS_ACTIVE);
|
||||
$email->setSubject($args['subject'] ?? '');
|
||||
$email->setPreheader($args['preheader'] ?? '');
|
||||
$email->setSenderName($args['sender_name'] ?? '');
|
||||
$email->setSenderAddress($args['sender_address'] ?? '');
|
||||
$email->setReplyToName($args['reply_to_name'] ?? '');
|
||||
$email->setReplyToAddress($args['reply_to_address'] ?? '');
|
||||
$email->setGaCampaign($args['ga_campaign'] ?? '');
|
||||
$this->storeNewsletterOption(
|
||||
$email,
|
||||
NewsletterOptionFieldEntity::NAME_GROUP,
|
||||
$this->automationHasWooCommerceTrigger($automation) ? 'woocommerce' : null
|
||||
);
|
||||
$this->storeNewsletterOption(
|
||||
$email,
|
||||
NewsletterOptionFieldEntity::NAME_EVENT,
|
||||
$this->automationHasAbandonedCartTrigger($automation) ? 'woocommerce_abandoned_shopping_cart' : null
|
||||
);
|
||||
|
||||
$this->newslettersRepository->persist($email);
|
||||
$this->newslettersRepository->flush();
|
||||
}
|
||||
|
||||
private function storeNewsletterOption(NewsletterEntity $newsletter, string $optionName, string $optionValue = null): void {
|
||||
$options = $newsletter->getOptions()->toArray();
|
||||
foreach ($options as $key => $option) {
|
||||
if ($option->getName() === $optionName) {
|
||||
if ($optionValue) {
|
||||
$option->setValue($optionValue);
|
||||
return;
|
||||
}
|
||||
$newsletter->getOptions()->remove($key);
|
||||
$this->newsletterOptionsRepository->remove($option);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$optionValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
$field = $this->newsletterOptionFieldsRepository->findOneBy([
|
||||
'name' => $optionName,
|
||||
'newsletterType' => $newsletter->getType(),
|
||||
]);
|
||||
if (!$field) {
|
||||
return;
|
||||
}
|
||||
$option = new NewsletterOptionEntity($newsletter, $field);
|
||||
$option->setValue($optionValue);
|
||||
$this->newsletterOptionsRepository->persist($option);
|
||||
$newsletter->getOptions()->add($option);
|
||||
}
|
||||
|
||||
private function isTransactional(Step $step, Automation $automation): bool {
|
||||
$triggers = $automation->getTriggers();
|
||||
$transactionalTriggers = array_filter(
|
||||
$triggers,
|
||||
function(Step $step): bool {
|
||||
return in_array($step->getKey(), self::TRANSACTIONAL_TRIGGERS, true);
|
||||
}
|
||||
);
|
||||
|
||||
if (!$triggers || count($transactionalTriggers) !== count($triggers)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($transactionalTriggers as $trigger) {
|
||||
if (!in_array($step->getId(), $trigger->getNextStepIds(), true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private function automationHasWooCommerceTrigger(Automation $automation): bool {
|
||||
return (bool)array_filter(
|
||||
$automation->getTriggers(),
|
||||
function(Step $step): bool {
|
||||
return strpos($step->getKey(), 'woocommerce:') === 0;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private function automationHasAbandonedCartTrigger(Automation $automation): bool {
|
||||
return (bool)array_filter(
|
||||
$automation->getTriggers(),
|
||||
function(Step $step): bool {
|
||||
return in_array($step->getKey(), ['woocommerce:abandoned-cart'], true);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private function getEmailForStep(Step $step): NewsletterEntity {
|
||||
$emailId = $step->getArgs()['email_id'] ?? null;
|
||||
if (!$emailId) {
|
||||
throw InvalidStateException::create();
|
||||
}
|
||||
|
||||
$email = $this->newslettersRepository->findOneBy([
|
||||
'id' => $emailId,
|
||||
]);
|
||||
if (!$email || !in_array($email->getType(), [NewsletterEntity::TYPE_AUTOMATION, NewsletterEntity::TYPE_AUTOMATION_TRANSACTIONAL], true)) {
|
||||
throw InvalidStateException::create()->withMessage(
|
||||
// translators: %s is the ID of email.
|
||||
sprintf(__("Automation email with ID '%s' not found.", 'mailpoet'), $emailId)
|
||||
);
|
||||
}
|
||||
return $email;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Analytics;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\REST\API;
|
||||
use MailPoet\Automation\Engine\Hooks;
|
||||
use MailPoet\Automation\Engine\WordPress;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Analytics\Endpoints\AutomationFlowEndpoint;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Analytics\Endpoints\OverviewEndpoint;
|
||||
|
||||
class Analytics {
|
||||
|
||||
/** @var WordPress */
|
||||
private $wordPress;
|
||||
|
||||
public function __construct(
|
||||
WordPress $wordPress
|
||||
) {
|
||||
$this->wordPress = $wordPress;
|
||||
}
|
||||
|
||||
public function register(): void {
|
||||
$this->wordPress->addAction(Hooks::API_INITIALIZE, function (API $api) {
|
||||
$api->registerGetRoute('automation/analytics/automation_flow', AutomationFlowEndpoint::class);
|
||||
$api->registerGetRoute('automation/analytics/overview', OverviewEndpoint::class);
|
||||
});
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Analytics\Controller;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Data\Automation;
|
||||
use MailPoet\Automation\Engine\Storage\AutomationStorage;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Actions\SendEmailAction;
|
||||
use MailPoet\Entities\NewsletterEntity;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
|
||||
class AutomationTimeSpanController {
|
||||
|
||||
/** @var AutomationStorage */
|
||||
private $automationStorage;
|
||||
|
||||
/** @var NewslettersRepository */
|
||||
private $newslettersRepository;
|
||||
|
||||
public function __construct(
|
||||
AutomationStorage $automationStorage,
|
||||
NewslettersRepository $newslettersRepository
|
||||
) {
|
||||
$this->automationStorage = $automationStorage;
|
||||
$this->newslettersRepository = $newslettersRepository;
|
||||
}
|
||||
|
||||
public function getAutomationsInTimespan(Automation $automation, \DateTimeImmutable $after, \DateTimeImmutable $before): array {
|
||||
$automationVersions = $this->automationStorage->getAutomationVersionDates($automation->getId());
|
||||
usort(
|
||||
$automationVersions,
|
||||
function (array $a, array $b) {
|
||||
return $a['created_at'] <=> $b['created_at'];
|
||||
}
|
||||
);
|
||||
|
||||
// Find all versions, which could have been active in the given time span
|
||||
$versionIds = [];
|
||||
foreach ($automationVersions as $automationVersion) {
|
||||
if ($automationVersion['created_at'] > $before) {
|
||||
// We are past the time span
|
||||
break;
|
||||
}
|
||||
if (!$versionIds || $automationVersion['created_at'] <= $after) {
|
||||
// This is the first version in the time span
|
||||
$versionIds = [(int)$automationVersion['id']];
|
||||
continue;
|
||||
}
|
||||
$versionIds[] = (int)$automationVersion['id'];
|
||||
}
|
||||
|
||||
return count($versionIds) > 0 ? $this->automationStorage->getAutomationWithDifferentVersions($versionIds) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Automation $automation
|
||||
* @param \DateTimeImmutable $after
|
||||
* @param \DateTimeImmutable $before
|
||||
* @return NewsletterEntity[]
|
||||
*/
|
||||
public function getAutomationEmailsInTimeSpan(Automation $automation, \DateTimeImmutable $after, \DateTimeImmutable $before): array {
|
||||
$automations = $this->getAutomationsInTimespan($automation, $after, $before);
|
||||
return count($automations) > 0 ? $this->getEmailsFromAutomations($automations) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Automation[] $automations
|
||||
* @return NewsletterEntity[]
|
||||
*/
|
||||
public function getEmailsFromAutomations(array $automations): array {
|
||||
$emailSteps = [];
|
||||
foreach ($automations as $automation) {
|
||||
$emailSteps = array_merge(
|
||||
$emailSteps,
|
||||
array_values(
|
||||
array_filter(
|
||||
$automation->getSteps(),
|
||||
function($step) {
|
||||
return $step->getKey() === SendEmailAction::KEY;
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
$emailIds = array_unique(
|
||||
array_filter(
|
||||
array_map(
|
||||
function($step) {
|
||||
$args = $step->getArgs();
|
||||
return isset($args['email_id']) ? absint($args['email_id']) : null;
|
||||
},
|
||||
$emailSteps
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return $this->newslettersRepository->findBy(['id' => $emailIds]);
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Analytics\Controller;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Data\Automation;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Analytics\Entities\QueryWithCompare;
|
||||
use MailPoet\Entities\StatisticsClickEntity;
|
||||
use MailPoet\Entities\StatisticsOpenEntity;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
use MailPoet\Newsletter\Statistics\NewsletterStatisticsRepository;
|
||||
use MailPoet\Newsletter\Statistics\WooCommerceRevenue;
|
||||
use MailPoet\Newsletter\Url as NewsletterUrl;
|
||||
|
||||
class OverviewStatisticsController {
|
||||
/** @var NewslettersRepository */
|
||||
private $newslettersRepository;
|
||||
|
||||
/** @var NewsletterStatisticsRepository */
|
||||
private $newsletterStatisticsRepository;
|
||||
|
||||
/** @var NewsletterUrl */
|
||||
private $newsletterUrl;
|
||||
|
||||
/** @var AutomationTimeSpanController */
|
||||
private $automationTimeSpanController;
|
||||
|
||||
public function __construct(
|
||||
NewslettersRepository $newslettersRepository,
|
||||
NewsletterStatisticsRepository $newsletterStatisticsRepository,
|
||||
NewsletterUrl $newsletterUrl,
|
||||
AutomationTimeSpanController $automationTimeSpanController
|
||||
) {
|
||||
$this->newslettersRepository = $newslettersRepository;
|
||||
$this->newsletterStatisticsRepository = $newsletterStatisticsRepository;
|
||||
$this->newsletterUrl = $newsletterUrl;
|
||||
$this->automationTimeSpanController = $automationTimeSpanController;
|
||||
}
|
||||
|
||||
public function getStatisticsForAutomation(Automation $automation, QueryWithCompare $query): array {
|
||||
$currentEmails = $this->automationTimeSpanController->getAutomationEmailsInTimeSpan($automation, $query->getAfter(), $query->getBefore());
|
||||
$previousEmails = $this->automationTimeSpanController->getAutomationEmailsInTimeSpan($automation, $query->getCompareWithAfter(), $query->getCompareWithBefore());
|
||||
$data = [
|
||||
'sent' => ['current' => 0, 'previous' => 0],
|
||||
'opened' => ['current' => 0, 'previous' => 0],
|
||||
'clicked' => ['current' => 0, 'previous' => 0],
|
||||
'orders' => ['current' => 0, 'previous' => 0],
|
||||
'unsubscribed' => ['current' => 0, 'previous' => 0],
|
||||
'revenue' => ['current' => 0, 'previous' => 0],
|
||||
'emails' => [],
|
||||
];
|
||||
if (!$currentEmails) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$requiredData = [
|
||||
'totals',
|
||||
StatisticsClickEntity::class,
|
||||
StatisticsOpenEntity::class,
|
||||
WooCommerceRevenue::class,
|
||||
];
|
||||
|
||||
$currentStatistics = $this->newsletterStatisticsRepository->getBatchStatistics(
|
||||
$currentEmails,
|
||||
$query->getAfter(),
|
||||
$query->getBefore(),
|
||||
$requiredData
|
||||
);
|
||||
foreach ($currentStatistics as $newsletterId => $statistic) {
|
||||
$data['sent']['current'] += $statistic->getTotalSentCount();
|
||||
$data['opened']['current'] += $statistic->getOpenCount();
|
||||
$data['clicked']['current'] += $statistic->getClickCount();
|
||||
$data['unsubscribed']['current'] += $statistic->getUnsubscribeCount();
|
||||
$data['orders']['current'] += $statistic->getWooCommerceRevenue() ? $statistic->getWooCommerceRevenue()->getOrdersCount() : 0;
|
||||
$data['revenue']['current'] += $statistic->getWooCommerceRevenue() ? $statistic->getWooCommerceRevenue()->getValue() : 0;
|
||||
$newsletter = $this->newslettersRepository->findOneById($newsletterId);
|
||||
$data['emails'][$newsletterId]['id'] = $newsletterId;
|
||||
$data['emails'][$newsletterId]['name'] = $newsletter ? $newsletter->getSubject() : '';
|
||||
$data['emails'][$newsletterId]['sent']['current'] = $statistic->getTotalSentCount();
|
||||
$data['emails'][$newsletterId]['sent']['previous'] = 0;
|
||||
$data['emails'][$newsletterId]['opened'] = $statistic->getOpenCount();
|
||||
$data['emails'][$newsletterId]['clicked'] = $statistic->getClickCount();
|
||||
$data['emails'][$newsletterId]['unsubscribed'] = $statistic->getUnsubscribeCount();
|
||||
$data['emails'][$newsletterId]['orders'] = $statistic->getWooCommerceRevenue() ? $statistic->getWooCommerceRevenue()->getOrdersCount() : 0;
|
||||
$data['emails'][$newsletterId]['revenue'] = $statistic->getWooCommerceRevenue() ? $statistic->getWooCommerceRevenue()->getValue() : 0;
|
||||
$data['emails'][$newsletterId]['previewUrl'] = $newsletter ? $this->newsletterUrl->getViewInBrowserUrl($newsletter) : '';
|
||||
$data['emails'][$newsletterId]['order'] = count($data['emails']);
|
||||
}
|
||||
|
||||
$previousStatistics = $this->newsletterStatisticsRepository->getBatchStatistics(
|
||||
$previousEmails,
|
||||
$query->getCompareWithAfter(),
|
||||
$query->getCompareWithBefore(),
|
||||
$requiredData
|
||||
);
|
||||
|
||||
foreach ($previousStatistics as $newsletterId => $statistic) {
|
||||
$data['sent']['previous'] += $statistic->getTotalSentCount();
|
||||
$data['opened']['previous'] += $statistic->getOpenCount();
|
||||
$data['clicked']['previous'] += $statistic->getClickCount();
|
||||
$data['unsubscribed']['previous'] += $statistic->getUnsubscribeCount();
|
||||
$data['orders']['previous'] += $statistic->getWooCommerceRevenue() ? $statistic->getWooCommerceRevenue()->getOrdersCount() : 0;
|
||||
$data['revenue']['previous'] += $statistic->getWooCommerceRevenue() ? $statistic->getWooCommerceRevenue()->getValue() : 0;
|
||||
if (isset($data['emails'][$newsletterId])) {
|
||||
$data['emails'][$newsletterId]['sent']['previous'] = $statistic->getTotalSentCount();
|
||||
}
|
||||
}
|
||||
|
||||
usort($data['emails'], function ($a, $b) {
|
||||
return $a['order'] <=> $b['order'];
|
||||
});
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Analytics\Controller;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Data\Automation;
|
||||
use MailPoet\Automation\Engine\Data\AutomationRun;
|
||||
use MailPoet\Automation\Engine\Data\AutomationRunLog;
|
||||
use MailPoet\Automation\Engine\Data\Step;
|
||||
use MailPoet\Automation\Engine\Storage\AutomationRunLogStorage;
|
||||
use MailPoet\Automation\Engine\Storage\AutomationRunStorage;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Analytics\Entities\Query;
|
||||
|
||||
class StepStatisticController {
|
||||
|
||||
/** @var AutomationRunStorage */
|
||||
private $automationRunStorage;
|
||||
|
||||
/** @var AutomationRunLogStorage */
|
||||
private $automationRunLogStorage;
|
||||
|
||||
public function __construct(
|
||||
AutomationRunStorage $automationRunStorage,
|
||||
AutomationRunLogStorage $automationRunLogStorage
|
||||
) {
|
||||
$this->automationRunStorage = $automationRunStorage;
|
||||
$this->automationRunLogStorage = $automationRunLogStorage;
|
||||
}
|
||||
|
||||
public function getWaitingStatistics(Automation $automation, Query $query): array {
|
||||
$rawData = $this->automationRunStorage->getAutomationStepStatisticForTimeFrame(
|
||||
$automation->getId(),
|
||||
AutomationRun::STATUS_RUNNING,
|
||||
$query->getAfter(),
|
||||
$query->getBefore()
|
||||
);
|
||||
|
||||
$data = [];
|
||||
foreach ($automation->getSteps() as $step) {
|
||||
foreach ($rawData as $rawDatum) {
|
||||
if ($rawDatum['next_step_id'] === $step->getId()) {
|
||||
$data[$step->getId()] = (int)$rawDatum['count'];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getFailedStatistics(Automation $automation, Query $query): array {
|
||||
$rawData = $this->automationRunStorage->getAutomationStepStatisticForTimeFrame(
|
||||
$automation->getId(),
|
||||
AutomationRun::STATUS_FAILED,
|
||||
$query->getAfter(),
|
||||
$query->getBefore()
|
||||
);
|
||||
|
||||
$data = [];
|
||||
foreach ($automation->getSteps() as $step) {
|
||||
foreach ($rawData as $rawDatum) {
|
||||
if ($rawDatum['next_step_id'] === $step->getId()) {
|
||||
$data[$step->getId()] = (int)$rawDatum['count'];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getCompletedStatistics(Automation $automation, Query $query): array {
|
||||
$statistics = $this->automationRunLogStorage->getAutomationRunStatisticsForAutomationInTimeFrame(
|
||||
$automation->getId(),
|
||||
AutomationRunLog::STATUS_COMPLETE,
|
||||
$query->getAfter(),
|
||||
$query->getBefore()
|
||||
);
|
||||
|
||||
$data = [];
|
||||
|
||||
foreach ($automation->getSteps() as $step) {
|
||||
if ($step->getType() === Step::TYPE_ROOT) {
|
||||
continue;
|
||||
}
|
||||
$data[$step->getId()] = 0;
|
||||
foreach ($statistics as $stat) {
|
||||
if ($stat['step_id'] === $step->getId()) {
|
||||
$data[$step->getId()] = (int)$stat['count'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Analytics\Endpoints;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\REST\Request;
|
||||
use MailPoet\API\REST\Response;
|
||||
use MailPoet\Automation\Engine\API\Endpoint;
|
||||
use MailPoet\Automation\Engine\Data\Automation;
|
||||
use MailPoet\Automation\Engine\Exceptions;
|
||||
use MailPoet\Automation\Engine\Mappers\AutomationMapper;
|
||||
use MailPoet\Automation\Engine\Storage\AutomationStatisticsStorage;
|
||||
use MailPoet\Automation\Engine\Storage\AutomationStorage;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Analytics\Controller\AutomationTimeSpanController;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Analytics\Controller\StepStatisticController;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Analytics\Entities\Query;
|
||||
use MailPoet\Validator\Builder;
|
||||
|
||||
class AutomationFlowEndpoint extends Endpoint {
|
||||
|
||||
/** @var AutomationStorage */
|
||||
private $automationStorage;
|
||||
|
||||
/** @var AutomationStatisticsStorage */
|
||||
private $automationStatisticsStorage;
|
||||
|
||||
/** @var AutomationMapper */
|
||||
private $automationMapper;
|
||||
|
||||
/** @var AutomationTimeSpanController */
|
||||
private $automationTimeSpanController;
|
||||
|
||||
/** @var StepStatisticController */
|
||||
private $stepStatisticController;
|
||||
|
||||
public function __construct(
|
||||
AutomationStorage $automationStorage,
|
||||
AutomationStatisticsStorage $automationStatisticsStorage,
|
||||
AutomationMapper $automationMapper,
|
||||
AutomationTimeSpanController $automationTimeSpanController,
|
||||
StepStatisticController $stepStatisticController
|
||||
) {
|
||||
$this->automationStorage = $automationStorage;
|
||||
$this->automationStatisticsStorage = $automationStatisticsStorage;
|
||||
$this->automationMapper = $automationMapper;
|
||||
$this->automationTimeSpanController = $automationTimeSpanController;
|
||||
$this->stepStatisticController = $stepStatisticController;
|
||||
}
|
||||
|
||||
public function handle(Request $request): Response {
|
||||
$id = absint(is_numeric($request->getParam('id')) ? $request->getParam('id') : 0);
|
||||
$automation = $this->automationStorage->getAutomation($id);
|
||||
if (!$automation) {
|
||||
throw Exceptions::automationNotFound($id);
|
||||
}
|
||||
$query = Query::fromRequest($request);
|
||||
$automations = $this->automationTimeSpanController->getAutomationsInTimespan($automation, $query->getAfter(), $query->getBefore());
|
||||
if (!count($automations)) {
|
||||
throw Exceptions::automationNotFoundInTimeSpan($id);
|
||||
}
|
||||
$automation = current($automations);
|
||||
$shortStatistics = $this->automationStatisticsStorage->getAutomationStats(
|
||||
$automation->getId(),
|
||||
null,
|
||||
$query->getAfter(),
|
||||
$query->getBefore()
|
||||
);
|
||||
|
||||
$waitingData = $this->stepStatisticController->getWaitingStatistics($automation, $query);
|
||||
$failedData = $this->stepStatisticController->getFailedStatistics($automation, $query);
|
||||
try {
|
||||
$completedData = $this->stepStatisticController->getCompletedStatistics($automation, $query);
|
||||
} catch (\Throwable $e) {
|
||||
return new Response([$e->getMessage()], 500);
|
||||
}
|
||||
$stepData = [
|
||||
'total' => $shortStatistics->getEntered(),
|
||||
];
|
||||
if ($waitingData) {
|
||||
$stepData['waiting'] = $waitingData;
|
||||
}
|
||||
if ($failedData) {
|
||||
$stepData['failed'] = $failedData;
|
||||
}
|
||||
if ($completedData) {
|
||||
$stepData['completed'] = $completedData;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'automation' => $this->automationMapper->buildAutomation($automation, $shortStatistics),
|
||||
'step_data' => $stepData,
|
||||
'tree_is_inconsistent' => !$this->isTreeConsistent(...$automations),
|
||||
];
|
||||
return new Response($data);
|
||||
}
|
||||
|
||||
private function isTreeConsistent(Automation ...$automations): bool {
|
||||
if (count($automations) === 1) {
|
||||
return true;
|
||||
}
|
||||
$stepIds = array_map(function (Automation $automation) {
|
||||
return array_keys($automation->getSteps());
|
||||
}, $automations);
|
||||
$compareTo = array_shift($stepIds);
|
||||
if (!$compareTo) {
|
||||
return true;
|
||||
}
|
||||
foreach ($stepIds as $stepId) {
|
||||
if (count(array_diff($stepId, $compareTo)) !== 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function getRequestSchema(): array {
|
||||
return [
|
||||
'id' => Builder::integer()->required(),
|
||||
'query' => Query::getRequestSchema(),
|
||||
];
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Analytics\Endpoints;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\REST\Request;
|
||||
use MailPoet\API\REST\Response;
|
||||
use MailPoet\Automation\Engine\API\Endpoint;
|
||||
use MailPoet\Automation\Engine\Exceptions;
|
||||
use MailPoet\Automation\Engine\Storage\AutomationStorage;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Analytics\Controller\OverviewStatisticsController;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Analytics\Entities\QueryWithCompare;
|
||||
use MailPoet\Validator\Builder;
|
||||
|
||||
class OverviewEndpoint extends Endpoint {
|
||||
|
||||
/** @var AutomationStorage */
|
||||
private $automationStorage;
|
||||
|
||||
/** @var OverviewStatisticsController */
|
||||
private $overviewStatisticsController;
|
||||
|
||||
public function __construct(
|
||||
AutomationStorage $automationStorage,
|
||||
OverviewStatisticsController $overviewStatisticsController
|
||||
) {
|
||||
$this->automationStorage = $automationStorage;
|
||||
$this->overviewStatisticsController = $overviewStatisticsController;
|
||||
}
|
||||
|
||||
public function handle(Request $request): Response {
|
||||
$id = absint(is_numeric($request->getParam('id')) ? $request->getParam('id') : 0);
|
||||
$automation = $this->automationStorage->getAutomation($id);
|
||||
if (!$automation) {
|
||||
throw Exceptions::automationNotFound($id);
|
||||
}
|
||||
$query = QueryWithCompare::fromRequest($request);
|
||||
|
||||
$result = $this->overviewStatisticsController->getStatisticsForAutomation($automation, $query);
|
||||
return new Response($result);
|
||||
}
|
||||
|
||||
public static function getRequestSchema(): array {
|
||||
return [
|
||||
'id' => Builder::integer()->required(),
|
||||
'query' => QueryWithCompare::getRequestSchema(),
|
||||
];
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Analytics\Entities;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\REST\Request;
|
||||
use MailPoet\Automation\Engine\Exceptions\UnexpectedValueException;
|
||||
use MailPoet\Validator\Builder;
|
||||
use MailPoet\Validator\Schema;
|
||||
|
||||
class Query {
|
||||
|
||||
/** @var \DateTimeImmutable */
|
||||
private $primaryAfter;
|
||||
|
||||
/** @var \DateTimeImmutable */
|
||||
private $primaryBefore;
|
||||
|
||||
/** @var int */
|
||||
private $limit;
|
||||
|
||||
/** @var string */
|
||||
private $orderBy;
|
||||
|
||||
/** @var string */
|
||||
private $orderDirection;
|
||||
|
||||
/** @var int */
|
||||
private $page;
|
||||
|
||||
/** @var array */
|
||||
private $filter;
|
||||
|
||||
/** @var string | null */
|
||||
private $search;
|
||||
|
||||
public function __construct(
|
||||
\DateTimeImmutable $primaryAfter,
|
||||
\DateTimeImmutable $primaryBefore,
|
||||
int $limit = 25,
|
||||
string $orderBy = '',
|
||||
string $orderDirection = 'asc',
|
||||
int $page = 1,
|
||||
array $filter = [],
|
||||
string $search = null
|
||||
) {
|
||||
$this->primaryAfter = $primaryAfter;
|
||||
$this->primaryBefore = $primaryBefore;
|
||||
$this->limit = $limit;
|
||||
$this->orderBy = $orderBy;
|
||||
$this->orderDirection = $orderDirection;
|
||||
$this->page = $page;
|
||||
$this->filter = $filter;
|
||||
$this->search = $search;
|
||||
}
|
||||
|
||||
public function getAfter(): \DateTimeImmutable {
|
||||
return $this->primaryAfter;
|
||||
}
|
||||
|
||||
public function getBefore(): \DateTimeImmutable {
|
||||
return $this->primaryBefore;
|
||||
}
|
||||
|
||||
public function getLimit(): int {
|
||||
return $this->limit;
|
||||
}
|
||||
|
||||
public function getOrderBy(): string {
|
||||
return $this->orderBy;
|
||||
}
|
||||
|
||||
public function getOrderDirection(): string {
|
||||
return $this->orderDirection;
|
||||
}
|
||||
|
||||
public function getPage(): int {
|
||||
return $this->page;
|
||||
}
|
||||
|
||||
public function getFilter(): array {
|
||||
return $this->filter;
|
||||
}
|
||||
|
||||
public function getSearch(): ?string {
|
||||
return $this->search;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return Query
|
||||
* @throws UnexpectedValueException
|
||||
*/
|
||||
public static function fromRequest(Request $request) {
|
||||
$query = $request->getParam('query');
|
||||
if (!is_array($query)) {
|
||||
throw new UnexpectedValueException('Invalid query parameters');
|
||||
}
|
||||
$primary = $query['primary'] ?? null;
|
||||
if (!is_array($primary)) {
|
||||
throw new UnexpectedValueException('Invalid query parameters');
|
||||
}
|
||||
$primaryAfter = $primary['after'] ?? null;
|
||||
$primaryBefore = $primary['before'] ?? null;
|
||||
if (
|
||||
!is_string($primaryAfter) ||
|
||||
!is_string($primaryBefore)
|
||||
) {
|
||||
throw new UnexpectedValueException('Invalid query parameters');
|
||||
}
|
||||
|
||||
$limit = $query['limit'] ?? 25;
|
||||
$orderBy = $query['order_by'] ?? '';
|
||||
$orderDirection = isset($query['order']) && strtolower($query['order']) === 'asc' ? 'asc' : 'desc';
|
||||
$page = $query['page'] ?? 1;
|
||||
$filter = $query['filter'] ?? [];
|
||||
$search = $query['search'] ?? null;
|
||||
|
||||
return new self(
|
||||
new \DateTimeImmutable($primaryAfter),
|
||||
new \DateTimeImmutable($primaryBefore),
|
||||
$limit,
|
||||
$orderBy,
|
||||
$orderDirection,
|
||||
$page,
|
||||
$filter,
|
||||
$search
|
||||
);
|
||||
}
|
||||
|
||||
public static function getRequestSchema(): Schema {
|
||||
return Builder::object(
|
||||
[
|
||||
'primary' => Builder::object(
|
||||
[
|
||||
'after' => Builder::string()->formatDateTime()->required(),
|
||||
'before' => Builder::string()->formatDateTime()->required(),
|
||||
]
|
||||
),
|
||||
'limit' => Builder::integer()->minimum(1)->maximum(100),
|
||||
'order_by' => Builder::string(),
|
||||
'order' => Builder::string(),
|
||||
'page' => Builder::integer()->minimum(1),
|
||||
'filter' => Builder::object([]),
|
||||
'search' => Builder::string()->nullable(),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Analytics\Entities;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\API\REST\Request;
|
||||
use MailPoet\Automation\Engine\Exceptions\UnexpectedValueException;
|
||||
use MailPoet\Validator\Builder;
|
||||
use MailPoet\Validator\Schema;
|
||||
|
||||
class QueryWithCompare extends Query {
|
||||
|
||||
/** @var \DateTimeImmutable */
|
||||
private $secondaryAfter;
|
||||
|
||||
/** @var \DateTimeImmutable */
|
||||
private $secondaryBefore;
|
||||
|
||||
public function __construct(
|
||||
\DateTimeImmutable $primaryAfter,
|
||||
\DateTimeImmutable $primaryBefore,
|
||||
\DateTimeImmutable $secondaryAfter,
|
||||
\DateTimeImmutable $secondaryBefore,
|
||||
int $limit = 25,
|
||||
string $orderBy = '',
|
||||
string $orderDirection = 'asc',
|
||||
int $page = 0,
|
||||
array $filter = [],
|
||||
string $search = null
|
||||
) {
|
||||
parent::__construct($primaryAfter, $primaryBefore, $limit, $orderBy, $orderDirection, $page, $filter, $search);
|
||||
$this->secondaryAfter = $secondaryAfter;
|
||||
$this->secondaryBefore = $secondaryBefore;
|
||||
}
|
||||
|
||||
public function getCompareWithAfter(): \DateTimeImmutable {
|
||||
return $this->secondaryAfter;
|
||||
}
|
||||
|
||||
public function getCompareWithBefore(): \DateTimeImmutable {
|
||||
return $this->secondaryBefore;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return QueryWithCompare
|
||||
* @throws UnexpectedValueException
|
||||
*/
|
||||
public static function fromRequest(Request $request) {
|
||||
|
||||
$query = $request->getParam('query');
|
||||
if (!is_array($query)) {
|
||||
throw new UnexpectedValueException('Invalid query parameters');
|
||||
}
|
||||
$primary = $query['primary'] ?? null;
|
||||
$secondary = $query['secondary'] ?? null;
|
||||
if (!is_array($primary) || !is_array($secondary)) {
|
||||
throw new UnexpectedValueException('Invalid query parameters');
|
||||
}
|
||||
$primaryAfter = $primary['after'] ?? null;
|
||||
$primaryBefore = $primary['before'] ?? null;
|
||||
$secondaryAfter = $secondary['after'] ?? null;
|
||||
$secondaryBefore = $secondary['before'] ?? null;
|
||||
if (
|
||||
!is_string($primaryAfter) ||
|
||||
!is_string($primaryBefore) ||
|
||||
!is_string($secondaryAfter) ||
|
||||
!is_string($secondaryBefore)
|
||||
) {
|
||||
throw new UnexpectedValueException('Invalid query parameters');
|
||||
}
|
||||
|
||||
$limit = $query['limit'] ?? 25;
|
||||
$orderBy = $query['orderBy'] ?? '';
|
||||
$orderDirection = $query['orderDirection'] ?? 'asc';
|
||||
$page = $query['page'] ?? 0;
|
||||
|
||||
return new self(
|
||||
new \DateTimeImmutable($primaryAfter),
|
||||
new \DateTimeImmutable($primaryBefore),
|
||||
new \DateTimeImmutable($secondaryAfter),
|
||||
new \DateTimeImmutable($secondaryBefore),
|
||||
$limit,
|
||||
$orderBy,
|
||||
$orderDirection,
|
||||
$page
|
||||
);
|
||||
}
|
||||
|
||||
public static function getRequestSchema(): Schema {
|
||||
return Builder::object(
|
||||
[
|
||||
'primary' => Builder::object(
|
||||
[
|
||||
'after' => Builder::string()->formatDateTime()->required(),
|
||||
'before' => Builder::string()->formatDateTime()->required(),
|
||||
]
|
||||
),
|
||||
'secondary' => Builder::object(
|
||||
[
|
||||
'after' => Builder::string()->formatDateTime()->required(),
|
||||
'before' => Builder::string()->formatDateTime()->required(),
|
||||
]
|
||||
),
|
||||
'limit' => Builder::integer()->minimum(1)->maximum(100),
|
||||
'orderBy' => Builder::string(),
|
||||
'orderDirection' => Builder::string(),
|
||||
'page' => Builder::integer()->minimum(1),
|
||||
'filter' => Builder::object(),
|
||||
'search' => Builder::string()->nullable(),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Config\ServicesChecker;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
use MailPoet\Services\AuthorizedEmailsController;
|
||||
use MailPoet\Services\AuthorizedSenderDomainController;
|
||||
use MailPoet\Services\Bridge;
|
||||
|
||||
class ContextFactory {
|
||||
/** @var SegmentsRepository */
|
||||
private $segmentsRepository;
|
||||
|
||||
/** @var Bridge */
|
||||
private $bridge;
|
||||
|
||||
/** @var ServicesChecker */
|
||||
private $servicesChecker;
|
||||
|
||||
/** @var AuthorizedSenderDomainController */
|
||||
private $authorizedSenderDomainController;
|
||||
|
||||
/** @var AuthorizedEmailsController */
|
||||
private $authorizedEmailsController;
|
||||
|
||||
public function __construct(
|
||||
SegmentsRepository $segmentsRepository,
|
||||
Bridge $bridge,
|
||||
ServicesChecker $servicesChecker,
|
||||
AuthorizedSenderDomainController $authorizedSenderDomainController,
|
||||
AuthorizedEmailsController $authorizedEmailsController
|
||||
) {
|
||||
$this->segmentsRepository = $segmentsRepository;
|
||||
$this->servicesChecker = $servicesChecker;
|
||||
$this->bridge = $bridge;
|
||||
$this->authorizedSenderDomainController = $authorizedSenderDomainController;
|
||||
$this->authorizedEmailsController = $authorizedEmailsController;
|
||||
}
|
||||
|
||||
/** @return mixed[] */
|
||||
public function getContextData(): array {
|
||||
$data = [
|
||||
'segments' => $this->getSegments(),
|
||||
'userRoles' => $this->getUserRoles(),
|
||||
];
|
||||
|
||||
if ($this->isMSSEnabled()) {
|
||||
$data['senderDomainsConfig'] = $this->getSenderDomainsConfig();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function getSenderDomainsConfig(): array {
|
||||
$senderDomainsConfig = $this->authorizedSenderDomainController->getContextDataForAutomations();
|
||||
$senderDomainsConfig['authorizedEmails'] = $this->authorizedEmailsController->getAuthorizedEmailAddresses();
|
||||
return $senderDomainsConfig;
|
||||
}
|
||||
|
||||
private function isMSSEnabled(): bool {
|
||||
$mpApiKeyValid = $this->servicesChecker->isMailPoetAPIKeyValid(false, true);
|
||||
return $mpApiKeyValid && $this->bridge->isMailpoetSendingServiceEnabled();
|
||||
}
|
||||
|
||||
private function getSegments(): array {
|
||||
$segments = [];
|
||||
foreach ($this->segmentsRepository->findAll() as $segment) {
|
||||
$segments[] = [
|
||||
'id' => $segment->getId(),
|
||||
'name' => $segment->getName(),
|
||||
'type' => $segment->getType(),
|
||||
];
|
||||
}
|
||||
return $segments;
|
||||
}
|
||||
|
||||
private function getUserRoles(): array {
|
||||
$userRoles = [];
|
||||
foreach (wp_roles()->roles as $role => $details) {
|
||||
$userRoles[] = [
|
||||
'id' => $role,
|
||||
'name' => $details['name'],
|
||||
];
|
||||
}
|
||||
return $userRoles;
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Fields;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Data\Field;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Payloads\NewsletterLinkPayload;
|
||||
|
||||
class NewsletterLinkFieldsFactory {
|
||||
public function getFields(): array {
|
||||
return [
|
||||
new Field(
|
||||
'mailpoet:email-link:url',
|
||||
Field::TYPE_STRING,
|
||||
__('Link URL', 'mailpoet'),
|
||||
function(NewsletterLinkPayload $payload) {
|
||||
return $payload->getLink()->getUrl();
|
||||
}
|
||||
),
|
||||
new Field(
|
||||
'mailpoet:email-link:created',
|
||||
Field::TYPE_DATETIME,
|
||||
__('Created', 'mailpoet'),
|
||||
function(NewsletterLinkPayload $payload) {
|
||||
return $payload->getLink()->getCreatedAt();
|
||||
}
|
||||
),
|
||||
new Field(
|
||||
'mailpoet:email-link:id',
|
||||
Field::TYPE_INTEGER,
|
||||
__('Link ID', 'mailpoet'),
|
||||
function(NewsletterLinkPayload $payload) {
|
||||
return $payload->getLink()->getId();
|
||||
}
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Fields;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Data\Automation;
|
||||
use MailPoet\Automation\Engine\Data\AutomationRun;
|
||||
use MailPoet\Automation\Engine\Data\Field;
|
||||
use MailPoet\Automation\Engine\Data\Subject;
|
||||
use MailPoet\Automation\Engine\Storage\AutomationStorage;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Payloads\SubscriberPayload;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Subjects\SubscriberSubject;
|
||||
|
||||
class SubscriberAutomationFieldsFactory {
|
||||
/** @var AutomationStorage */
|
||||
private $automationStorage;
|
||||
|
||||
public function __construct(
|
||||
AutomationStorage $automationStorage
|
||||
) {
|
||||
$this->automationStorage = $automationStorage;
|
||||
}
|
||||
|
||||
/** @return Field[] */
|
||||
public function getFields(): array {
|
||||
$automations = $this->automationStorage->getAutomations(
|
||||
array_diff(Automation::STATUS_ALL, [Automation::STATUS_TRASH])
|
||||
);
|
||||
$args = [
|
||||
'options' => array_map(function (Automation $automation) {
|
||||
return [
|
||||
'id' => $automation->getId(),
|
||||
'name' => $automation->getName() . " (#{$automation->getId()})",
|
||||
];
|
||||
}, $automations),
|
||||
'params' => ['in_the_last'],
|
||||
];
|
||||
|
||||
return [
|
||||
new Field(
|
||||
'mailpoet:subscriber:automations-entered',
|
||||
Field::TYPE_ENUM_ARRAY,
|
||||
__('Automations — entered', 'mailpoet'),
|
||||
function (SubscriberPayload $payload, array $params = []) {
|
||||
return $this->getAutomationIds($payload, null, $params);
|
||||
},
|
||||
$args
|
||||
),
|
||||
new Field(
|
||||
'mailpoet:subscriber:automations-processing',
|
||||
Field::TYPE_ENUM_ARRAY,
|
||||
__('Automations — processing', 'mailpoet'),
|
||||
function (SubscriberPayload $payload, array $params = []) {
|
||||
return $this->getAutomationIds($payload, [AutomationRun::STATUS_RUNNING], $params);
|
||||
},
|
||||
$args
|
||||
),
|
||||
new Field(
|
||||
'mailpoet:subscriber:automations-exited',
|
||||
Field::TYPE_ENUM_ARRAY,
|
||||
__('Automations — exited', 'mailpoet'),
|
||||
function (SubscriberPayload $payload, array $params = []) {
|
||||
return $this->getAutomationIds($payload, [AutomationRun::STATUS_COMPLETE], $params);
|
||||
},
|
||||
$args
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
private function getAutomationIds(SubscriberPayload $payload, array $status = null, array $params = []): array {
|
||||
$inTheLastSeconds = isset($params['in_the_last']) ? (int)$params['in_the_last'] : null;
|
||||
$subject = new Subject(SubscriberSubject::KEY, ['subscriber_id' => $payload->getId()]);
|
||||
return $this->automationStorage->getAutomationIdsBySubject($subject, $status, $inTheLastSeconds);
|
||||
}
|
||||
}
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Fields;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use DateTimeImmutable;
|
||||
use MailPoet\Automation\Engine\Data\Field;
|
||||
use MailPoet\Automation\Engine\Exceptions\InvalidStateException;
|
||||
use MailPoet\Automation\Engine\WordPress;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Payloads\SubscriberPayload;
|
||||
use MailPoet\CustomFields\CustomFieldsRepository;
|
||||
use MailPoet\Entities\CustomFieldEntity;
|
||||
use MailPoet\Entities\SubscriberCustomFieldEntity;
|
||||
use MailPoet\Util\DateConverter;
|
||||
|
||||
class SubscriberCustomFieldsFactory {
|
||||
/** @var CustomFieldsRepository */
|
||||
private $customFieldsRepository;
|
||||
|
||||
/** @var WordPress */
|
||||
private $wordPress;
|
||||
|
||||
public function __construct(
|
||||
CustomFieldsRepository $customFieldsRepository,
|
||||
WordPress $wordPress
|
||||
) {
|
||||
$this->customFieldsRepository = $customFieldsRepository;
|
||||
$this->wordPress = $wordPress;
|
||||
}
|
||||
|
||||
/** @return Field[] */
|
||||
public function getFields(): array {
|
||||
return array_map(function (CustomFieldEntity $customField) {
|
||||
return $this->getField($customField);
|
||||
}, $this->customFieldsRepository->findAll());
|
||||
}
|
||||
|
||||
private function getField(CustomFieldEntity $customField): Field {
|
||||
switch ($customField->getType()) {
|
||||
case CustomFieldEntity::TYPE_TEXT:
|
||||
case CustomFieldEntity::TYPE_TEXTAREA:
|
||||
$validate = $customField->getParams()['validate'] ?? null;
|
||||
return $validate === 'number'
|
||||
? $this->createNumberField($customField)
|
||||
: $this->createStringField($customField);
|
||||
case CustomFieldEntity::TYPE_CHECKBOX:
|
||||
return $this->createBooleanField($customField);
|
||||
case CustomFieldEntity::TYPE_RADIO:
|
||||
case CustomFieldEntity::TYPE_SELECT:
|
||||
return $this->createEnumField($customField);
|
||||
case CustomFieldEntity::TYPE_DATE:
|
||||
$type = $customField->getParams()['date_type'] ?? null;
|
||||
if ($type === 'year_month_day' || $type === 'year_month') {
|
||||
return $this->createDateTimeField($customField);
|
||||
} elseif ($type === 'year') {
|
||||
return $this->createYearField($customField);
|
||||
} elseif ($type === 'month') {
|
||||
return $this->createMonthField($customField);
|
||||
} elseif ($type === 'day') {
|
||||
return $this->createDayField($customField);
|
||||
} else {
|
||||
throw new InvalidStateException(sprintf('Unknown date type "%s"', $type));
|
||||
}
|
||||
default:
|
||||
throw new InvalidStateException(sprintf('Unknown custom field type "%s"', $customField->getType()));
|
||||
}
|
||||
}
|
||||
|
||||
private function createStringField(CustomFieldEntity $customField): Field {
|
||||
$factory = function (SubscriberPayload $payload) use ($customField) {
|
||||
return $this->getCustomFieldValue($payload, $customField);
|
||||
};
|
||||
return $this->createField($customField, Field::TYPE_STRING, $factory);
|
||||
}
|
||||
|
||||
private function createNumberField(CustomFieldEntity $customField): Field {
|
||||
$factory = function (SubscriberPayload $payload) use ($customField) {
|
||||
$value = $this->getCustomFieldValue($payload, $customField);
|
||||
return is_numeric($value) ? (float)$value : null;
|
||||
};
|
||||
return $this->createField($customField, Field::TYPE_NUMBER, $factory);
|
||||
}
|
||||
|
||||
private function createBooleanField(CustomFieldEntity $customField): Field {
|
||||
$factory = function (SubscriberPayload $payload) use ($customField) {
|
||||
$value = $this->getCustomFieldValue($payload, $customField);
|
||||
return $value === null ? null : (bool)$value;
|
||||
};
|
||||
return $this->createField($customField, Field::TYPE_BOOLEAN, $factory);
|
||||
}
|
||||
|
||||
private function createEnumField(CustomFieldEntity $customField): Field {
|
||||
$factory = function (SubscriberPayload $payload) use ($customField) {
|
||||
$value = $this->getCustomFieldValue($payload, $customField);
|
||||
return $value === null ? null : $value;
|
||||
};
|
||||
return $this->createField($customField, Field::TYPE_ENUM, $factory, [
|
||||
'options' => array_map(function (array $value) {
|
||||
return ['id' => $value['value'], 'name' => $value['value']];
|
||||
}, $customField->getParams()['values'] ?? []),
|
||||
]);
|
||||
}
|
||||
|
||||
private function createDateTimeField(CustomFieldEntity $customField): Field {
|
||||
$factory = function (SubscriberPayload $payload) use ($customField) {
|
||||
return $this->getDateTimeValue($customField, $this->getCustomFieldValue($payload, $customField) ?? '');
|
||||
};
|
||||
return $this->createField($customField, Field::TYPE_DATETIME, $factory);
|
||||
}
|
||||
|
||||
private function createYearField(CustomFieldEntity $customField): Field {
|
||||
$factory = function (SubscriberPayload $payload) use ($customField) {
|
||||
$value = $this->getDateTimeValue($customField, $this->getCustomFieldValue($payload, $customField) ?? '');
|
||||
return $value ? (int)$value->format('Y') : null;
|
||||
};
|
||||
return $this->createField($customField, Field::TYPE_INTEGER, $factory);
|
||||
}
|
||||
|
||||
private function createMonthField(CustomFieldEntity $customField): Field {
|
||||
$factory = function (SubscriberPayload $payload) use ($customField) {
|
||||
$value = $this->getDateTimeValue($customField, $this->getCustomFieldValue($payload, $customField) ?? '');
|
||||
return $value ? (int)$value->format('n') : null;
|
||||
};
|
||||
return $this->createField($customField, Field::TYPE_ENUM, $factory, [
|
||||
'options' => array_map(function (int $value) {
|
||||
return ['id' => $value, 'name' => $this->wordPress->getWpLocale()->get_month($value)];
|
||||
}, range(1, 12)),
|
||||
]);
|
||||
}
|
||||
|
||||
private function createDayField(CustomFieldEntity $customField): Field {
|
||||
$factory = function (SubscriberPayload $payload) use ($customField) {
|
||||
$value = $this->getDateTimeValue($customField, $this->getCustomFieldValue($payload, $customField) ?? '');
|
||||
return $value ? (int)$value->format('j') : null;
|
||||
};
|
||||
return $this->createField($customField, Field::TYPE_ENUM, $factory, [
|
||||
'options' => array_map(function (int $value) {
|
||||
return ['id' => $value, 'name' => "$value"];
|
||||
}, range(1, 31)),
|
||||
]);
|
||||
}
|
||||
|
||||
private function getCustomFieldValue(SubscriberPayload $payload, CustomFieldEntity $customField): ?string {
|
||||
$subscriberCustomField = $payload->getSubscriber()->getSubscriberCustomFields()->filter(
|
||||
function (SubscriberCustomFieldEntity $subscriberCustomField = null) use ($customField) {
|
||||
return $subscriberCustomField && $subscriberCustomField->getCustomField() === $customField;
|
||||
}
|
||||
)->first() ?: null;
|
||||
return $subscriberCustomField ? $subscriberCustomField->getValue() : null;
|
||||
}
|
||||
|
||||
private function createField(CustomFieldEntity $customField, string $type, callable $factory, array $args = []): Field {
|
||||
$key = 'mailpoet:subscriber:custom-field:' . $customField->getName();
|
||||
$name = sprintf(
|
||||
// translators: %s is the name of the custom field
|
||||
__('Custom field: %s', 'mailpoet'),
|
||||
$customField->getParams()['label'] ?? $customField->getName()
|
||||
);
|
||||
return new Field($key, $type, $name, $factory, $args);
|
||||
}
|
||||
|
||||
private function getDateTimeValue(CustomFieldEntity $customField, ?string $value): ?DateTimeImmutable {
|
||||
$dateFormat = $customField->getParams()['date_format'] ?? null;
|
||||
if (!$dateFormat || !$value) {
|
||||
return null;
|
||||
}
|
||||
$dateString = (new DateConverter())->convertDateToDatetime($value, $dateFormat) ?: null;
|
||||
return $dateString ? new DateTimeImmutable($dateString, $this->wordPress->wpTimezone()) : null;
|
||||
}
|
||||
}
|
||||
+259
@@ -0,0 +1,259 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Fields;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Data\Field;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Payloads\SubscriberPayload;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Segments\SegmentsFinder;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
use MailPoet\Tags\TagRepository;
|
||||
|
||||
class SubscriberFieldsFactory {
|
||||
/** @var SegmentsFinder */
|
||||
private $segmentsFinder;
|
||||
|
||||
/** @var SegmentsRepository */
|
||||
private $segmentsRepository;
|
||||
|
||||
/** @var SubscriberAutomationFieldsFactory */
|
||||
private $automationFieldsFactory;
|
||||
|
||||
/** @var SubscriberCustomFieldsFactory */
|
||||
private $customFieldsFactory;
|
||||
|
||||
/** @var TagRepository */
|
||||
private $tagRepository;
|
||||
|
||||
/** @var SubscriberStatisticFieldsFactory */
|
||||
private $statisticFieldsFactory;
|
||||
|
||||
public function __construct(
|
||||
SegmentsFinder $segmentsFinder,
|
||||
SegmentsRepository $segmentsRepository,
|
||||
SubscriberAutomationFieldsFactory $automationFieldsFactory,
|
||||
SubscriberCustomFieldsFactory $customFieldsFactory,
|
||||
SubscriberStatisticFieldsFactory $statisticFieldsFactory,
|
||||
TagRepository $tagRepository
|
||||
) {
|
||||
$this->segmentsFinder = $segmentsFinder;
|
||||
$this->segmentsRepository = $segmentsRepository;
|
||||
$this->automationFieldsFactory = $automationFieldsFactory;
|
||||
$this->customFieldsFactory = $customFieldsFactory;
|
||||
$this->statisticFieldsFactory = $statisticFieldsFactory;
|
||||
$this->tagRepository = $tagRepository;
|
||||
}
|
||||
|
||||
/** @return Field[] */
|
||||
public function getFields(): array {
|
||||
return array_merge(
|
||||
$this->customFieldsFactory->getFields(),
|
||||
[
|
||||
new Field(
|
||||
'mailpoet:subscriber:email',
|
||||
Field::TYPE_STRING,
|
||||
__('Email address', 'mailpoet'),
|
||||
function (SubscriberPayload $payload) {
|
||||
return $payload->getEmail();
|
||||
}
|
||||
),
|
||||
new Field(
|
||||
'mailpoet:subscriber:engagement-score',
|
||||
Field::TYPE_NUMBER,
|
||||
__('Engagement score', 'mailpoet'),
|
||||
function (SubscriberPayload $payload) {
|
||||
return $payload->getSubscriber()->getEngagementScore();
|
||||
}
|
||||
),
|
||||
new Field(
|
||||
'mailpoet:subscriber:first-name',
|
||||
Field::TYPE_STRING,
|
||||
__('First name', 'mailpoet'),
|
||||
function (SubscriberPayload $payload) {
|
||||
return $payload->getSubscriber()->getFirstName();
|
||||
}
|
||||
),
|
||||
new Field(
|
||||
'mailpoet:subscriber:last-name',
|
||||
Field::TYPE_STRING,
|
||||
__('Last name', 'mailpoet'),
|
||||
function (SubscriberPayload $payload) {
|
||||
return $payload->getSubscriber()->getLastName();
|
||||
}
|
||||
),
|
||||
new Field(
|
||||
'mailpoet:subscriber:is-globally-subscribed',
|
||||
Field::TYPE_BOOLEAN,
|
||||
__('Is globally subscribed', 'mailpoet'),
|
||||
function (SubscriberPayload $payload) {
|
||||
return $payload->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED;
|
||||
}
|
||||
),
|
||||
new Field(
|
||||
'mailpoet:subscriber:last-engagement-at',
|
||||
Field::TYPE_DATETIME,
|
||||
__('Last engaged', 'mailpoet'),
|
||||
function (SubscriberPayload $payload) {
|
||||
return $payload->getSubscriber()->getLastEngagementAt();
|
||||
}
|
||||
),
|
||||
new Field(
|
||||
'mailpoet:subscriber:status',
|
||||
Field::TYPE_ENUM,
|
||||
__('Status', 'mailpoet'),
|
||||
function (SubscriberPayload $payload) {
|
||||
return $payload->getStatus();
|
||||
},
|
||||
[
|
||||
'options' => [
|
||||
[
|
||||
'id' => SubscriberEntity::STATUS_SUBSCRIBED,
|
||||
'name' => __('Subscribed', 'mailpoet'),
|
||||
],
|
||||
[
|
||||
'id' => SubscriberEntity::STATUS_UNCONFIRMED,
|
||||
'name' => __('Unconfirmed', 'mailpoet'),
|
||||
],
|
||||
[
|
||||
'id' => SubscriberEntity::STATUS_UNSUBSCRIBED,
|
||||
'name' => __('Unsubscribed', 'mailpoet'),
|
||||
],
|
||||
[
|
||||
'id' => SubscriberEntity::STATUS_INACTIVE,
|
||||
'name' => __('Inactive', 'mailpoet'),
|
||||
],
|
||||
[
|
||||
'id' => SubscriberEntity::STATUS_BOUNCED,
|
||||
'name' => __('Bounced', 'mailpoet'),
|
||||
],
|
||||
],
|
||||
]
|
||||
),
|
||||
new Field(
|
||||
'mailpoet:subscriber:subscription-source',
|
||||
Field::TYPE_ENUM,
|
||||
__('Subscription source', 'mailpoet'),
|
||||
function (SubscriberPayload $payload) {
|
||||
return $payload->getSubscriber()->getSource();
|
||||
},
|
||||
[
|
||||
'options' => [
|
||||
[
|
||||
'id' => 'api',
|
||||
'name' => __('API', 'mailpoet'),
|
||||
],
|
||||
[
|
||||
'id' => 'form',
|
||||
'name' => __('Form', 'mailpoet'),
|
||||
],
|
||||
[
|
||||
'id' => 'unknown',
|
||||
'name' => __('Unknown', 'mailpoet'),
|
||||
],
|
||||
[
|
||||
'id' => 'imported',
|
||||
'name' => __('Imported', 'mailpoet'),
|
||||
],
|
||||
[
|
||||
'id' => 'administrator',
|
||||
'name' => __('Administrator', 'mailpoet'),
|
||||
],
|
||||
[
|
||||
'id' => 'wordpress_user',
|
||||
'name' => __('WordPress user', 'mailpoet'),
|
||||
],
|
||||
[
|
||||
'id' => 'woocommerce_user',
|
||||
'name' => __('WooCommerce user', 'mailpoet'),
|
||||
],
|
||||
[
|
||||
'id' => 'woocommerce_checkout',
|
||||
'name' => __('WooCommerce checkout', 'mailpoet'),
|
||||
],
|
||||
],
|
||||
]
|
||||
),
|
||||
new Field(
|
||||
'mailpoet:subscriber:last-subscribed-at',
|
||||
Field::TYPE_DATETIME,
|
||||
__('Subscribed date', 'mailpoet'),
|
||||
function (SubscriberPayload $payload) {
|
||||
return $payload->getSubscriber()->getLastSubscribedAt();
|
||||
}
|
||||
),
|
||||
new Field(
|
||||
'mailpoet:subscriber:lists',
|
||||
Field::TYPE_ENUM_ARRAY,
|
||||
__('Subscribed lists', 'mailpoet'),
|
||||
function (SubscriberPayload $payload) {
|
||||
$value = [];
|
||||
foreach ($payload->getSubscriber()->getSegments() as $list) {
|
||||
if ($list->getType() !== SegmentEntity::TYPE_DYNAMIC) {
|
||||
$value[] = $list->getId();
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
},
|
||||
[
|
||||
'options' => array_map(function ($segment) {
|
||||
return [
|
||||
'id' => $segment->getId(),
|
||||
'name' => $segment->getName(),
|
||||
];
|
||||
}, $this->segmentsRepository->findByTypeNotIn([SegmentEntity::TYPE_DYNAMIC])),
|
||||
]
|
||||
),
|
||||
new Field(
|
||||
'mailpoet:subscriber:tags',
|
||||
Field::TYPE_ENUM_ARRAY,
|
||||
__('Tags', 'mailpoet'),
|
||||
function (SubscriberPayload $payload) {
|
||||
$value = [];
|
||||
foreach ($payload->getSubscriber()->getSubscriberTags() as $subscriberTag) {
|
||||
$tag = $subscriberTag->getTag();
|
||||
if ($tag) {
|
||||
$value[] = $tag->getId();
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
},
|
||||
[
|
||||
'options' => array_map(function ($tag) {
|
||||
return [
|
||||
'id' => $tag->getId(),
|
||||
'name' => $tag->getName(),
|
||||
];
|
||||
}, $this->tagRepository->findAll()),
|
||||
]
|
||||
),
|
||||
new Field(
|
||||
'mailpoet:subscriber:segments',
|
||||
Field::TYPE_ENUM_ARRAY,
|
||||
__('Segments', 'mailpoet'),
|
||||
function (SubscriberPayload $payload) {
|
||||
$segments = $this->segmentsFinder->findDynamicSegments($payload->getSubscriber());
|
||||
$value = [];
|
||||
foreach ($segments as $segment) {
|
||||
$value[] = $segment->getId();
|
||||
}
|
||||
return $value;
|
||||
},
|
||||
[
|
||||
'options' => array_map(function ($segment) {
|
||||
return [
|
||||
'id' => $segment->getId(),
|
||||
'name' => $segment->getName(),
|
||||
];
|
||||
}, $this->segmentsRepository->findBy(['type' => SegmentEntity::TYPE_DYNAMIC])),
|
||||
]
|
||||
),
|
||||
],
|
||||
$this->statisticFieldsFactory->getFields(),
|
||||
$this->automationFieldsFactory->getFields()
|
||||
);
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Fields;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Data\Field;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Payloads\SubscriberPayload;
|
||||
use MailPoet\Subscribers\Statistics\SubscriberStatisticsRepository;
|
||||
use MailPoetVendor\Carbon\Carbon;
|
||||
|
||||
class SubscriberStatisticFieldsFactory {
|
||||
/** @var SubscriberStatisticsRepository */
|
||||
private $subscriberStatisticsRepository;
|
||||
|
||||
public function __construct(
|
||||
SubscriberStatisticsRepository $subscriberStatisticsRepository
|
||||
) {
|
||||
$this->subscriberStatisticsRepository = $subscriberStatisticsRepository;
|
||||
}
|
||||
|
||||
/** @return Field[] */
|
||||
public function getFields(): array {
|
||||
return [
|
||||
new Field(
|
||||
'mailpoet:subscriber:email-sent-count',
|
||||
Field::TYPE_INTEGER,
|
||||
__('Email — sent count', 'mailpoet'),
|
||||
function (SubscriberPayload $payload, array $params = []) {
|
||||
$startTime = $this->getStartTime($params);
|
||||
return $this->subscriberStatisticsRepository->getTotalSentCount($payload->getSubscriber(), $startTime);
|
||||
},
|
||||
[
|
||||
'params' => ['in_the_last'],
|
||||
]
|
||||
),
|
||||
new Field(
|
||||
'mailpoet:subscriber:email-opened-count',
|
||||
Field::TYPE_INTEGER,
|
||||
__('Email — opened count', 'mailpoet'),
|
||||
function (SubscriberPayload $payload, array $params = []) {
|
||||
$startTime = $this->getStartTime($params);
|
||||
return $this->subscriberStatisticsRepository->getStatisticsOpenCount($payload->getSubscriber(), $startTime);
|
||||
},
|
||||
[
|
||||
'params' => ['in_the_last'],
|
||||
]
|
||||
),
|
||||
new Field(
|
||||
'mailpoet:subscriber:email-machine-opened-count',
|
||||
Field::TYPE_INTEGER,
|
||||
__('Email — machine opened count', 'mailpoet'),
|
||||
function (SubscriberPayload $payload, array $params = []) {
|
||||
$startTime = $this->getStartTime($params);
|
||||
return $this->subscriberStatisticsRepository->getStatisticsMachineOpenCount($payload->getSubscriber(), $startTime);
|
||||
},
|
||||
[
|
||||
'params' => ['in_the_last'],
|
||||
]
|
||||
),
|
||||
new Field(
|
||||
'mailpoet:subscriber:email-clicked-count',
|
||||
Field::TYPE_INTEGER,
|
||||
__('Email — clicked count', 'mailpoet'),
|
||||
function (SubscriberPayload $payload, array $params = []) {
|
||||
$startTime = $this->getStartTime($params);
|
||||
return $this->subscriberStatisticsRepository->getStatisticsClickCount($payload->getSubscriber(), $startTime);
|
||||
},
|
||||
[
|
||||
'params' => ['in_the_last'],
|
||||
]
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
private function getStartTime(array $params): ?Carbon {
|
||||
$inTheLastSeconds = $params['in_the_last'] ?? null;
|
||||
return $inTheLastSeconds ? Carbon::now()->subSeconds((int)$inTheLastSeconds) : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Hooks;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Data\Automation;
|
||||
use MailPoet\Automation\Engine\Data\Step;
|
||||
use MailPoet\Automation\Engine\Hooks;
|
||||
use MailPoet\Automation\Engine\Storage\AutomationStorage;
|
||||
use MailPoet\Automation\Engine\WordPress;
|
||||
use MailPoet\Newsletter\NewsletterDeleteController;
|
||||
use MailPoet\Newsletter\NewslettersRepository;
|
||||
|
||||
class AutomationEditorLoadingHooks {
|
||||
|
||||
/** @var WordPress */
|
||||
private $wp;
|
||||
|
||||
/** @var AutomationStorage */
|
||||
private $automationStorage;
|
||||
|
||||
/** @var NewslettersRepository */
|
||||
private $newslettersRepository;
|
||||
|
||||
private NewsletterDeleteController $newsletterDeleteController;
|
||||
|
||||
public function __construct(
|
||||
WordPress $wp,
|
||||
AutomationStorage $automationStorage,
|
||||
NewslettersRepository $newslettersRepository,
|
||||
NewsletterDeleteController $newsletterDeleteController
|
||||
) {
|
||||
$this->wp = $wp;
|
||||
$this->automationStorage = $automationStorage;
|
||||
$this->newslettersRepository = $newslettersRepository;
|
||||
$this->newsletterDeleteController = $newsletterDeleteController;
|
||||
}
|
||||
|
||||
public function init(): void {
|
||||
$this->wp->addAction(Hooks::EDITOR_BEFORE_LOAD, [$this, 'beforeEditorLoad']);
|
||||
}
|
||||
|
||||
public function beforeEditorLoad(int $automationId): void {
|
||||
$automation = $this->automationStorage->getAutomation($automationId);
|
||||
if (!$automation) {
|
||||
return;
|
||||
}
|
||||
$this->disconnectEmptyEmailsFromSendEmailStep($automation);
|
||||
}
|
||||
|
||||
private function disconnectEmptyEmailsFromSendEmailStep(Automation $automation): void {
|
||||
$sendEmailSteps = array_filter(
|
||||
$automation->getSteps(),
|
||||
function(Step $step): bool {
|
||||
return $step->getKey() === 'mailpoet:send-email';
|
||||
}
|
||||
);
|
||||
foreach ($sendEmailSteps as $step) {
|
||||
$emailId = $step->getArgs()['email_id'] ?? 0;
|
||||
if (!$emailId) {
|
||||
continue;
|
||||
}
|
||||
$newsletterEntity = $this->newslettersRepository->findOneById($emailId);
|
||||
if ($newsletterEntity && $newsletterEntity->getBody() !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->newsletterDeleteController->bulkDelete([$emailId]);
|
||||
$args = $step->getArgs();
|
||||
unset($args['email_id']);
|
||||
$updatedStep = new Step(
|
||||
$step->getId(),
|
||||
$step->getType(),
|
||||
$step->getKey(),
|
||||
$args,
|
||||
$step->getNextSteps()
|
||||
);
|
||||
|
||||
$steps = array_merge(
|
||||
$automation->getSteps(),
|
||||
[$updatedStep->getId() => $updatedStep]
|
||||
);
|
||||
$automation->setSteps($steps);
|
||||
|
||||
//To be valid, an email would need to be associated to an active automation.
|
||||
if ($automation->getStatus() === Automation::STATUS_ACTIVE) {
|
||||
$automation->setStatus(Automation::STATUS_DRAFT);
|
||||
}
|
||||
$this->automationStorage->updateAutomation($automation);
|
||||
}
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Hooks;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Data\StepRunArgs;
|
||||
use MailPoet\Automation\Engine\Hooks;
|
||||
use MailPoet\Automation\Engine\Storage\AutomationRunStorage;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Subjects\SubscriberSubject;
|
||||
use MailPoet\Util\Security;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class CreateAutomationRunHook {
|
||||
private AutomationRunStorage $automationRunStorage;
|
||||
private WPFunctions $wp;
|
||||
|
||||
public function __construct(
|
||||
AutomationRunStorage $automationRunStorage,
|
||||
WPFunctions $wp
|
||||
) {
|
||||
$this->automationRunStorage = $automationRunStorage;
|
||||
$this->wp = $wp;
|
||||
}
|
||||
|
||||
public function init(): void {
|
||||
$this->wp->addAction(Hooks::AUTOMATION_RUN_CREATE, [$this, 'createAutomationRun'], 5, 2);
|
||||
}
|
||||
|
||||
public function createAutomationRun(bool $result, StepRunArgs $args): bool {
|
||||
if (!$result) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$automation = $args->getAutomation();
|
||||
$runOnlyOnce = $automation->getMeta('mailpoet:run-once-per-subscriber');
|
||||
if (!$runOnlyOnce) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$subscriberSubject = array_values($args->getAutomationRun()->getSubjects(SubscriberSubject::KEY))[0] ?? null;
|
||||
if (!$subscriberSubject) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Use locking mechanism to minimize the risk of race conditions.
|
||||
// WP transients don't provide atomic operations, so we can't guarantee
|
||||
// race-condition safety with a 100% certainty, but we can significantly
|
||||
// minimize the risk by generating and re-checking a unique lock value.
|
||||
$key = sprintf('mailpoet:run-once-per-subscriber:[%s][%s]', $automation->getId(), $subscriberSubject->getHash());
|
||||
|
||||
// 1. If lock already exists, do not create automation run.
|
||||
$value = $this->wp->getTransient($key);
|
||||
if ($value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. If lock does not exist, create it with a unique value.
|
||||
$value = Security::generateRandomString(16);
|
||||
$this->wp->setTransient($key, $value, MINUTE_IN_SECONDS);
|
||||
|
||||
// 3. If no automation run exist, ensure that the lock wasn't updated by another process.
|
||||
$count = $this->automationRunStorage->getCountByAutomationAndSubject($automation, $subscriberSubject);
|
||||
return $count === 0 && $this->wp->getTransient($key) === $value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Integration;
|
||||
use MailPoet\Automation\Engine\Registry;
|
||||
use MailPoet\Automation\Engine\WordPress;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Actions\SendEmailAction;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Analytics\Analytics;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Hooks\AutomationEditorLoadingHooks;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Hooks\CreateAutomationRunHook;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Subjects\NewsletterLinkSubject;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Subjects\SegmentSubject;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Subjects\SubscriberSubject;
|
||||
use MailPoet\Automation\Integrations\MailPoet\SubjectTransformers\CommentSubjectToSubscriberSubjectTransformer;
|
||||
use MailPoet\Automation\Integrations\MailPoet\SubjectTransformers\OrderSubjectToSegmentSubjectTransformer;
|
||||
use MailPoet\Automation\Integrations\MailPoet\SubjectTransformers\OrderSubjectToSubscriberSubjectTransformer;
|
||||
use MailPoet\Automation\Integrations\MailPoet\SubjectTransformers\SubscriberSubjectToWordPressUserSubjectTransformer;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Templates\TemplatesFactory;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Triggers\SomeoneSubscribesTrigger;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Triggers\UserRegistrationTrigger;
|
||||
|
||||
class MailPoetIntegration implements Integration {
|
||||
/** @var ContextFactory */
|
||||
private $contextFactory;
|
||||
|
||||
/** @var SegmentSubject */
|
||||
private $segmentSubject;
|
||||
|
||||
/** @var SubscriberSubject */
|
||||
private $subscriberSubject;
|
||||
|
||||
/** @var NewsletterLinkSubject */
|
||||
private $emailLinkSubject;
|
||||
|
||||
/** @var SomeoneSubscribesTrigger */
|
||||
private $someoneSubscribesTrigger;
|
||||
|
||||
/** @var UserRegistrationTrigger */
|
||||
private $userRegistrationTrigger;
|
||||
|
||||
/** @var SendEmailAction */
|
||||
private $sendEmailAction;
|
||||
|
||||
/** @var AutomationEditorLoadingHooks */
|
||||
private $automationEditorLoadingHooks;
|
||||
|
||||
/** @var CreateAutomationRunHook */
|
||||
private $createAutomationRunHook;
|
||||
|
||||
/** @var OrderSubjectToSubscriberSubjectTransformer */
|
||||
private $orderToSubscriberTransformer;
|
||||
|
||||
/** @var OrderSubjectToSegmentSubjectTransformer */
|
||||
private $orderToSegmentTransformer;
|
||||
|
||||
/** @var SubscriberSubjectToWordPressUserSubjectTransformer */
|
||||
private $subscriberToWordPressUserTransformer;
|
||||
|
||||
/** @var CommentSubjectToSubscriberSubjectTransformer */
|
||||
private $commentToSubscriberTransformer;
|
||||
|
||||
/** @var TemplatesFactory */
|
||||
private $templatesFactory;
|
||||
|
||||
/** @var Analytics */
|
||||
private $registerAnalytics;
|
||||
|
||||
/** @var WordPress */
|
||||
private $wordPress;
|
||||
|
||||
public function __construct(
|
||||
ContextFactory $contextFactory,
|
||||
SegmentSubject $segmentSubject,
|
||||
SubscriberSubject $subscriberSubject,
|
||||
NewsletterLinkSubject $emailLinkSubject,
|
||||
OrderSubjectToSubscriberSubjectTransformer $orderToSubscriberTransformer,
|
||||
OrderSubjectToSegmentSubjectTransformer $orderToSegmentTransformer,
|
||||
SubscriberSubjectToWordPressUserSubjectTransformer $subscriberToWordPressUserTransformer,
|
||||
CommentSubjectToSubscriberSubjectTransformer $commentToSubscriberTransformer,
|
||||
SomeoneSubscribesTrigger $someoneSubscribesTrigger,
|
||||
UserRegistrationTrigger $userRegistrationTrigger,
|
||||
SendEmailAction $sendEmailAction,
|
||||
AutomationEditorLoadingHooks $automationEditorLoadingHooks,
|
||||
CreateAutomationRunHook $createAutomationRunHook,
|
||||
TemplatesFactory $templatesFactory,
|
||||
Analytics $registerAnalytics,
|
||||
WordPress $wordPress
|
||||
) {
|
||||
$this->contextFactory = $contextFactory;
|
||||
$this->segmentSubject = $segmentSubject;
|
||||
$this->subscriberSubject = $subscriberSubject;
|
||||
$this->emailLinkSubject = $emailLinkSubject;
|
||||
$this->orderToSubscriberTransformer = $orderToSubscriberTransformer;
|
||||
$this->orderToSegmentTransformer = $orderToSegmentTransformer;
|
||||
$this->subscriberToWordPressUserTransformer = $subscriberToWordPressUserTransformer;
|
||||
$this->commentToSubscriberTransformer = $commentToSubscriberTransformer;
|
||||
$this->someoneSubscribesTrigger = $someoneSubscribesTrigger;
|
||||
$this->userRegistrationTrigger = $userRegistrationTrigger;
|
||||
$this->sendEmailAction = $sendEmailAction;
|
||||
$this->automationEditorLoadingHooks = $automationEditorLoadingHooks;
|
||||
$this->createAutomationRunHook = $createAutomationRunHook;
|
||||
$this->templatesFactory = $templatesFactory;
|
||||
$this->registerAnalytics = $registerAnalytics;
|
||||
$this->wordPress = $wordPress;
|
||||
}
|
||||
|
||||
public function register(Registry $registry): void {
|
||||
$registry->addContextFactory('mailpoet', function () {
|
||||
return $this->contextFactory->getContextData();
|
||||
});
|
||||
|
||||
$registry->addSubject($this->segmentSubject);
|
||||
$registry->addSubject($this->subscriberSubject);
|
||||
$registry->addSubject($this->emailLinkSubject);
|
||||
$registry->addTrigger($this->someoneSubscribesTrigger);
|
||||
$registry->addTrigger($this->userRegistrationTrigger);
|
||||
$registry->addAction($this->sendEmailAction);
|
||||
$registry->addSubjectTransformer($this->orderToSubscriberTransformer);
|
||||
$registry->addSubjectTransformer($this->orderToSegmentTransformer);
|
||||
$registry->addSubjectTransformer($this->subscriberToWordPressUserTransformer);
|
||||
$registry->addSubjectTransformer($this->commentToSubscriberTransformer);
|
||||
|
||||
foreach ($this->templatesFactory->createTemplates() as $template) {
|
||||
$registry->addTemplate($template);
|
||||
}
|
||||
|
||||
// sync step args (subject, preheader, etc.) to email settings
|
||||
$registry->onBeforeAutomationStepSave(
|
||||
[$this->sendEmailAction, 'saveEmailSettings'],
|
||||
$this->sendEmailAction->getKey()
|
||||
);
|
||||
|
||||
// execute send email step progress when email is sent
|
||||
$this->wordPress->addAction('mailpoet_automation_email_sent', [$this->sendEmailAction, 'handleEmailSent']);
|
||||
|
||||
$this->automationEditorLoadingHooks->init();
|
||||
$this->createAutomationRunHook->init();
|
||||
|
||||
$this->registerAnalytics->register();
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Payloads;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Integration\Payload;
|
||||
use MailPoet\Entities\NewsletterLinkEntity;
|
||||
|
||||
class NewsletterLinkPayload implements Payload {
|
||||
|
||||
|
||||
/** @var NewsletterLinkEntity */
|
||||
private $linkEntity;
|
||||
|
||||
public function __construct(
|
||||
NewsletterLinkEntity $linkEntity
|
||||
) {
|
||||
$this->linkEntity = $linkEntity;
|
||||
}
|
||||
|
||||
public function getId(): ?int {
|
||||
return $this->linkEntity->getId();
|
||||
}
|
||||
|
||||
public function getLink(): NewsletterLinkEntity {
|
||||
return $this->linkEntity;
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Payloads;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Integration\Payload;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\InvalidStateException;
|
||||
|
||||
class SegmentPayload implements Payload {
|
||||
/** @var SegmentEntity */
|
||||
private $segment;
|
||||
|
||||
public function __construct(
|
||||
SegmentEntity $segment
|
||||
) {
|
||||
$this->segment = $segment;
|
||||
}
|
||||
|
||||
public function getId(): int {
|
||||
$id = $this->segment->getId();
|
||||
if (!$id) {
|
||||
throw new InvalidStateException();
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->segment->getName();
|
||||
}
|
||||
|
||||
public function getType(): string {
|
||||
return $this->segment->getType();
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Payloads;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Integration\Payload;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\InvalidStateException;
|
||||
|
||||
class SubscriberPayload implements Payload {
|
||||
/** @var SubscriberEntity */
|
||||
private $subscriber;
|
||||
|
||||
public function __construct(
|
||||
SubscriberEntity $subscriber
|
||||
) {
|
||||
$this->subscriber = $subscriber;
|
||||
}
|
||||
|
||||
public function getId(): int {
|
||||
$id = $this->subscriber->getId();
|
||||
if (!$id) {
|
||||
throw new InvalidStateException();
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
public function getEmail(): string {
|
||||
return $this->subscriber->getEmail();
|
||||
}
|
||||
|
||||
public function getStatus(): string {
|
||||
return $this->subscriber->getStatus();
|
||||
}
|
||||
|
||||
public function isWpUser(): bool {
|
||||
return $this->subscriber->isWPUser();
|
||||
}
|
||||
|
||||
public function getWpUserId(): ?int {
|
||||
return $this->subscriber->getWpUserId();
|
||||
}
|
||||
|
||||
public function getSubscriber(): SubscriberEntity {
|
||||
return $this->subscriber;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\SubjectTransformers;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Data\Subject;
|
||||
use MailPoet\Automation\Engine\Integration\SubjectTransformer;
|
||||
use MailPoet\Automation\Engine\WordPress;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Subjects\SubscriberSubject;
|
||||
use MailPoet\Automation\Integrations\WordPress\Subjects\CommentSubject;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
|
||||
class CommentSubjectToSubscriberSubjectTransformer implements SubjectTransformer {
|
||||
|
||||
/** @var WordPress */
|
||||
private $wp;
|
||||
|
||||
/** @var SubscribersRepository */
|
||||
private $subscribersRepository;
|
||||
|
||||
public function __construct(
|
||||
WordPress $wp,
|
||||
SubscribersRepository $subscribersRepository
|
||||
) {
|
||||
$this->wp = $wp;
|
||||
$this->subscribersRepository = $subscribersRepository;
|
||||
}
|
||||
|
||||
public function transform(Subject $data): ?Subject {
|
||||
|
||||
if ($this->accepts() !== $data->getKey()) {
|
||||
throw new \InvalidArgumentException('Invalid subject type');
|
||||
}
|
||||
$commentId = (int)$data->getArgs()['comment_id'];
|
||||
$comment = $this->wp->getComment($commentId);
|
||||
if (!$comment instanceof \WP_Comment) {
|
||||
return null;
|
||||
}
|
||||
//phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
|
||||
$email = $comment->comment_author_email;
|
||||
if (!$this->wp->isEmail($email)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$subscriber = $this->subscribersRepository->findOneBy(['email' => $email]);
|
||||
if (!$subscriber) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Subject(
|
||||
SubscriberSubject::KEY,
|
||||
[
|
||||
'subscriber_id' => $subscriber->getId(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function returns(): string {
|
||||
return SubscriberSubject::KEY;
|
||||
}
|
||||
|
||||
public function accepts(): string {
|
||||
return CommentSubject::KEY;
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\SubjectTransformers;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Data\Subject;
|
||||
use MailPoet\Automation\Engine\Integration\SubjectTransformer;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Subjects\SegmentSubject;
|
||||
use MailPoet\Automation\Integrations\WooCommerce\Subjects\OrderSubject;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
|
||||
class OrderSubjectToSegmentSubjectTransformer implements SubjectTransformer {
|
||||
|
||||
/** @var SegmentsRepository */
|
||||
private $segmentRepository;
|
||||
|
||||
public function __construct(
|
||||
SegmentsRepository $segmentRepository
|
||||
) {
|
||||
$this->segmentRepository = $segmentRepository;
|
||||
}
|
||||
|
||||
public function accepts(): string {
|
||||
return OrderSubject::KEY;
|
||||
}
|
||||
|
||||
public function returns(): string {
|
||||
return SegmentSubject::KEY;
|
||||
}
|
||||
|
||||
public function transform(Subject $data): Subject {
|
||||
|
||||
if ($this->accepts() !== $data->getKey()) {
|
||||
throw new \InvalidArgumentException('Invalid subject type');
|
||||
}
|
||||
|
||||
$wooCommerceSegment = $this->segmentRepository->getWooCommerceSegment();
|
||||
return new Subject(SegmentSubject::KEY, ['segment_id' => $wooCommerceSegment->getId()]);
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\SubjectTransformers;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Data\Subject;
|
||||
use MailPoet\Automation\Engine\Integration\SubjectTransformer;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Subjects\SubscriberSubject;
|
||||
use MailPoet\Automation\Integrations\WooCommerce\Subjects\OrderSubject;
|
||||
use MailPoet\Automation\Integrations\WooCommerce\WooCommerce;
|
||||
use MailPoet\Entities\SubscriberEntity;
|
||||
use MailPoet\Segments;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
|
||||
class OrderSubjectToSubscriberSubjectTransformer implements SubjectTransformer {
|
||||
|
||||
/** @var SubscribersRepository */
|
||||
private $subscribersRepository;
|
||||
|
||||
/** @var Segments\WooCommerce */
|
||||
private $woocommerce;
|
||||
|
||||
/** @var WooCommerce */
|
||||
private $woocommerceHelper;
|
||||
|
||||
public function __construct(
|
||||
SubscribersRepository $subscribersRepository,
|
||||
Segments\WooCommerce $woocommerce,
|
||||
WooCommerce $woocommerceHelper
|
||||
) {
|
||||
$this->subscribersRepository = $subscribersRepository;
|
||||
$this->woocommerce = $woocommerce;
|
||||
$this->woocommerceHelper = $woocommerceHelper;
|
||||
}
|
||||
|
||||
public function transform(Subject $data): Subject {
|
||||
if ($this->accepts() !== $data->getKey()) {
|
||||
throw new \InvalidArgumentException('Invalid subject type');
|
||||
}
|
||||
|
||||
$subscriber = $this->findOrCreateSubscriber($data);
|
||||
if (!$subscriber instanceof SubscriberEntity) {
|
||||
throw new \InvalidArgumentException('Subscriber not found');
|
||||
}
|
||||
|
||||
return new Subject(SubscriberSubject::KEY, ['subscriber_id' => $subscriber->getId()]);
|
||||
}
|
||||
|
||||
public function accepts(): string {
|
||||
return OrderSubject::KEY;
|
||||
}
|
||||
|
||||
public function returns(): string {
|
||||
return SubscriberSubject::KEY;
|
||||
}
|
||||
|
||||
private function findOrCreateSubscriber(Subject $order): ?SubscriberEntity {
|
||||
$subscriber = $this->findSubscriber($order);
|
||||
if ($subscriber) {
|
||||
return $subscriber;
|
||||
}
|
||||
|
||||
$orderId = $order->getArgs()['order_id'] ?? null;
|
||||
if (!$orderId) {
|
||||
return null;
|
||||
}
|
||||
$this->woocommerce->synchronizeGuestCustomer($orderId);
|
||||
|
||||
return $this->findSubscriber($order);
|
||||
}
|
||||
|
||||
private function findSubscriber(Subject $order): ?SubscriberEntity {
|
||||
$orderId = $order->getArgs()['order_id'] ?? null;
|
||||
if (!$orderId) {
|
||||
return null;
|
||||
}
|
||||
$wcOrder = $this->woocommerceHelper->wcGetOrder($orderId);
|
||||
if (!$wcOrder instanceof \WC_Order) {
|
||||
return null;
|
||||
}
|
||||
$billingEmail = $wcOrder->get_billing_email();
|
||||
return $billingEmail ?
|
||||
$this->subscribersRepository->findOneBy(['email' => $billingEmail]) :
|
||||
$this->subscribersRepository->findOneBy(['wpUserId' => $wcOrder->get_user_id()]);
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\SubjectTransformers;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Data\Subject;
|
||||
use MailPoet\Automation\Engine\Integration\SubjectTransformer;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Subjects\SubscriberSubject;
|
||||
use MailPoet\Automation\Integrations\WordPress\Subjects\UserSubject;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
|
||||
class SubscriberSubjectToWordPressUserSubjectTransformer implements SubjectTransformer {
|
||||
/** @var SubscribersRepository */
|
||||
private $subscribersRepository;
|
||||
|
||||
public function __construct(
|
||||
SubscribersRepository $subscribersRepository
|
||||
) {
|
||||
$this->subscribersRepository = $subscribersRepository;
|
||||
}
|
||||
|
||||
public function accepts(): string {
|
||||
return SubscriberSubject::KEY;
|
||||
}
|
||||
|
||||
public function returns(): string {
|
||||
return UserSubject::KEY;
|
||||
}
|
||||
|
||||
public function transform(Subject $data): Subject {
|
||||
if ($this->accepts() !== $data->getKey()) {
|
||||
throw new \InvalidArgumentException('Invalid subject type');
|
||||
}
|
||||
|
||||
$subscriber = $this->subscribersRepository->findOneById((int)$data->getArgs()['subscriber_id']);
|
||||
if (!$subscriber) {
|
||||
throw new \InvalidArgumentException('Subscriber not found');
|
||||
}
|
||||
return new Subject(UserSubject::KEY, ['user_id' => $subscriber->getWpUserId()]);
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Subjects;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Data\Subject as SubjectData;
|
||||
use MailPoet\Automation\Engine\Integration\Payload;
|
||||
use MailPoet\Automation\Engine\Integration\Subject;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Fields\NewsletterLinkFieldsFactory;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Payloads\NewsletterLinkPayload;
|
||||
use MailPoet\Cron\Workers\StatsNotifications\NewsletterLinkRepository;
|
||||
use MailPoet\NotFoundException;
|
||||
use MailPoet\Validator\Builder;
|
||||
use MailPoet\Validator\Schema\ObjectSchema;
|
||||
|
||||
/**
|
||||
* @implements Subject<NewsletterLinkPayload>
|
||||
*/
|
||||
class NewsletterLinkSubject implements Subject {
|
||||
|
||||
|
||||
const KEY = 'mailpoet:email-link';
|
||||
|
||||
/** @var NewsletterLinkFieldsFactory */
|
||||
private $emailLinkFieldsFactory;
|
||||
|
||||
/** @var NewsletterLinkRepository */
|
||||
private $newsletterLinkRepository;
|
||||
|
||||
public function __construct(
|
||||
NewsletterLinkFieldsFactory $emailLinkFieldsFactory,
|
||||
NewsletterLinkRepository $newsletterLinkRepository
|
||||
) {
|
||||
$this->emailLinkFieldsFactory = $emailLinkFieldsFactory;
|
||||
$this->newsletterLinkRepository = $newsletterLinkRepository;
|
||||
}
|
||||
|
||||
public function getKey(): string {
|
||||
return self::KEY;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
// translators: automation subject (entity entering automation) title
|
||||
return __('Email link', 'mailpoet');
|
||||
}
|
||||
|
||||
public function getArgsSchema(): ObjectSchema {
|
||||
return Builder::object([
|
||||
'link_id' => Builder::integer()->minimum(1)->required(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function getFields(): array {
|
||||
return $this->emailLinkFieldsFactory->getFields();
|
||||
}
|
||||
|
||||
public function getPayload(SubjectData $subjectData): Payload {
|
||||
$linkId = $subjectData->getArgs()['link_id'];
|
||||
$linkEntity = $this->newsletterLinkRepository->findOneById($linkId);
|
||||
if (!$linkEntity) {
|
||||
throw NotFoundException::create()->withMessage(sprintf("Email link with ID '%d' not found", $linkId));
|
||||
}
|
||||
|
||||
return new NewsletterLinkPayload($linkEntity);
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Subjects;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Data\Field;
|
||||
use MailPoet\Automation\Engine\Data\Subject as SubjectData;
|
||||
use MailPoet\Automation\Engine\Integration\Payload;
|
||||
use MailPoet\Automation\Engine\Integration\Subject;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Payloads\SegmentPayload;
|
||||
use MailPoet\NotFoundException;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
use MailPoet\Validator\Builder;
|
||||
use MailPoet\Validator\Schema\ObjectSchema;
|
||||
|
||||
/**
|
||||
* @implements Subject<SegmentPayload>
|
||||
*/
|
||||
class SegmentSubject implements Subject {
|
||||
const KEY = 'mailpoet:segment';
|
||||
|
||||
/** @var SegmentsRepository */
|
||||
private $segmentsRepository;
|
||||
|
||||
public function __construct(
|
||||
SegmentsRepository $segmentsRepository
|
||||
) {
|
||||
$this->segmentsRepository = $segmentsRepository;
|
||||
}
|
||||
|
||||
public function getKey(): string {
|
||||
return self::KEY;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
// translators: automation subject (entity entering automation) title
|
||||
return __('MailPoet segment', 'mailpoet');
|
||||
}
|
||||
|
||||
public function getArgsSchema(): ObjectSchema {
|
||||
return Builder::object([
|
||||
'segment_id' => Builder::integer()->required(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function getPayload(SubjectData $subjectData): Payload {
|
||||
$id = $subjectData->getArgs()['segment_id'];
|
||||
$segment = $this->segmentsRepository->findOneById($id);
|
||||
if (!$segment) {
|
||||
// translators: %d is the ID.
|
||||
throw NotFoundException::create()->withMessage(sprintf(__("Segment with ID '%d' not found.", 'mailpoet'), $id));
|
||||
}
|
||||
return new SegmentPayload($segment);
|
||||
}
|
||||
|
||||
/** @return Field[] */
|
||||
public function getFields(): array {
|
||||
return [
|
||||
// phpcs:disable Squiz.PHP.CommentedOutCode.Found -- temporarily hide those fields
|
||||
/*
|
||||
new Field(
|
||||
'mailpoet:segment:id',
|
||||
Field::TYPE_INTEGER,
|
||||
__('Segment ID', 'mailpoet'),
|
||||
function (SegmentPayload $payload) {
|
||||
return $payload->getId();
|
||||
}
|
||||
),
|
||||
new Field(
|
||||
'mailpoet:segment:name',
|
||||
Field::TYPE_STRING,
|
||||
__('Segment name', 'mailpoet'),
|
||||
function (SegmentPayload $payload) {
|
||||
return $payload->getName();
|
||||
}
|
||||
),
|
||||
*/
|
||||
];
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Subjects;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Data\Field;
|
||||
use MailPoet\Automation\Engine\Data\Subject as SubjectData;
|
||||
use MailPoet\Automation\Engine\Integration\Payload;
|
||||
use MailPoet\Automation\Engine\Integration\Subject;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Fields\SubscriberFieldsFactory;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Payloads\SubscriberPayload;
|
||||
use MailPoet\NotFoundException;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
use MailPoet\Validator\Builder;
|
||||
use MailPoet\Validator\Schema\ObjectSchema;
|
||||
|
||||
/**
|
||||
* @implements Subject<SubscriberPayload>
|
||||
*/
|
||||
class SubscriberSubject implements Subject {
|
||||
const KEY = 'mailpoet:subscriber';
|
||||
|
||||
/** @var SubscriberFieldsFactory */
|
||||
private $subscriberFieldsFactory;
|
||||
|
||||
/** @var SubscribersRepository */
|
||||
private $subscribersRepository;
|
||||
|
||||
public function __construct(
|
||||
SubscriberFieldsFactory $subscriberFieldsFactory,
|
||||
SubscribersRepository $subscribersRepository
|
||||
) {
|
||||
$this->subscriberFieldsFactory = $subscriberFieldsFactory;
|
||||
$this->subscribersRepository = $subscribersRepository;
|
||||
}
|
||||
|
||||
public function getKey(): string {
|
||||
return self::KEY;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
// translators: automation subject (entity entering automation) title
|
||||
return __('MailPoet subscriber', 'mailpoet');
|
||||
}
|
||||
|
||||
public function getArgsSchema(): ObjectSchema {
|
||||
return Builder::object([
|
||||
'subscriber_id' => Builder::integer()->required(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function getPayload(SubjectData $subjectData): Payload {
|
||||
$id = $subjectData->getArgs()['subscriber_id'];
|
||||
$subscriber = $this->subscribersRepository->findOneById($id);
|
||||
if (!$subscriber) {
|
||||
// translators: %d is the ID.
|
||||
throw NotFoundException::create()->withMessage(sprintf(__("Subscriber with ID '%d' not found.", 'mailpoet'), $id));
|
||||
}
|
||||
return new SubscriberPayload($subscriber);
|
||||
}
|
||||
|
||||
/** @return Field[] */
|
||||
public function getFields(): array {
|
||||
return $this->subscriberFieldsFactory->getFields();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+394
@@ -0,0 +1,394 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Templates;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Data\Automation;
|
||||
use MailPoet\Automation\Engine\Data\AutomationTemplate;
|
||||
use MailPoet\Automation\Engine\Templates\AutomationBuilder;
|
||||
use MailPoet\Automation\Integrations\WooCommerce\WooCommerce;
|
||||
|
||||
class TemplatesFactory {
|
||||
/** @var AutomationBuilder */
|
||||
private $builder;
|
||||
|
||||
/** @var WooCommerce */
|
||||
private $woocommerce;
|
||||
|
||||
public function __construct(
|
||||
AutomationBuilder $builder,
|
||||
WooCommerce $woocommerce
|
||||
) {
|
||||
$this->builder = $builder;
|
||||
$this->woocommerce = $woocommerce;
|
||||
}
|
||||
|
||||
public function createTemplates(): array {
|
||||
$templates = [
|
||||
$this->createSubscriberWelcomeEmailTemplate(),
|
||||
$this->createUserWelcomeEmailTemplate(),
|
||||
$this->createSubscriberWelcomeSeriesTemplate(),
|
||||
$this->createUserWelcomeSeriesTemplate(),
|
||||
];
|
||||
|
||||
if ($this->woocommerce->isWooCommerceActive()) {
|
||||
$templates[] = $this->createFirstPurchaseTemplate();
|
||||
$templates[] = $this->createThankLoyalCustomersTemplate();
|
||||
$templates[] = $this->createWinBackCustomersTemplate();
|
||||
$templates[] = $this->createAbandonedCartTemplate();
|
||||
$templates[] = $this->createAbandonedCartCampaignTemplate();
|
||||
$templates[] = $this->createPurchasedProductTemplate();
|
||||
$templates[] = $this->createPurchasedProductWithTagTemplate();
|
||||
$templates[] = $this->createPurchasedInCategoryTemplate();
|
||||
}
|
||||
|
||||
return $templates;
|
||||
}
|
||||
|
||||
private function createSubscriberWelcomeEmailTemplate(): AutomationTemplate {
|
||||
return new AutomationTemplate(
|
||||
'subscriber-welcome-email',
|
||||
'welcome',
|
||||
__('Welcome new subscribers', 'mailpoet'),
|
||||
__(
|
||||
'Send a welcome email when someone subscribes to your list. Optionally, you can choose to send this email after a specified period.',
|
||||
'mailpoet'
|
||||
),
|
||||
function (): Automation {
|
||||
return $this->builder->createFromSequence(
|
||||
__('Welcome new subscribers', 'mailpoet'),
|
||||
[
|
||||
['key' => 'mailpoet:someone-subscribes'],
|
||||
['key' => 'core:delay', 'args' => ['delay' => 1, 'delay_type' => 'MINUTES']],
|
||||
['key' => 'mailpoet:send-email'],
|
||||
],
|
||||
[
|
||||
'mailpoet:run-once-per-subscriber' => true,
|
||||
]
|
||||
);
|
||||
},
|
||||
[
|
||||
'automationSteps' => 1,
|
||||
],
|
||||
AutomationTemplate::TYPE_DEFAULT
|
||||
);
|
||||
}
|
||||
|
||||
private function createUserWelcomeEmailTemplate(): AutomationTemplate {
|
||||
return new AutomationTemplate(
|
||||
'user-welcome-email',
|
||||
'welcome',
|
||||
__('Welcome new WordPress users', 'mailpoet'),
|
||||
__(
|
||||
'Send a welcome email when a new WordPress user registers to your website. Optionally, you can choose to send this email after a specified period.',
|
||||
'mailpoet'
|
||||
),
|
||||
function (): Automation {
|
||||
return $this->builder->createFromSequence(
|
||||
__('Welcome new WordPress users', 'mailpoet'),
|
||||
[
|
||||
['key' => 'mailpoet:wp-user-registered'],
|
||||
['key' => 'core:delay', 'args' => ['delay' => 1, 'delay_type' => 'MINUTES']],
|
||||
['key' => 'mailpoet:send-email'],
|
||||
],
|
||||
[
|
||||
'mailpoet:run-once-per-subscriber' => true,
|
||||
]
|
||||
);
|
||||
},
|
||||
[
|
||||
'automationSteps' => 1,
|
||||
],
|
||||
AutomationTemplate::TYPE_DEFAULT
|
||||
);
|
||||
}
|
||||
|
||||
private function createSubscriberWelcomeSeriesTemplate(): AutomationTemplate {
|
||||
return new AutomationTemplate(
|
||||
'subscriber-welcome-series',
|
||||
'welcome',
|
||||
__('Welcome series for new subscribers', 'mailpoet'),
|
||||
__(
|
||||
'Welcome new subscribers and start building a relationship with them. Send an email immediately after someone subscribes to your list to introduce your brand and a follow-up two days later to keep the conversation going.',
|
||||
'mailpoet'
|
||||
),
|
||||
function (): Automation {
|
||||
return $this->builder->createFromSequence(
|
||||
__('Welcome series for new subscribers', 'mailpoet'),
|
||||
[]
|
||||
);
|
||||
},
|
||||
[
|
||||
'automationSteps' => 2,
|
||||
],
|
||||
AutomationTemplate::TYPE_PREMIUM
|
||||
);
|
||||
}
|
||||
|
||||
private function createUserWelcomeSeriesTemplate(): AutomationTemplate {
|
||||
return new AutomationTemplate(
|
||||
'user-welcome-series',
|
||||
'welcome',
|
||||
__('Welcome series for new WordPress users', 'mailpoet'),
|
||||
__(
|
||||
'Welcome new WordPress users to your site. Send an email immediately after a WordPress user registers. Send a follow-up email two days later with more in-depth information.',
|
||||
'mailpoet'
|
||||
),
|
||||
function (): Automation {
|
||||
return $this->builder->createFromSequence(
|
||||
__('Welcome series for new WordPress users', 'mailpoet'),
|
||||
[]
|
||||
);
|
||||
},
|
||||
[
|
||||
'automationSteps' => 2,
|
||||
],
|
||||
AutomationTemplate::TYPE_PREMIUM
|
||||
);
|
||||
}
|
||||
|
||||
private function createFirstPurchaseTemplate(): AutomationTemplate {
|
||||
return new AutomationTemplate(
|
||||
'first-purchase',
|
||||
'woocommerce',
|
||||
__('Celebrate first-time buyers', 'mailpoet'),
|
||||
__(
|
||||
'Welcome your first-time customers by sending an email with a special offer for their next purchase. Make them feel appreciated within your brand.',
|
||||
'mailpoet'
|
||||
),
|
||||
function (): Automation {
|
||||
return $this->builder->createFromSequence(
|
||||
__('Celebrate first-time buyers', 'mailpoet'),
|
||||
[
|
||||
[
|
||||
'key' => 'woocommerce:order-completed',
|
||||
'filters' => [
|
||||
'operator' => 'and',
|
||||
'groups' => [
|
||||
[
|
||||
'operator' => 'and',
|
||||
'filters' => [
|
||||
['field' => 'woocommerce:order:is-first-order', 'condition' => 'is', 'value' => true],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'mailpoet:send-email',
|
||||
'args' => [
|
||||
'name' => __('Thank you', 'mailpoet'),
|
||||
'subject' => __('Thank You for Choosing Us!', 'mailpoet'),
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'mailpoet:run-once-per-subscriber' => true,
|
||||
]
|
||||
);
|
||||
},
|
||||
[
|
||||
'automationSteps' => 1,
|
||||
],
|
||||
AutomationTemplate::TYPE_DEFAULT
|
||||
);
|
||||
}
|
||||
|
||||
private function createThankLoyalCustomersTemplate(): AutomationTemplate {
|
||||
return new AutomationTemplate(
|
||||
'thank-loyal-customers',
|
||||
'woocommerce',
|
||||
__('Thank loyal customers', 'mailpoet'),
|
||||
__(
|
||||
'These are your most important customers. Make them feel special by sending a thank you note for supporting your brand.',
|
||||
'mailpoet'
|
||||
),
|
||||
function (): Automation {
|
||||
return $this->builder->createFromSequence(
|
||||
__('Thank loyal customers', 'mailpoet'),
|
||||
[]
|
||||
);
|
||||
},
|
||||
[
|
||||
'automationSteps' => 1,
|
||||
],
|
||||
AutomationTemplate::TYPE_PREMIUM
|
||||
);
|
||||
}
|
||||
|
||||
private function createWinBackCustomersTemplate(): AutomationTemplate {
|
||||
return new AutomationTemplate(
|
||||
'win-back-customers',
|
||||
'woocommerce',
|
||||
__('Win-back customers', 'mailpoet'),
|
||||
__(
|
||||
'Rekindle the relationship with past customers by reminding them of their favorite products and showcasing what’s new, encouraging a return to your brand.',
|
||||
'mailpoet'
|
||||
),
|
||||
function (): Automation {
|
||||
return $this->builder->createFromSequence(
|
||||
__('Win-back customers', 'mailpoet'),
|
||||
[]
|
||||
);
|
||||
},
|
||||
[
|
||||
'automationSteps' => 4,
|
||||
],
|
||||
AutomationTemplate::TYPE_PREMIUM
|
||||
);
|
||||
}
|
||||
|
||||
private function createAbandonedCartTemplate(): AutomationTemplate {
|
||||
return new AutomationTemplate(
|
||||
'abandoned-cart',
|
||||
'abandoned-cart',
|
||||
__('Abandoned cart reminder', 'mailpoet'),
|
||||
__(
|
||||
'Nudge your shoppers to complete the purchase after they have added a product to the cart but haven’t completed the order.',
|
||||
'mailpoet'
|
||||
),
|
||||
function (): Automation {
|
||||
return $this->builder->createFromSequence(
|
||||
__('Abandoned cart reminder', 'mailpoet'),
|
||||
[
|
||||
['key' => 'woocommerce:abandoned-cart'],
|
||||
[
|
||||
'key' => 'mailpoet:send-email',
|
||||
'args' => [
|
||||
'name' => __('Abandoned cart', 'mailpoet'),
|
||||
'subject' => __('Looks like you forgot something', 'mailpoet'),
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
},
|
||||
[
|
||||
'automationSteps' => 1,
|
||||
],
|
||||
AutomationTemplate::TYPE_DEFAULT
|
||||
);
|
||||
}
|
||||
|
||||
private function createAbandonedCartCampaignTemplate(): AutomationTemplate {
|
||||
return new AutomationTemplate(
|
||||
'abandoned-cart-campaign',
|
||||
'abandoned-cart',
|
||||
__('Abandoned cart campaign', 'mailpoet'),
|
||||
__(
|
||||
'Encourage your potential customers to finalize their purchase when they have added items to their cart but haven’t finished the order yet. Offer a coupon code as a last resort to convert them to customers.',
|
||||
'mailpoet'
|
||||
),
|
||||
function (): Automation {
|
||||
return $this->builder->createFromSequence(
|
||||
__('Abandoned cart campaign', 'mailpoet'),
|
||||
[]
|
||||
);
|
||||
},
|
||||
[
|
||||
'automationSteps' => 5,
|
||||
],
|
||||
AutomationTemplate::TYPE_PREMIUM
|
||||
);
|
||||
}
|
||||
|
||||
private function createPurchasedProductTemplate(): AutomationTemplate {
|
||||
return new AutomationTemplate(
|
||||
'purchased-product',
|
||||
'woocommerce',
|
||||
__('Purchased a product', 'mailpoet'),
|
||||
__(
|
||||
'Share care instructions or simply thank the customer for making an order.',
|
||||
'mailpoet'
|
||||
),
|
||||
function (): Automation {
|
||||
return $this->builder->createFromSequence(
|
||||
__('Purchased a product', 'mailpoet'),
|
||||
[
|
||||
[
|
||||
'key' => 'woocommerce:buys-a-product',
|
||||
],
|
||||
[
|
||||
'key' => 'mailpoet:send-email',
|
||||
'args' => [
|
||||
'name' => __('Important information about your order', 'mailpoet'),
|
||||
'subject' => __('Important information about your order', 'mailpoet'),
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
},
|
||||
[
|
||||
'automationSteps' => 1,
|
||||
],
|
||||
AutomationTemplate::TYPE_DEFAULT
|
||||
);
|
||||
}
|
||||
|
||||
private function createPurchasedProductWithTagTemplate(): AutomationTemplate {
|
||||
return new AutomationTemplate(
|
||||
'purchased-product-with-tag',
|
||||
'woocommerce',
|
||||
__('Purchased a product with a tag', 'mailpoet'),
|
||||
__(
|
||||
'Share care instructions or simply thank the customer for making an order.',
|
||||
'mailpoet'
|
||||
),
|
||||
function (): Automation {
|
||||
return $this->builder->createFromSequence(
|
||||
__('Purchased a product with a tag', 'mailpoet'),
|
||||
[
|
||||
[
|
||||
'key' => 'woocommerce:buys-from-a-tag',
|
||||
],
|
||||
[
|
||||
'key' => 'mailpoet:send-email',
|
||||
'args' => [
|
||||
'name' => __('Important information about your order', 'mailpoet'),
|
||||
'subject' => __('Important information about your order', 'mailpoet'),
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
},
|
||||
[
|
||||
'automationSteps' => 1,
|
||||
],
|
||||
AutomationTemplate::TYPE_DEFAULT
|
||||
);
|
||||
}
|
||||
|
||||
private function createPurchasedInCategoryTemplate(): AutomationTemplate {
|
||||
return new AutomationTemplate(
|
||||
'purchased-in-category',
|
||||
'woocommerce',
|
||||
__('Purchased in a category', 'mailpoet'),
|
||||
__(
|
||||
'Share care instructions or simply thank the customer for making an order.',
|
||||
'mailpoet'
|
||||
),
|
||||
function (): Automation {
|
||||
return $this->builder->createFromSequence(
|
||||
__('Purchased in a category', 'mailpoet'),
|
||||
[
|
||||
[
|
||||
'key' => 'woocommerce:buys-from-a-category',
|
||||
],
|
||||
[
|
||||
'key' => 'mailpoet:send-email',
|
||||
'args' => [
|
||||
'name' => __('Important information about your order', 'mailpoet'),
|
||||
'subject' => __('Important information about your order', 'mailpoet'),
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
},
|
||||
[
|
||||
'automationSteps' => 1,
|
||||
],
|
||||
AutomationTemplate::TYPE_DEFAULT
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Triggers;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Data\StepRunArgs;
|
||||
use MailPoet\Automation\Engine\Data\StepValidationArgs;
|
||||
use MailPoet\Automation\Engine\Data\Subject;
|
||||
use MailPoet\Automation\Engine\Hooks;
|
||||
use MailPoet\Automation\Engine\Integration\Trigger;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Payloads\SegmentPayload;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Subjects\SegmentSubject;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Subjects\SubscriberSubject;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Entities\SubscriberSegmentEntity;
|
||||
use MailPoet\InvalidStateException;
|
||||
use MailPoet\Segments\SegmentsRepository;
|
||||
use MailPoet\Validator\Builder;
|
||||
use MailPoet\Validator\Schema\ObjectSchema;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
class SomeoneSubscribesTrigger implements Trigger {
|
||||
const KEY = 'mailpoet:someone-subscribes';
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
/** @var SegmentsRepository */
|
||||
private $segmentsRepository;
|
||||
|
||||
public function __construct(
|
||||
WPFunctions $wp,
|
||||
SegmentsRepository $segmentsRepository
|
||||
) {
|
||||
$this->wp = $wp;
|
||||
$this->segmentsRepository = $segmentsRepository;
|
||||
}
|
||||
|
||||
public function getKey(): string {
|
||||
return 'mailpoet:someone-subscribes';
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
// translators: automation trigger title
|
||||
return __('Someone subscribes', 'mailpoet');
|
||||
}
|
||||
|
||||
public function getArgsSchema(): ObjectSchema {
|
||||
return Builder::object([
|
||||
'segment_ids' => Builder::array(Builder::number()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function getSubjectKeys(): array {
|
||||
return [
|
||||
SubscriberSubject::KEY,
|
||||
SegmentSubject::KEY,
|
||||
];
|
||||
}
|
||||
|
||||
public function validate(StepValidationArgs $args): void {
|
||||
}
|
||||
|
||||
public function registerHooks(): void {
|
||||
$this->wp->addAction('mailpoet_segment_subscribed', [$this, 'handleSubscription'], 10, 2);
|
||||
}
|
||||
|
||||
public function handleSubscription(SubscriberSegmentEntity $subscriberSegment): void {
|
||||
$segment = $subscriberSegment->getSegment();
|
||||
$subscriber = $subscriberSegment->getSubscriber();
|
||||
|
||||
if (!$segment || !$subscriber) {
|
||||
throw new InvalidStateException();
|
||||
}
|
||||
|
||||
$this->wp->doAction(Hooks::TRIGGER, $this, [
|
||||
new Subject(SegmentSubject::KEY, ['segment_id' => $segment->getId()]),
|
||||
new Subject(SubscriberSubject::KEY, ['subscriber_id' => $subscriber->getId()]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function isTriggeredBy(StepRunArgs $args): bool {
|
||||
$segmentId = $args->getSinglePayloadByClass(SegmentPayload::class)->getId();
|
||||
$segment = $this->segmentsRepository->findOneById($segmentId);
|
||||
if (!$segment || $segment->getType() !== SegmentEntity::TYPE_DEFAULT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Triggers when no segment IDs defined (= any segment) or the current segment paylo.
|
||||
$triggerArgs = $args->getStep()->getArgs();
|
||||
$segmentIds = $triggerArgs['segment_ids'] ?? [];
|
||||
return !is_array($segmentIds) || !$segmentIds || in_array($segmentId, $segmentIds, true);
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
namespace MailPoet\Automation\Integrations\MailPoet\Triggers;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
|
||||
use MailPoet\Automation\Engine\Data\StepRunArgs;
|
||||
use MailPoet\Automation\Engine\Data\StepValidationArgs;
|
||||
use MailPoet\Automation\Engine\Data\Subject;
|
||||
use MailPoet\Automation\Engine\Hooks;
|
||||
use MailPoet\Automation\Engine\Integration\Trigger;
|
||||
use MailPoet\Automation\Engine\WordPress;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Payloads\SegmentPayload;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Payloads\SubscriberPayload;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Subjects\SegmentSubject;
|
||||
use MailPoet\Automation\Integrations\MailPoet\Subjects\SubscriberSubject;
|
||||
use MailPoet\Entities\SegmentEntity;
|
||||
use MailPoet\Entities\SubscriberSegmentEntity;
|
||||
use MailPoet\InvalidStateException;
|
||||
use MailPoet\Subscribers\SubscribersRepository;
|
||||
use MailPoet\Validator\Builder;
|
||||
use MailPoet\Validator\Schema\ObjectSchema;
|
||||
|
||||
class UserRegistrationTrigger implements Trigger {
|
||||
const KEY = 'mailpoet:wp-user-registered';
|
||||
|
||||
/** @var WordPress */
|
||||
private $wp;
|
||||
|
||||
private $subscribersRepository;
|
||||
|
||||
public function __construct(
|
||||
WordPress $wp,
|
||||
SubscribersRepository $subscribersRepository
|
||||
) {
|
||||
$this->wp = $wp;
|
||||
$this->subscribersRepository = $subscribersRepository;
|
||||
}
|
||||
|
||||
public function getKey(): string {
|
||||
return self::KEY;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
// translators: automation trigger title
|
||||
return __('WordPress user registers', 'mailpoet');
|
||||
}
|
||||
|
||||
public function getArgsSchema(): ObjectSchema {
|
||||
return Builder::object([
|
||||
'roles' => Builder::array(Builder::string()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function getSubjectKeys(): array {
|
||||
return [
|
||||
SegmentSubject::KEY,
|
||||
SubscriberSubject::KEY,
|
||||
];
|
||||
}
|
||||
|
||||
public function validate(StepValidationArgs $args): void {
|
||||
}
|
||||
|
||||
public function registerHooks(): void {
|
||||
$this->wp->addAction('mailpoet_segment_subscribed', [$this, 'handleSubscription']);
|
||||
}
|
||||
|
||||
public function handleSubscription(SubscriberSegmentEntity $subscriberSegment): void {
|
||||
$segment = $subscriberSegment->getSegment();
|
||||
$subscriber = $subscriberSegment->getSubscriber();
|
||||
|
||||
if (!$segment || !$subscriber) {
|
||||
throw new InvalidStateException();
|
||||
}
|
||||
|
||||
$this->wp->doAction(Hooks::TRIGGER, $this, [
|
||||
new Subject(SegmentSubject::KEY, ['segment_id' => $segment->getId()]),
|
||||
new Subject(SubscriberSubject::KEY, ['subscriber_id' => $subscriber->getId()]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function isTriggeredBy(StepRunArgs $args): bool {
|
||||
$segmentPayload = $args->getSinglePayloadByClass(SegmentPayload::class);
|
||||
if ($segmentPayload->getType() !== SegmentEntity::TYPE_WP_USERS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$subscriberPayload = $args->getSinglePayloadByClass(SubscriberPayload::class);
|
||||
$this->subscribersRepository->refresh($subscriberPayload->getSubscriber());
|
||||
if (!$subscriberPayload->isWPUser()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$user = $this->wp->getUserBy('id', (int)$subscriberPayload->getWpUserId());
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$triggerArgs = $args->getStep()->getArgs();
|
||||
$roles = $triggerArgs['roles'] ?? [];
|
||||
return !is_array($roles) || !$roles || count(array_intersect($user->roles, $roles)) > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -0,0 +1 @@
|
||||
<?php
|
||||
Reference in New Issue
Block a user