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; } public function onDuplicate(Step $step): Step { $args = $step->getArgs(); $emailId = (int)$args['email_id']; if (!$emailId) { // if the email is not yet designed, we don't need to duplicate it return $step; } $email = $this->newslettersRepository->findOneBy([ 'id' => $emailId, ]); if (!$email) { throw new \MailPoet\Automation\Engine\Exceptions\InvalidStateException('Automation email entity not found for duplication.'); } try { $duplicatedNewsletter = $this->newsletterSaveController->duplicate($email); } catch (\Throwable $e) { throw new \MailPoet\Automation\Engine\Exceptions\InvalidStateException('Failed to duplicate automation email: ' . $e->getMessage()); } $duplicatedNewsletter->setStatus($email->getStatus()); $this->newslettersRepository->flush(); $args['email_id'] = $duplicatedNewsletter->getId(); $args['subject'] = $duplicatedNewsletter->getSubject(); return new Step( $step->getId(), $step->getType(), $step->getKey(), $args, $step->getNextSteps(), $step->getFilters() ); } }