tings if (empty($data['sender'])) { $sender = $this->settings->get('sender', []); $data['sender_name'] = $sender['name'] ?? ''; $data['sender_address'] = $sender['address'] ?? ''; } // set default reply_to based on settings if (empty($data['reply_to'])) { $replyTo = $this->settings->get('reply_to', []); $data['reply_to_name'] = $replyTo['name'] ?? ''; $data['reply_to_address'] = $replyTo['address'] ?? ''; } $this->updateNewsletter($newsletter, $data); $this->newslettersRepository->persist($newsletter); return $newsletter; } private function updateNewsletter(NewsletterEntity $newsletter, array $data) { if (array_key_exists('type', $data)) { $newsletter->setType($data['type']); } if (array_key_exists('subject', $data)) { $newsletter->setSubject($data['subject']); } if (array_key_exists('preheader', $data)) { $newsletter->setPreheader($data['preheader']); } if (array_key_exists('body', $data)) { $decodedBody = json_decode($data['body'], true); $newsletter->setBody(is_array($decodedBody) ? $decodedBody : null); } if (array_key_exists('ga_campaign', $data)) { $newsletter->setGaCampaign($data['ga_campaign']); } if (array_key_exists('sender_name', $data)) { $newsletter->setSenderName($data['sender_name'] ?? ''); } if (array_key_exists('sender_address', $data)) { $newsletter->setSenderAddress($data['sender_address'] ?? ''); } if (array_key_exists('reply_to_name', $data)) { $newsletter->setReplyToName($data['reply_to_name'] ?? ''); } if (array_key_exists('reply_to_address', $data)) { $newsletter->setReplyToAddress($data['reply_to_address'] ?? ''); } if ($newsletter->getStatus() === NewsletterEntity::STATUS_CORRUPT) { $newsletter->setStatus($newsletter->canBeSetActive() ? NewsletterEntity::STATUS_ACTIVE : NewsletterEntity::STATUS_SENDING); } } private function updateSegments(NewsletterEntity $newsletter, array $segments) { $newsletterSegments = []; foreach ($segments as $segmentData) { if (!is_array($segmentData) || !isset($segmentData['id']) || !is_numeric($segmentData['id'])) { continue; } $segment = $this->entityManager->getReference(SegmentEntity::class, (int)$segmentData['id']); if (!$segment) { continue; } $newsletterSegment = $this->newsletterSegmentRepository->findOneBy([ 'newsletter' => $newsletter, 'segment' => $segment, ]); if (!$newsletterSegment) { $newsletterSegment = new NewsletterSegmentEntity($newsletter, $segment); $this->entityManager->persist($newsletterSegment); } if (!$newsletter->getNewsletterSegments()->contains($newsletterSegment)) { $newsletter->getNewsletterSegments()->add($newsletterSegment); } $newsletterSegments[] = $newsletterSegment; } // on Doctrine < 2.6, when using orphan removal, we need to remove items manually instead of replacing the // whole collection (see https://github.com/doctrine/orm/commit/1587aac4ff6b0753ddd5f8b8d4558b6b40096057) foreach ($newsletter->getNewsletterSegments() as $newsletterSegment) { if (!in_array($newsletterSegment, $newsletterSegments, true)) { $newsletter->getNewsletterSegments()->removeElement($newsletterSegment); // triggers orphan removal } } $this->entityManager->flush(); } private function updateOptions(NewsletterEntity $newsletter, array $options) { $optionFields = $this->newsletterOptionFieldsRepository->findBy(['newsletterType' => $newsletter->getType()]); foreach ($optionFields as $optionField) { $name = $optionField->getName(); if (!isset($options[$name])) { continue; } $option = $this->newsletterOptionsRepository->findOneBy([ 'newsletter' => $newsletter, 'optionField' => $optionField, ]); if (!$option) { $option = new NewsletterOptionEntity($newsletter, $optionField); $this->newsletterOptionsRepository->persist($option); } $value = $options[$name]; if ($name === NewsletterOptionFieldEntity::NAME_SHARE_VISIBILITY) { $value = $this->shareVisibility->sanitize((string)$value); } $option->setValue($value); if (!$newsletter->getOptions()->contains($option)) { $newsletter->getOptions()->add($option); } } $this->entityManager->flush(); } private function rescheduleIfNeeded(NewsletterEntity $newsletter) { if ($newsletter->getType() !== NewsletterEntity::TYPE_NOTIFICATION) { return; } // generate the new schedule from options and get the new "next run" date $schedule = $this->postNotificationScheduler->processPostNotificationSchedule($newsletter); $nextRunDateString = $this->scheduler->getNextRunDate($schedule); $nextRunDate = $nextRunDateString ? Carbon::createFromFormat('Y-m-d H:i:s', $nextRunDateString) : null; if ($nextRunDate === false) { throw InvalidStateException::create()->withMessage('Invalid next run date generated'); } // find previously scheduled jobs and reschedule them $scheduledTasks = $this->scheduledTasksRepository->findByNewsletterAndStatus($newsletter, ScheduledTaskEntity::STATUS_SCHEDULED); foreach ($scheduledTasks as $scheduledTask) { $scheduledTask->setScheduledAt($nextRunDate); } $this->entityManager->flush(); // 'processPostNotificationSchedule' modifies newsletter options by old model - let's reload them foreach ($newsletter->getOptions() as $newsletterOption) { $this->entityManager->refresh($newsletterOption); } } private function updateQueue(NewsletterEntity $newsletter, array $options) { if ($newsletter->getType() !== NewsletterEntity::TYPE_STANDARD) { return; } $queue = $newsletter->getLatestQueue(); if (!$queue) { return; } // if newsletter was previously scheduled and is now unscheduled, set its status to DRAFT and delete associated queue record if ($newsletter->getStatus() === NewsletterEntity::STATUS_SCHEDULED && isset($options['isScheduled']) && empty($options['isScheduled'])) { if ($this->timeZoneCampaignScheduler->isTimeZoneQueue($queue)) { $this->timeZoneCampaignScheduler->deleteScheduledCampaignQueues($newsletter); $newsletter->setStatus(NewsletterEntity::STATUS_DRAFT); $this->entityManager->flush(); return; } $this->entityManager->remove($queue); $newsletter->setStatus(NewsletterEntity::STATUS_DRAFT); } else { if ($this->timeZoneCampaignScheduler->isTimeZoneQueue($queue)) { if (!$this->timeZoneCampaignScheduler->canReplaceScheduledCampaign($newsletter)) { throw new InvalidStateException(__('This email can no longer be edited because one or more time zone batches have already started.', 'mailpoet')); } foreach ($this->timeZoneCampaignScheduler->getCampaignQueues($queue) as $campaignQueue) { $campaignQueue->setNewsletterRenderedSubject(null); $campaignQueue->setNewsletterRenderedBody(null); $this->entityManager->persist($campaignQueue); } $this->entityManager->flush(); return; } $queue->setNewsletterRenderedSubject(null); $queue->setNewsletterRenderedBody(null); $this->entityManager->persist($queue); $newsletterQueueTask = new NewsletterQueueTask(); $task = $queue->getTask(); if (!$task instanceof ScheduledTaskEntity) { throw new InvalidStateException('Newsletter queue has no associated scheduled task'); } $newsletterQueueTask->preProcessNewsletter($newsletter, $task); } $this->entityManager->flush(); } private function ensureWpPost(NewsletterEntity $newsletter): void { if ($newsletter->getWpPostId()) { return; } // Automation emails use 'private' status to prevent them from appearing in public queries $isAutomation = $newsletter->getType() === NewsletterEntity::TYPE_AUTOMATION; $postStatus = $isAutomation ? 'private' : 'draft'; $postData = [ 'post_content' => '', 'post_type' => EmailEditor::MAILPOET_EMAIL_POST_TYPE, 'post_status' => $postStatus, 'post_author' => $this->wp->getCurrentUserId(), 'post_title' => __('New Email', 'mailpoet'), ]; // Set meta flag for automation emails to prevent other plugins from creating duplicate newsletters if ($isAutomation) { $postData['meta_input'] = ['_mailpoet_is_automation_email' => '1']; } $newPostId = $this->wp->wpInsertPost($postData); $newsletter->setWpPost($this->entityManager->getReference(WpPostEntity::class, $newPostId)); $this->entityManager->flush(); } }