PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.36.0
MailPoet – Newsletters, Email Marketing, and Automation v5.36.0
5.36.0 5.35.1 5.35.0 5.34.3 5.34.2 5.34.1 5.34.0 5.33.1 5.33.0 5.32.0 5.31.0 5.30.0 5.29.0 5.28.1 5.28.0 5.27.0 5.26.0 5.26.1 5.25.0 5.24.0 4.43.0 4.43.1 4.44.0 4.44.1 4.45.0 4.46.0 4.47.0 4.48.0 4.48.1 4.48.2 4.49.0 4.49.1 4.5.0 4.5.1 4.5.2 4.50.0 4.50.1 4.51.0 4.51.1 4.51.2 4.52.0 4.53.0 4.54.0 4.55.0 4.56.0 4.57.0 4.58.0 4.58.1 4.58.2 4.6.0 4.6.1 4.6.2 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 5.0.0 5.0.1 5.0.2 5.1.0 5.1.1 5.10.0 5.10.1 5.11.0 5.12.0 5.12.1 5.12.10 5.12.11 5.12.12 5.12.13 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.12.7 5.12.8 5.12.9 5.13.0 5.13.1 5.13.2 5.14.0 5.14.1 5.14.2 5.14.3 5.15.0 5.15.1 5.16.0 5.16.1 5.16.2 5.16.3 5.16.4 5.17.0 5.17.1 5.17.2 5.17.3 5.17.4 5.17.5 5.17.6 5.18.0 5.19.0 5.2.0 5.2.1 5.2.2 5.2.3 5.20.0 5.21.0 5.21.1 5.21.2 5.21.3 5.22.0 5.22.1 5.22.2 5.22.3 5.22.4 5.23.0 5.23.1 5.23.2 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.6.3 5.6.4 5.7.0 5.7.1 5.8.0 5.8.1 5.9.0 3.0.0-beta.15 3.7.1 3.0.0-beta.16 3.7.2 3.0.0-beta.17 3.7.3 3.0.0-beta.18 3.7.4 3.0.0-beta.19 3.7.5 3.0.0-beta.2 3.7.6 3.0.0-beta.20 3.7.8 3.0.0-beta.21 3.70.0 3.0.0-beta.22 3.71.0 3.0.0-beta.23 3.71.1 3.0.0-beta.23.1 3.71.2 3.0.0-beta.23.2 3.71.3 3.0.0-beta.24 3.72.0 3.0.0-beta.25 3.73.0 3.0.0-beta.26 3.73.1 3.0.0-beta.27 3.73.2 3.0.0-beta.28 3.74.0 3.0.0-beta.29 3.74.1 3.0.0-beta.3 3.74.2 3.0.0-beta.30 3.74.3 3.0.0-beta.31 3.75.0 3.0.0-beta.32 3.75.1 3.0.0-beta.33 3.76.0 3.0.0-beta.33.1 3.77.0 3.0.0-beta.34.0.0 3.77.1 3.0.0-beta.36.0.0 3.78.0 3.0.0-beta.36.0.1 3.79.0 3.0.0-beta.36.2.0 3.8 3.0.0-beta.36.3.0 3.8.1 3.0.0-beta.36.3.1 3.8.2 3.0.0-beta.37.0.0 3.8.3 3.0.0-beta.4 3.8.4 3.0.0-beta.5 3.8.5 3.0.0-beta.6 3.8.6 3.0.0-beta.7 3.80.0 3.0.0-beta.7.1 3.81.0 3.0.0-beta.8 3.82.0 3.0.0-beta.9 3.83.0 3.0.0-rc.1.0.0 3.84.0 3.0.0-rc.1.0.1 3.84.1 3.0.0-rc.1.0.2 3.85.0 3.0.0-rc.1.0.3 3.85.1 3.0.0-rc.1.0.4 3.86.0 3.0.0-rc.2.0.0 3.87.0 3.0.0-rc.2.0.1 3.87.1 3.0.0-rc.2.0.2 3.87.2 3.0.0-rc.2.0.3 3.88.0 3.0.1 3.88.1 3.0.2 3.88.2 3.0.3 3.89.0 3.0.4 3.89.1 3.0.5 3.89.2 3.0.6 3.89.3 3.0.7 3.89.4 3.0.8 3.9.0 3.0.9 3.9.1 3.1.0 3.90.0 3.10 3.90.1 3.10.1 3.90.2 3.100.0 3.91.0 3.100.1 3.91.1 3.100.2 3.92.0 3.101.0 3.92.1 3.101.1 3.93.0 3.102.0 3.93.1 3.102.1 3.94.0 3.103.0 3.95.0 3.103.1 3.95.1 3.11.0 3.96.0 3.11.1 3.96.1 3.11.2 3.97.0 3.11.3 3.98.0 3.11.4 3.98.1 3.11.5 3.99.0 3.12.0 3.99.1 3.12.1 4.0.0 3.13.0 4.0.1 3.14.0 4.1.0 3.14.1 4.1.1 3.15.0 4.10.0 3.16.0 4.11.0 3.16.1 4.11.1 3.16.2 4.12.0 3.16.3 4.12.1 3.17.0 4.12.2 3.17.1 4.13.0 3.17.2 4.14.0 3.18.0 4.15.0 3.18.1 4.16.0 3.18.2 4.17.0 3.19.0 4.17.1 3.19.1 4.18.0 3.19.2 4.18.1 3.19.3 4.19.0 3.2.0 4.2.0 3.2.1 4.20.0 3.2.2 4.20.1 3.2.3 4.20.2 3.2.4 4.21.0 3.2.5 4.22.0 3.20.0 4.22.1 3.21.0 4.22.2 3.21.1 4.23.0 3.22.0 4.24.0 3.23.0 4.25.0 3.23.1 4.26.0 3.23.2 4.26.1 3.24.0 4.27.0 3.25.0 4.28.0 3.25.1 4.29.0 3.26.0 4.3.0 3.26.1 4.3.1 3.27.0 4.30.0 3.28.0 4.31.0 3.29.0 4.31.1 3.3.0 4.32.0 3.3.1 4.33.0 3.3.2 4.34.0 3.3.3 4.35.0 3.3.4 4.35.1 3.3.5 4.36.0 3.3.6 4.37.0 3.30.0 4.38.0 3.31.0 4.39.0 3.31.1 4.4.0 3.32.0 4.40.0 3.32.1 4.41.0 3.32.2 4.41.1 3.33.0 4.41.2 3.34.0 4.41.3 3.34.1 4.42.0 3.34.2 4.42.1 3.34.3 3.34.4 3.35.0 3.35.1 3.35.3 3.35.4 3.36.0 3.37.0 3.37.1 3.37.2 3.37.3 3.38.0 3.38.1 3.39.0 3.39.1 3.39.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.40.0 3.40.1 3.41.0 3.41.1 3.41.2 3.42.0 3.42.1 3.42.2 3.42.3 3.43.0 3.43.1 3.44.0 3.45.0 3.45.1 3.46.0 3.46.1 3.46.10 3.46.11 3.46.12 3.46.13 3.46.14 3.46.2 3.46.3 3.46.4 3.46.5 3.46.6 3.46.7 3.46.8 3.46.9 3.47.0 3.47.1 3.47.10 3.47.11 3.47.2 3.47.3 3.47.5 3.47.6 3.47.7 3.47.9 3.48.0 3.48.1 3.49.0 3.49.1 3.5.0 3.5.1 3.50.0 3.51.0 3.51.1 3.51.2 3.52.0 3.53.0 3.54.0 3.54.1 3.54.2 3.54.3 3.55.0 3.55.1 3.56.0 3.56.1 3.56.2 3.57.0 3.57.1 3.58.0 3.59.0 3.59.1 3.59.2 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.60.0 3.60.1 3.60.10 3.60.11 3.60.12 3.60.2 3.60.3 3.60.4 3.60.6 3.60.7 3.60.8 3.60.9 3.61.0 3.62.0 3.62.1 3.63.0 3.64.0 3.64.1 3.64.2 3.64.3 3.65.0 trunk 3.65.1 3.0.0 3.66.0 3.0.0-beta.1 3.67.0 3.0.0-beta.10 3.67.1 3.0.0-beta.11 3.68.0 3.0.0-beta.12 3.69.0 3.0.0-beta.13 3.69.1 3.0.0-beta.14 3.7.0
mailpoet / lib / Util / DataInconsistency / DataInconsistencyRepository.php
mailpoet / lib / Util / DataInconsistency Last commit date
DataInconsistencyController.php 1 day ago DataInconsistencyRepository.php 1 day ago index.php 2 years ago
DataInconsistencyRepository.php
331 lines
1 <?php declare(strict_types = 1);
2
3 namespace MailPoet\Util\DataInconsistency;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\Cron\Workers\SendingQueue\SendingQueue;
9 use MailPoet\Entities\CustomFieldEntity;
10 use MailPoet\Entities\NewsletterEntity;
11 use MailPoet\Entities\NewsletterLinkEntity;
12 use MailPoet\Entities\NewsletterPostEntity;
13 use MailPoet\Entities\ScheduledTaskEntity;
14 use MailPoet\Entities\ScheduledTaskSubscriberEntity;
15 use MailPoet\Entities\SegmentEntity;
16 use MailPoet\Entities\SendingQueueEntity;
17 use MailPoet\Entities\SubscriberCustomFieldEntity;
18 use MailPoet\Entities\SubscriberEntity;
19 use MailPoet\Entities\SubscriberSegmentEntity;
20 use MailPoet\Entities\SubscriberTagEntity;
21 use MailPoet\Entities\TagEntity;
22 use MailPoetVendor\Doctrine\DBAL\ArrayParameterType;
23 use MailPoetVendor\Doctrine\DBAL\ParameterType;
24 use MailPoetVendor\Doctrine\ORM\EntityManager;
25 use MailPoetVendor\Doctrine\ORM\Query;
26 use MailPoetVendor\Doctrine\ORM\QueryBuilder;
27
28 class DataInconsistencyRepository {
29 const DELETE_ROWS_LIMIT = 10000;
30
31 private EntityManager $entityManager;
32
33 public function __construct(
34 EntityManager $entityManager
35 ) {
36 $this->entityManager = $entityManager;
37 }
38
39 public function getOrphanedSendingTasksCount(): int {
40 $builder = $this->entityManager->createQueryBuilder()
41 ->select('count(st.id)');
42 return (int)$this->buildOrphanedSendingTasksQuery($builder)->getSingleScalarResult();
43 }
44
45 public function getOrphanedScheduledTasksSubscribersCount(): int {
46 $this->createOrphanedScheduledTaskSubscribersTemporaryTables();
47 $count = $this->getOrphanedScheduledTasksSubscribersCountFromTemporaryTables();
48 $this->dropOrphanedScheduledTaskSubscribersTemporaryTables();
49 return $count;
50 }
51
52 private function getOrphanedScheduledTasksSubscribersCountFromTemporaryTables(): int {
53 $connection = $this->entityManager->getConnection();
54 $stsTable = $this->entityManager->getClassMetadata(ScheduledTaskSubscriberEntity::class)->getTableName();
55 /** @var string $count */
56 $count = $connection->executeQuery("
57 SELECT COUNT(*) FROM $stsTable sts WHERE sts.task_id IN (SELECT task_id FROM orphaned_task_ids)
58 ")->fetchOne();
59 return intval($count);
60 }
61
62 /**
63 * The mirror image of getOrphanedSendingTasksCount(): sending queues whose scheduled task
64 * is gone. Such a queue is the only remaining record of how many emails it sent, so there
65 * is no safe cleanup for it — it is reported so support can recognise a damaged sending
66 * history behind statistics that no longer add up.
67 */
68 public function getSendingQueuesWithoutTaskCount(): int {
69 $sqTable = $this->entityManager->getClassMetadata(SendingQueueEntity::class)->getTableName();
70 $stTable = $this->entityManager->getClassMetadata(ScheduledTaskEntity::class)->getTableName();
71 /** @var string $count */
72 $count = $this->entityManager->getConnection()->executeQuery("
73 SELECT count(*) FROM $sqTable sq
74 LEFT JOIN $stTable st ON st.`id` = sq.`task_id`
75 WHERE st.`id` IS NULL
76 ")->fetchOne();
77 return intval($count);
78 }
79
80 public function getSendingQueuesWithoutNewsletterCount(): int {
81 $sqTable = $this->entityManager->getClassMetadata(SendingQueueEntity::class)->getTableName();
82 $newsletterTable = $this->entityManager->getClassMetadata(NewsletterEntity::class)->getTableName();
83 /** @var string $count */
84 $count = $this->entityManager->getConnection()->executeQuery("
85 SELECT count(*) FROM $sqTable sq
86 LEFT JOIN $newsletterTable n ON n.`id` = sq.`newsletter_id`
87 WHERE n.`id` IS NULL
88 ")->fetchOne();
89 return intval($count);
90 }
91
92 public function getOrphanedSubscriptionsCount(): int {
93 $subscriberTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
94 $segmentTable = $this->entityManager->getClassMetadata(SegmentEntity::class)->getTableName();
95 $subscriberSegmentTable = $this->entityManager->getClassMetadata(SubscriberSegmentEntity::class)->getTableName();
96 /** @var string $count */
97 $count = $this->entityManager->getConnection()->executeQuery("
98 SELECT count(distinct ss.`id`) FROM $subscriberSegmentTable ss
99 LEFT JOIN $segmentTable seg ON seg.`id` = ss.`segment_id`
100 LEFT JOIN $subscriberTable sub ON sub.`id` = ss.`subscriber_id`
101 WHERE seg.`id` IS NULL OR sub.`id` IS NULL
102 ")->fetchOne();
103 return intval($count);
104 }
105
106 public function getOrphanedSubscriberCustomFieldsCount(): int {
107 $subscriberTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
108 $customFieldTable = $this->entityManager->getClassMetadata(CustomFieldEntity::class)->getTableName();
109 $subscriberCustomFieldTable = $this->entityManager->getClassMetadata(SubscriberCustomFieldEntity::class)->getTableName();
110 /** @var string $count */
111 $count = $this->entityManager->getConnection()->executeQuery("
112 SELECT count(distinct scf.`id`) FROM $subscriberCustomFieldTable scf
113 LEFT JOIN $customFieldTable cf ON cf.`id` = scf.`custom_field_id`
114 LEFT JOIN $subscriberTable sub ON sub.`id` = scf.`subscriber_id`
115 WHERE cf.`id` IS NULL OR sub.`id` IS NULL
116 ")->fetchOne();
117 return intval($count);
118 }
119
120 public function getOrphanedSubscriberTagsCount(): int {
121 $subscriberTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
122 $tagTable = $this->entityManager->getClassMetadata(TagEntity::class)->getTableName();
123 $subscriberTagTable = $this->entityManager->getClassMetadata(SubscriberTagEntity::class)->getTableName();
124 /** @var string $count */
125 $count = $this->entityManager->getConnection()->executeQuery("
126 SELECT count(distinct st.`id`) FROM $subscriberTagTable st
127 LEFT JOIN $tagTable t ON t.`id` = st.`tag_id`
128 LEFT JOIN $subscriberTable sub ON sub.`id` = st.`subscriber_id`
129 WHERE t.`id` IS NULL OR sub.`id` IS NULL
130 ")->fetchOne();
131 return intval($count);
132 }
133
134 public function getOrphanedNewsletterLinksCount(): int {
135 $newsletterTable = $this->entityManager->getClassMetadata(NewsletterEntity::class)->getTableName();
136 $sendingQueueTable = $this->entityManager->getClassMetadata(SendingQueueEntity::class)->getTableName();
137 $newsletterLinkTable = $this->entityManager->getClassMetadata(NewsletterLinkEntity::class)->getTableName();
138 /** @var string $count */
139 $count = $this->entityManager->getConnection()->executeQuery("
140 SELECT count(distinct nl.`id`) FROM $newsletterLinkTable nl
141 LEFT JOIN $newsletterTable n ON n.`id` = nl.`newsletter_id`
142 LEFT JOIN $sendingQueueTable sq ON sq.`id` = nl.`queue_id`
143 WHERE n.`id` IS NULL OR sq.`id` IS NULL
144 ")->fetchOne();
145 return intval($count);
146 }
147
148 public function getOrphanedNewsletterPostsCount(): int {
149 $newsletterTable = $this->entityManager->getClassMetadata(NewsletterEntity::class)->getTableName();
150 $newsletterPostTable = $this->entityManager->getClassMetadata(NewsletterPostEntity::class)->getTableName();
151 /** @var string $count */
152 $count = $this->entityManager->getConnection()->executeQuery("
153 SELECT count(distinct np.`id`) FROM $newsletterPostTable np
154 LEFT JOIN $newsletterTable n ON n.`id` = np.`newsletter_id`
155 WHERE n.`id` IS NULL
156 ")->fetchOne();
157 return intval($count);
158 }
159
160 public function cleanupOrphanedSendingTasks(): int {
161 /** @var array<int, array{id: string}> $ids */
162 $ids = $this->buildOrphanedSendingTasksQuery(
163 $this->entityManager->createQueryBuilder()
164 ->select('st.id')
165 )->getResult();
166
167 if (!$ids) {
168 return 0;
169 }
170 $ids = array_column($ids, 'id');
171 // delete the orphaned tasks
172 $qb = $this->entityManager->createQueryBuilder();
173 $countDeletedTasks = $qb->delete(ScheduledTaskEntity::class, 'st')
174 ->where($qb->expr()->in('st.id', ':ids'))
175 ->setParameter('ids', $ids)
176 ->getQuery()
177 ->execute();
178
179 // delete the scheduled tasks subscribers
180 $stsTable = $this->entityManager->getClassMetadata(ScheduledTaskSubscriberEntity::class)->getTableName();
181 $this->entityManager->getConnection()->executeStatement(
182 "DELETE sts_top FROM $stsTable sts_top
183 JOIN (
184 SELECT sts.`task_id`, sts.`subscriber_id` FROM $stsTable sts
185 WHERE `task_id` IN (:ids)
186 LIMIT :limit
187 ) as to_delete ON sts_top.`task_id` = to_delete.`task_id` AND sts_top.`subscriber_id` = to_delete.`subscriber_id`",
188 ['limit' => self::DELETE_ROWS_LIMIT, 'ids' => $ids],
189 ['limit' => ParameterType::INTEGER, 'ids' => ArrayParameterType::INTEGER]
190 );
191
192
193 $qb = $this->entityManager->createQueryBuilder();
194 $qb->delete(ScheduledTaskSubscriberEntity::class, 'sts')
195 ->where($qb->expr()->in('sts.task', ':ids'))
196 ->setParameter('ids', $ids)
197 ->getQuery()
198 ->execute();
199
200 return $countDeletedTasks;
201 }
202
203 public function cleanupOrphanedScheduledTaskSubscribers(): int {
204 $stsTable = $this->entityManager->getClassMetadata(ScheduledTaskSubscriberEntity::class)->getTableName();
205 $deletedCount = 0;
206
207 $this->createOrphanedScheduledTaskSubscribersTemporaryTables();
208 do {
209 $deletedCount += (int)$this->entityManager->getConnection()->executeStatement(
210 "
211 DELETE sts_top FROM $stsTable sts_top
212 JOIN (
213 SELECT task_id, subscriber_id
214 FROM $stsTable
215 WHERE task_id IN (SELECT task_id FROM orphaned_task_ids)
216 LIMIT :limit
217 ) AS to_delete ON sts_top.task_id = to_delete.task_id AND sts_top.subscriber_id = to_delete.subscriber_id
218 ",
219 ['limit' => self::DELETE_ROWS_LIMIT],
220 ['limit' => ParameterType::INTEGER]
221 );
222 } while ($this->getOrphanedScheduledTasksSubscribersCountFromTemporaryTables() > 0);
223 $this->dropOrphanedScheduledTaskSubscribersTemporaryTables();
224 return $deletedCount;
225 }
226
227 public function cleanupSendingQueuesWithoutNewsletter(): int {
228 $sqTable = $this->entityManager->getClassMetadata(SendingQueueEntity::class)->getTableName();
229 $newsletterTable = $this->entityManager->getClassMetadata(NewsletterEntity::class)->getTableName();
230 $deletedQueuesCount = (int)$this->entityManager->getConnection()->executeStatement("
231 DELETE sq FROM $sqTable sq
232 LEFT JOIN $newsletterTable n ON n.`id` = sq.`newsletter_id`
233 WHERE n.`id` IS NULL
234 ");
235
236 $this->cleanupOrphanedSendingTasks();
237 return $deletedQueuesCount;
238 }
239
240 public function cleanupOrphanedSubscriptions(): int {
241 $subscriberTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
242 $segmentTable = $this->entityManager->getClassMetadata(SegmentEntity::class)->getTableName();
243 $subscriberSegmentTable = $this->entityManager->getClassMetadata(SubscriberSegmentEntity::class)->getTableName();
244 return (int)$this->entityManager->getConnection()->executeStatement("
245 DELETE ss FROM $subscriberSegmentTable ss
246 LEFT JOIN $segmentTable seg ON seg.`id` = ss.`segment_id`
247 LEFT JOIN $subscriberTable sub ON sub.`id` = ss.`subscriber_id`
248 WHERE seg.`id` IS NULL OR sub.`id` IS NULL
249 ");
250 }
251
252 public function cleanupOrphanedSubscriberCustomFields(): int {
253 $subscriberTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
254 $customFieldTable = $this->entityManager->getClassMetadata(CustomFieldEntity::class)->getTableName();
255 $subscriberCustomFieldTable = $this->entityManager->getClassMetadata(SubscriberCustomFieldEntity::class)->getTableName();
256 return (int)$this->entityManager->getConnection()->executeStatement("
257 DELETE scf FROM $subscriberCustomFieldTable scf
258 LEFT JOIN $customFieldTable cf ON cf.`id` = scf.`custom_field_id`
259 LEFT JOIN $subscriberTable sub ON sub.`id` = scf.`subscriber_id`
260 WHERE cf.`id` IS NULL OR sub.`id` IS NULL
261 ");
262 }
263
264 public function cleanupOrphanedSubscriberTags(): int {
265 $subscriberTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
266 $tagTable = $this->entityManager->getClassMetadata(TagEntity::class)->getTableName();
267 $subscriberTagTable = $this->entityManager->getClassMetadata(SubscriberTagEntity::class)->getTableName();
268 return (int)$this->entityManager->getConnection()->executeStatement("
269 DELETE st FROM $subscriberTagTable st
270 LEFT JOIN $tagTable t ON t.`id` = st.`tag_id`
271 LEFT JOIN $subscriberTable sub ON sub.`id` = st.`subscriber_id`
272 WHERE t.`id` IS NULL OR sub.`id` IS NULL
273 ");
274 }
275
276 public function cleanupOrphanedNewsletterLinks(): int {
277 $newsletterTable = $this->entityManager->getClassMetadata(NewsletterEntity::class)->getTableName();
278 $sendingQueueTable = $this->entityManager->getClassMetadata(SendingQueueEntity::class)->getTableName();
279 $newsletterLinkTable = $this->entityManager->getClassMetadata(NewsletterLinkEntity::class)->getTableName();
280 return (int)$this->entityManager->getConnection()->executeStatement("
281 DELETE nl FROM $newsletterLinkTable nl
282 LEFT JOIN $newsletterTable n ON n.`id` = nl.`newsletter_id`
283 LEFT JOIN $sendingQueueTable sq ON sq.`id` = nl.`queue_id`
284 WHERE n.`id` IS NULL OR sq.`id` IS NULL
285 ");
286 }
287
288 public function cleanupOrphanedNewsletterPosts(): int {
289 $newsletterTable = $this->entityManager->getClassMetadata(NewsletterEntity::class)->getTableName();
290 $newsletterPostTable = $this->entityManager->getClassMetadata(NewsletterPostEntity::class)->getTableName();
291 return (int)$this->entityManager->getConnection()->executeStatement("
292 DELETE np FROM $newsletterPostTable np
293 LEFT JOIN $newsletterTable n ON n.`id` = np.`newsletter_id`
294 WHERE n.`id` IS NULL
295 ");
296 }
297
298 private function buildOrphanedSendingTasksQuery(QueryBuilder $queryBuilder): Query {
299 return $queryBuilder
300 ->from(ScheduledTaskEntity::class, 'st')
301 ->leftJoin('st.sendingQueue', 'sq')
302 ->where('sq.id IS NULL')
303 ->andWhere('st.type = :type')
304 ->setParameter('type', SendingQueue::TASK_TYPE)
305 ->getQuery();
306 }
307
308 private function createOrphanedScheduledTaskSubscribersTemporaryTables(): void {
309 $connection = $this->entityManager->getConnection();
310 $stTable = $this->entityManager->getClassMetadata(ScheduledTaskEntity::class)->getTableName();
311 $stsTable = $this->entityManager->getClassMetadata(ScheduledTaskSubscriberEntity::class)->getTableName();
312
313 // 1. Get the DISTINCT task IDs so that the subsequent JOIN is more efficient.
314 $connection->executeStatement("
315 CREATE TEMPORARY TABLE IF NOT EXISTS task_ids
316 SELECT DISTINCT task_id FROM $stsTable
317 ");
318
319 // 2. Get the orphaned task IDs.
320 $connection->executeStatement("
321 CREATE TEMPORARY TABLE IF NOT EXISTS orphaned_task_ids
322 SELECT task_id FROM task_ids LEFT JOIN $stTable st ON st.id = task_ids.task_id WHERE st.id IS NULL
323 ");
324 }
325
326 private function dropOrphanedScheduledTaskSubscribersTemporaryTables(): void {
327 $this->entityManager->getConnection()->executeStatement("DROP TEMPORARY TABLE IF EXISTS task_ids");
328 $this->entityManager->getConnection()->executeStatement("DROP TEMPORARY TABLE IF EXISTS orphaned_task_ids");
329 }
330 }
331