settings = $settings; $this->newslettersRepository = $newslettersRepository; $this->subscriberSegmentRepository = $subscriberSegmentRepository; $this->subscribersRepository = $subscribersRepository; $this->automationEmailScheduler = $automationEmailScheduler; $this->newsletterOptionsRepository = $newsletterOptionsRepository; $this->newsletterOptionFieldsRepository = $newsletterOptionFieldsRepository; } public function getKey(): string { return self::KEY; } public function getName(): string { 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) { $emailId = $args->getStep()->getArgs()['email_id'] ?? ''; if (empty($emailId)) { throw ValidationException::create() ->withError('email_id', __("Automation email not found.", 'mailpoet')); } throw ValidationException::create() ->withError( 'email_id', // translators: %s is the ID of email. sprintf(__("Automation email with ID '%s' not found.", 'mailpoet'), $emailId) ); } } public function run(StepRunArgs $args, StepRunController $controller): void { $newsletter = $this->getEmailForStep($args->getStep()); $subscriber = $this->getSubscriber($args); $subscriberStatus = $subscriber->getStatus(); if ($newsletter->getType() !== NewsletterEntity::TYPE_AUTOMATION_TRANSACTIONAL && $subscriberStatus !== SubscriberEntity::STATUS_SUBSCRIBED) { throw InvalidStateException::create()->withMessage(sprintf("Cannot schedule a newsletter for subscriber ID '%s' because their status is '%s'.", $subscriber->getId(), $subscriberStatus)); } if ($subscriberStatus === SubscriberEntity::STATUS_BOUNCED) { throw InvalidStateException::create()->withMessage(sprintf("Cannot schedule an email for subscriber ID '%s' because their status is '%s'.", $subscriber->getId(), $subscriberStatus)); } $meta = $this->getNewsletterMeta($args); try { $this->automationEmailScheduler->createSendingTask($newsletter, $subscriber, $meta); } catch (Throwable $e) { throw InvalidStateException::create()->withMessage('Could not create sending task.'); } } private function getNewsletterMeta(StepRunArgs $args): array { if (!$this->automationHasAbandonedCartTrigger($args->getAutomation())) { return []; } $payload = $args->getSinglePayloadByClass(AbandonedCartPayload::class); return [AbandonedCart::TASK_META_NAME => $payload->getProductIds()]; } private function getSubscriber(StepRunArgs $args): SubscriberEntity { $subscriberId = $args->getSinglePayloadByClass(SubscriberPayload::class)->getId(); try { $segmentId = $args->getSinglePayloadByClass(SegmentPayload::class)->getId(); $subscriberSegment = $this->subscriberSegmentRepository->findOneBy([ 'subscriber' => $subscriberId, 'segment' => $segmentId, 'status' => SubscriberEntity::STATUS_SUBSCRIBED, ]); if (!$subscriberSegment) { throw InvalidStateException::create()->withMessage(sprintf("Subscriber ID '%s' is not subscribed to segment ID '%s'.", $subscriberId, $segmentId)); } $subscriber = $subscriberSegment->getSubscriber(); if (!$subscriber) { throw InvalidStateException::create(); } } catch (NotFoundException $e) { $subscriber = $this->subscribersRepository->findOneById($subscriberId); 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(), ['woocommerce:order-status-changed'], true); } ); if (!$triggers || count($transactionalTriggers) !== count($triggers)) { return false; } foreach ($transactionalTriggers as $trigger) { $nextSteps = array_map( function(NextStep $nextStep): string { return $nextStep->getId(); }, $trigger->getNextSteps() ); if (!in_array($step->getId(), $nextSteps, true)) { return false; } } return true; } private function automationHasWooCommerceTrigger(Automation $automation): bool { return (bool)array_filter( $automation->getTriggers(), function(Step $step): bool { return in_array($step->getKey(), ['woocommerce:order-status-changed', 'woocommerce:abandoned-cart'], true); } ); } 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; } }