diff --git a/mailpoet/lib/EmailEditor/Integrations/MailPoet/EmailApiController.php b/mailpoet/lib/EmailEditor/Integrations/MailPoet/EmailApiController.php index d5ec411207..081d0a765c 100644 --- a/mailpoet/lib/EmailEditor/Integrations/MailPoet/EmailApiController.php +++ b/mailpoet/lib/EmailEditor/Integrations/MailPoet/EmailApiController.php @@ -28,7 +28,7 @@ class EmailApiController { * @return array - MailPoet specific email data that will be attached to the post API response */ public function getEmailData($postEmailData): array { - $newsletter = $this->newsletterRepository->findOneBy(['wpPostId' => $postEmailData['id']]); + $newsletter = $this->newsletterRepository->findOneBy(['wpPost' => $postEmailData['id']]); return [ 'id' => $newsletter ? $newsletter->getId() : null, 'subject' => $newsletter ? $newsletter->getSubject() : '', diff --git a/mailpoet/lib/EmailEditor/Integrations/MailPoet/EmailEditor.php b/mailpoet/lib/EmailEditor/Integrations/MailPoet/EmailEditor.php index 4f2753d609..14bf440af1 100644 --- a/mailpoet/lib/EmailEditor/Integrations/MailPoet/EmailEditor.php +++ b/mailpoet/lib/EmailEditor/Integrations/MailPoet/EmailEditor.php @@ -3,10 +3,12 @@ namespace MailPoet\EmailEditor\Integrations\MailPoet; use MailPoet\Entities\NewsletterEntity; +use MailPoet\Entities\WpPostEntity; use MailPoet\Features\FeaturesController; use MailPoet\Newsletter\NewslettersRepository; use MailPoet\Util\Security; use MailPoet\WP\Functions as WPFunctions; +use MailPoetVendor\Doctrine\ORM\EntityManager; class EmailEditor { const MAILPOET_EMAIL_POST_TYPE = 'mailpoet_email'; @@ -23,16 +25,21 @@ class EmailEditor { /** @var EmailApiController */ private $emailApiController; + /** @var EntityManager */ + private $entityManager; + public function __construct( WPFunctions $wp, FeaturesController $featuresController, NewslettersRepository $newsletterRepository, - EmailApiController $emailApiController + EmailApiController $emailApiController, + EntityManager $entityManager ) { $this->wp = $wp; $this->featuresController = $featuresController; $this->newsletterRepository = $newsletterRepository; $this->emailApiController = $emailApiController; + $this->entityManager = $entityManager; } public function initialize(): void { @@ -66,13 +73,13 @@ class EmailEditor { if ($post->post_type !== self::MAILPOET_EMAIL_POST_TYPE) { // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps return; } - $newsletter = $this->newsletterRepository->findOneBy(['wpPostId' => $postId]); + $newsletter = $this->newsletterRepository->findOneBy(['wpPost' => $postId]); if ($newsletter) { return; } $newsletter = new NewsletterEntity(); - $newsletter->setWpPostId($postId); - $newsletter->setSubject('New Editor Email ' . $postId); + $newsletter->setWpPost($this->entityManager->getReference(WpPostEntity::class, $postId)); + $newsletter->setSubject(__('Subject', 'mailpoet')); $newsletter->setType(NewsletterEntity::TYPE_STANDARD); // We allow only standard emails in the new editor for now $newsletter->setHash(Security::generateHash()); $this->newsletterRepository->persist($newsletter); diff --git a/mailpoet/lib/Entities/NewsletterEntity.php b/mailpoet/lib/Entities/NewsletterEntity.php index 9177a591e5..43228e8678 100644 --- a/mailpoet/lib/Entities/NewsletterEntity.php +++ b/mailpoet/lib/Entities/NewsletterEntity.php @@ -113,12 +113,6 @@ class NewsletterEntity { */ private $preheader = ''; - /** - * @ORM\Column(type="integer", nullable=true) - * @var int|null - */ - private $wpPostId; - /** * @ORM\Column(type="json", nullable=true) * @var array|null @@ -173,6 +167,13 @@ class NewsletterEntity { */ private $queues; + /** + * @ORM\OneToOne(targetEntity="MailPoet\Entities\WpPostEntity") + * @ORM\JoinColumn(name="wp_post_id", referencedColumnName="ID", nullable=true) + * @var WpPostEntity|null + */ + private $wpPost; + public function __construct() { $this->children = new ArrayCollection(); $this->newsletterSegments = new ArrayCollection(); @@ -278,14 +279,6 @@ class NewsletterEntity { return $this->status; } - public function getWpPostId(): ?int { - return $this->wpPostId; - } - - public function setWpPostId(?int $wpPostId): void { - $this->wpPostId = $wpPostId; - } - /** * @param string $status */ @@ -573,23 +566,25 @@ class NewsletterEntity { return in_array($this->getType(), [self::TYPE_NOTIFICATION_HISTORY, self::TYPE_STANDARD], true); } - /** - * We don't use typehint for now because doctrine cache generator would fail as it doesn't know the class. - * @return \WP_Post|null - */ - public function getWpPost() { - if ($this->wpPostId === null) { - return null; - } - $post = \WP_Post::get_instance($this->wpPostId); - return $post ?: null; + public function getWpPost(): ?WpPostEntity { + $this->safelyLoadToOneAssociation('wpPost'); + return $this->wpPost; + } + + public function setWpPost(?WpPostEntity $wpPostEntity): void { + $this->wpPost = $wpPostEntity; + } + + public function getWpPostId(): ?int { + $wpPost = $this->wpPost; + return $wpPost ? $wpPost->getId() : null; } public function getCampaignName(): ?string { - $post = $this->getWpPost(); - if (!$post) { + $wpPost = $this->getWpPost(); + if (!$wpPost) { return null; } - return $post->post_title; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps + return $wpPost->getPostTitle(); } } diff --git a/mailpoet/lib/Entities/WpPostEntity.php b/mailpoet/lib/Entities/WpPostEntity.php new file mode 100644 index 0000000000..16ca95acdb --- /dev/null +++ b/mailpoet/lib/Entities/WpPostEntity.php @@ -0,0 +1,50 @@ +id = $id; + $this->$postTitle = $postTitle; + } + + public function getId(): int { + return $this->id; + } + + public function getPostTitle(): string { + return $this->postTitle; + } + + /** + * We don't use typehint for now because doctrine cache generator would fail as it doesn't know the class. + * @return \WP_Post|null + */ + public function getWpPostInstance() { + $post = \WP_Post::get_instance($this->id); + return $post ?: null; + } +} diff --git a/mailpoet/lib/Newsletter/NewsletterSaveController.php b/mailpoet/lib/Newsletter/NewsletterSaveController.php index 6c2f246803..dfd983dad6 100644 --- a/mailpoet/lib/Newsletter/NewsletterSaveController.php +++ b/mailpoet/lib/Newsletter/NewsletterSaveController.php @@ -9,6 +9,7 @@ use MailPoet\Entities\NewsletterOptionFieldEntity; use MailPoet\Entities\NewsletterSegmentEntity; use MailPoet\Entities\ScheduledTaskEntity; use MailPoet\Entities\SegmentEntity; +use MailPoet\Entities\WpPostEntity; use MailPoet\InvalidStateException; use MailPoet\Models\Newsletter; use MailPoet\Newsletter\Options\NewsletterOptionFieldsRepository; @@ -201,8 +202,10 @@ class NewsletterSaveController { $newPostId = $this->wp->wpInsertPost([ 'post_content' => $post->post_content, // @phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps 'post_type' => $post->post_type, // @phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps + // translators: %s is the campaign name of the mail which has been copied. + 'post_title' => sprintf(__('Copy of %s', 'mailpoet'), $post->post_title), // @phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps ]); - $duplicate->setWpPostId($newPostId); + $duplicate->setWpPost($this->entityManager->getReference(WpPostEntity::class, $newPostId)); } // create relationships between duplicate and segments diff --git a/mailpoet/lib/Newsletter/NewslettersRepository.php b/mailpoet/lib/Newsletter/NewslettersRepository.php index ff4aa550f3..9867c6d9b3 100644 --- a/mailpoet/lib/Newsletter/NewslettersRepository.php +++ b/mailpoet/lib/Newsletter/NewslettersRepository.php @@ -468,10 +468,10 @@ class NewslettersRepository extends Repository { // Fetch WP Posts IDs and delete them /** @var int[] $wpPostsIds */ - $wpPostsIds = $entityManager->createQueryBuilder()->select('n.wpPostId') + $wpPostsIds = $entityManager->createQueryBuilder()->select('wpp.id') ->from(NewsletterEntity::class, 'n') + ->join('n.wpPost', 'wpp') ->where('n.id IN (:ids)') - ->andWhere('n.wpPostId IS NOT NULL') ->setParameter('ids', $ids) ->getQuery()->getSingleColumnResult(); foreach ($wpPostsIds as $wpPostId) { diff --git a/mailpoet/lib/Newsletter/Renderer/Renderer.php b/mailpoet/lib/Newsletter/Renderer/Renderer.php index 6ab46fd659..b92e625870 100644 --- a/mailpoet/lib/Newsletter/Renderer/Renderer.php +++ b/mailpoet/lib/Newsletter/Renderer/Renderer.php @@ -87,7 +87,8 @@ class Renderer { $language = $this->wp->getBloginfo('language'); $metaRobots = $preview ? '' : ''; $subject = $subject ?: $newsletter->getSubject(); - $wpPost = $newsletter->getWpPost(); + $wpPostEntity = $newsletter->getWpPost(); + $wpPost = $wpPostEntity ? $wpPostEntity->getWpPostInstance() : null; if ($this->featuresController->isSupported(FeaturesController::GUTENBERG_EMAIL_EDITOR) && $wpPost instanceof \WP_Post) { $renderedNewsletter = $this->guntenbergRenderer->render($wpPost, $subject, $newsletter->getPreheader(), $language, $metaRobots); } else { diff --git a/mailpoet/tests/DataFactories/Newsletter.php b/mailpoet/tests/DataFactories/Newsletter.php index 61e065ad21..5f94c7b653 100644 --- a/mailpoet/tests/DataFactories/Newsletter.php +++ b/mailpoet/tests/DataFactories/Newsletter.php @@ -12,6 +12,7 @@ use MailPoet\Entities\ScheduledTaskSubscriberEntity; use MailPoet\Entities\SegmentEntity; use MailPoet\Entities\SendingQueueEntity; use MailPoet\Entities\SubscriberEntity; +use MailPoet\Entities\WpPostEntity; use MailPoet\Util\Security; use MailPoetVendor\Carbon\Carbon; use MailPoetVendor\Doctrine\ORM\EntityManager; @@ -420,7 +421,10 @@ class Newsletter { if (isset($this->data['parent'])) $newsletter->setParent($this->data['parent']); if (isset($this->data['deleted_at'])) $newsletter->setDeletedAt($this->data['deleted_at']); if (isset($this->data['ga_campaign'])) $newsletter->setGaCampaign($this->data['ga_campaign']); - if (isset($this->data['wp_post_id'])) $newsletter->setWpPostId($this->data['wp_post_id']); + $entityManager = ContainerWrapper::getInstance()->get(EntityManager::class); + if (isset($this->data['wp_post_id'])) { + $newsletter->setWpPost($entityManager->getReference(WpPostEntity::class, intval($this->data['wp_post_id']))); + } if (isset($this->data['unsubscribeToken'])) { $newsletter->setUnsubscribeToken($this->data['unsubscribeToken']); diff --git a/mailpoet/tests/integration/Newsletter/NewsletterRepositoryTest.php b/mailpoet/tests/integration/Newsletter/NewsletterRepositoryTest.php index ef22e07512..4a2fdacf2c 100644 --- a/mailpoet/tests/integration/Newsletter/NewsletterRepositoryTest.php +++ b/mailpoet/tests/integration/Newsletter/NewsletterRepositoryTest.php @@ -20,6 +20,7 @@ use MailPoet\Entities\StatisticsOpenEntity; use MailPoet\Entities\StatisticsWooCommercePurchaseEntity; use MailPoet\Entities\StatsNotificationEntity; use MailPoet\Entities\SubscriberEntity; +use MailPoet\Entities\WpPostEntity; use MailPoet\Newsletter\Sending\ScheduledTaskSubscribersRepository; use MailPoet\Tasks\Sending as SendingTask; use MailPoet\Test\DataFactories\NewsletterOptionField; @@ -248,12 +249,14 @@ class NewsletterRepositoryTest extends \MailPoetTest { public function testItDeletesWpPostsBulkDelete() { $newsletter1 = $this->createNewsletter(NewsletterEntity::TYPE_STANDARD, NewsletterEntity::STATUS_SENDING); $post1Id = $this->wp->wpInsertPost(['post_title' => 'Post 1']); - $newsletter1->setWpPostId($post1Id); + $newsletter1->setWpPost($this->entityManager->getReference(WpPostEntity::class, $post1Id)); $newsletter2 = $this->createNewsletter(NewsletterEntity::TYPE_WELCOME, NewsletterEntity::STATUS_SENDING); $post2Id = $this->wp->wpInsertPost(['post_title' => 'Post 2']); - $newsletter2->setWpPostId($post2Id); + $newsletter2->setWpPost($this->entityManager->getReference(WpPostEntity::class, $post2Id)); $newsletter3 = $this->createNewsletter(NewsletterEntity::TYPE_STANDARD, NewsletterEntity::STATUS_SENDING); + $blogPost = $this->wp->wpInsertPost(['post_title' => 'Regular blog post']); + verify($this->wp->getPost($post1Id))->instanceOf(\WP_Post::class); verify($this->wp->getPost($post2Id))->instanceOf(\WP_Post::class); @@ -263,6 +266,7 @@ class NewsletterRepositoryTest extends \MailPoetTest { $this->repository->bulkDelete([$newsletter1->getId(), $newsletter2->getId(), $newsletter3->getId()]); verify($this->wp->getPost($post1Id))->null(); verify($this->wp->getPost($post2Id))->null(); + verify($this->wp->getPost($blogPost))->instanceOf(\WP_Post::class); } public function testItGetsArchiveNewslettersForSegments() { diff --git a/mailpoet/tests/integration/Newsletter/NewsletterSaveControllerTest.php b/mailpoet/tests/integration/Newsletter/NewsletterSaveControllerTest.php index d13dfdfacc..7427867eff 100644 --- a/mailpoet/tests/integration/Newsletter/NewsletterSaveControllerTest.php +++ b/mailpoet/tests/integration/Newsletter/NewsletterSaveControllerTest.php @@ -10,6 +10,7 @@ use MailPoet\Entities\NewsletterSegmentEntity; use MailPoet\Entities\ScheduledTaskEntity; use MailPoet\Entities\SegmentEntity; use MailPoet\Entities\SendingQueueEntity; +use MailPoet\Entities\WpPostEntity; use MailPoet\Newsletter\Scheduler\PostNotificationScheduler; use MailPoet\Newsletter\Scheduler\Scheduler; use MailPoet\Newsletter\Sending\SendingQueuesRepository; @@ -336,12 +337,13 @@ class NewsletterSaveControllerTest extends \MailPoetTest { public function testItDuplicatesNewsletterWithAssociatedPost() { $newsletter = $this->createNewsletter(NewsletterEntity::TYPE_STANDARD, NewsletterEntity::STATUS_SENT); $wp = $this->diContainer->get(WPFunctions::class); - $postId = $wp->wpInsertPost(['post_content' => 'newsletter content']); - $newsletter->setWpPostId($postId); + $postId = $wp->wpInsertPost(['post_content' => 'newsletter content', 'post_title' => 'Newsletter Title']); + $newsletter->setWpPost($this->entityManager->getReference(WpPostEntity::class, $postId)); $this->entityManager->flush(); $duplicate = $this->saveController->duplicate($newsletter); verify($duplicate->getWpPostId())->notEquals($postId); $post = $wp->getPost($duplicate->getWpPostId()); + verify($duplicate->getCampaignName())->equals('Copy of Newsletter Title'); verify($post->post_content)->equals('newsletter content'); // @phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps }