PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / trunk
MailPoet – Newsletters, Email Marketing, and Automation vtrunk
5.37.0 5.36.1 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 / Subscribers / ImportExport / ImportExportRepository.php
mailpoet / lib / Subscribers / ImportExport Last commit date
Export 3 months ago Import 14 hours ago PersonalDataExporters 3 months ago ImportExportFactory.php 14 hours ago ImportExportRepository.php 14 hours ago index.php 3 years ago
ImportExportRepository.php
401 lines
1 <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
2
3 namespace MailPoet\Subscribers\ImportExport;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use DateTime;
9 use MailPoet\Config\SubscriberChangesNotifier;
10 use MailPoet\Entities\CustomFieldEntity;
11 use MailPoet\Entities\SegmentEntity;
12 use MailPoet\Entities\SubscriberCustomFieldEntity;
13 use MailPoet\Entities\SubscriberEntity;
14 use MailPoet\Entities\SubscriberSegmentEntity;
15 use MailPoet\Segments\DynamicSegments\FilterHandler;
16 use MailPoet\Subscribers\SubscriberCustomFieldRepository;
17 use MailPoet\Subscribers\SubscribersRepository;
18 use MailPoetVendor\Doctrine\DBAL\ArrayParameterType;
19 use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;
20 use MailPoetVendor\Doctrine\DBAL\Result;
21 use MailPoetVendor\Doctrine\ORM\EntityManager;
22 use MailPoetVendor\Doctrine\ORM\Mapping\ClassMetadata;
23
24 class ImportExportRepository {
25 private const IGNORED_COLUMNS_FOR_BULK_UPDATE = [
26 SubscriberEntity::class => [
27 'wp_user_id',
28 'is_woocommerce_user',
29 'email',
30 'created_at',
31 'last_subscribed_at',
32 ],
33 SubscriberCustomFieldEntity::class => [
34 'created_at',
35 ],
36 SubscriberSegmentEntity::class => [
37 'created_at',
38 ],
39 ];
40
41 private const KEY_COLUMNS_FOR_BULK_UPDATE = [
42 SubscriberEntity::class => [
43 'email',
44 ],
45 SubscriberCustomFieldEntity::class => [
46 'subscriber_id',
47 'custom_field_id',
48 ],
49 ];
50
51 /** @var EntityManager */
52 protected $entityManager;
53
54 /** @var SubscriberChangesNotifier */
55 private $subscriberChangesNotifier;
56
57 /** @var FilterHandler */
58 private $filterHandler;
59
60 /** @var SubscribersRepository */
61 private $subscribersRepository;
62
63 /** @var SubscriberCustomFieldRepository */
64 private $subscriberCustomFieldRepository;
65
66 public function __construct(
67 EntityManager $entityManager,
68 SubscriberChangesNotifier $changesNotifier,
69 FilterHandler $filterHandler,
70 SubscribersRepository $subscribersRepository,
71 SubscriberCustomFieldRepository $subscriberCustomFieldRepository
72 ) {
73 $this->entityManager = $entityManager;
74 $this->subscriberChangesNotifier = $changesNotifier;
75 $this->filterHandler = $filterHandler;
76 $this->subscribersRepository = $subscribersRepository;
77 $this->subscriberCustomFieldRepository = $subscriberCustomFieldRepository;
78 }
79
80 /**
81 * @param class-string<object> $className
82 * @return ClassMetadata<object>
83 */
84 protected function getClassMetadata(string $className): ClassMetadata {
85 return $this->entityManager->getClassMetadata($className);
86 }
87
88 /**
89 * @param class-string<object> $className
90 */
91 protected function getTableName(string $className): string {
92 return $this->getClassMetadata($className)->getTableName();
93 }
94
95 /**
96 * @param class-string<object> $className
97 */
98 protected function getTableColumns(string $className): array {
99 return $this->getClassMetadata($className)->getColumnNames();
100 }
101
102 /**
103 * @param class-string<object> $className
104 */
105 public function insertMultiple(
106 string $className,
107 array $columns,
108 array $data
109 ): int {
110 $tableName = $this->getTableName($className);
111
112 if (!$columns || !$data) {
113 return 0;
114 }
115
116 $rows = [];
117 $parameters = [];
118 foreach ($data as $key => $item) {
119 $paramNames = array_map(function (string $parameter) use ($key): string {
120 return ":{$parameter}_{$key}";
121 }, $columns);
122
123 foreach ($item as $columnKey => $column) {
124 // We need to remove the colon character from the query parameter name that is passed to the query builder
125 $parameters[substr($paramNames[$columnKey], 1)] = $column;
126 }
127 $rows[] = "(" . implode(', ', $paramNames) . ")";
128 }
129
130 $count = (int)$this->entityManager->getConnection()->executeStatement("
131 INSERT IGNORE INTO {$tableName} (`" . implode("`, `", $columns) . "`) VALUES
132 " . implode(", \n", $rows) . "
133 ", $parameters);
134 $this->notifyCreations($className, $columns, $data);
135 return $count;
136 }
137
138 /**
139 * @param class-string<object> $className
140 */
141 public function updateMultiple(
142 string $className,
143 array $columns,
144 array $data,
145 ?DateTime $updatedAt = null
146 ): int {
147 $tableName = $this->getTableName($className);
148 $entityColumns = $this->getTableColumns($className);
149
150 if (!$columns || !$data) {
151 return 0;
152 }
153
154 $parameters = [];
155 $parameterTypes = [];
156 $keyColumns = self::KEY_COLUMNS_FOR_BULK_UPDATE[$className] ?? [];
157 if (!$keyColumns) {
158 return 0;
159 }
160
161 $keyColumnsConditions = [];
162 foreach ($keyColumns as $keyColumn) {
163 $columnIndex = array_search($keyColumn, $columns);
164 $parameters[$keyColumn] = array_map(function(array $row) use ($columnIndex) {
165 return $row[$columnIndex];
166 }, $data);
167 $parameterTypes[$keyColumn] = ArrayParameterType::STRING;
168 $keyColumnsConditions[] = "{$keyColumn} IN (:{$keyColumn})";
169 }
170
171 $restoredSubscriberIds = $className === SubscriberEntity::class
172 ? $this->getDeletedSubscriberIdsByEmail($columns, $data)
173 : [];
174
175 $ignoredColumns = self::IGNORED_COLUMNS_FOR_BULK_UPDATE[$className] ?? ['created_at'];
176 $updateColumns = array_map(function($columnName) use ($keyColumns, $columns, $data, &$parameters): string {
177 $values = [];
178 foreach ($data as $index => $row) {
179 $keyCondition = array_map(function($keyColumn) use ($index, $row, $columns, &$parameters): string {
180 $parameters["{$keyColumn}_{$index}"] = $row[array_search($keyColumn, $columns)];
181 return "{$keyColumn} = :{$keyColumn}_{$index}";
182 }, $keyColumns);
183 $values[] = "WHEN " . implode(' AND ', $keyCondition) . " THEN :{$columnName}_{$index}";
184 $parameters["{$columnName}_{$index}"] = $row[array_search($columnName, $columns)];
185 }
186 return "{$columnName} = (CASE " . implode("\n", $values) . " END)";
187 }, array_diff($columns, $ignoredColumns));
188
189 if ($updatedAt && in_array('updated_at', $entityColumns, true)) {
190 $parameters['updated_at'] = $updatedAt;
191 $updateColumns[] = "updated_at = :updated_at";
192 }
193
194 // we want to reset deleted_at for updated rows
195 if (in_array('deleted_at', $entityColumns, true)) {
196 $updateColumns[] = 'deleted_at = NULL';
197 }
198
199 $count = (int)$this->entityManager->getConnection()->executeStatement("
200 UPDATE {$tableName} SET
201 " . implode(", \n", $updateColumns) . "
202 WHERE
203 " . implode(' AND ', $keyColumnsConditions) . "
204 ", $parameters, $parameterTypes);
205 $updatedSubscriberIds = $this->notifyUpdates($className, $columns, $data);
206 $countChangedSubscriberIds = $this->getCountChangedSubscriberIdsForBulkUpdate($className, $columns, $updatedSubscriberIds, $restoredSubscriberIds);
207 if ($countChangedSubscriberIds) {
208 $this->subscriberChangesNotifier->subscribersCountChanged($countChangedSubscriberIds);
209 }
210 if ($className === SubscriberEntity::class) {
211 $this->subscribersRepository->refreshAll();
212 }
213 if ($className === SubscriberCustomFieldEntity::class) {
214 $this->subscriberCustomFieldRepository->refreshAll();
215 }
216 return $count;
217 }
218
219 public function getSubscribersBatchBySegment(?SegmentEntity $segment, int $limit, int $offset = 0): array {
220 $subscriberSegmentTable = $this->getTableName(SubscriberSegmentEntity::class);
221 $subscriberTable = $this->getTableName(SubscriberEntity::class);
222 $segmentTable = $this->getTableName(SegmentEntity::class);
223
224 $qb = $this->createSubscribersQueryBuilder($limit, $offset);
225 $qb = $this->addSubscriberCustomFieldsToQueryBuilder($qb);
226
227 if (!$segment || $segment->isStatic()) {
228 // joining with the segments table is used only when there is no segment or for static segments.
229 // this because dynamic segments don't have a corresponding entry in the segments table.
230 $qb->leftJoin($subscriberSegmentTable, $segmentTable, $segmentTable, "{$segmentTable}.id = {$subscriberSegmentTable}.segment_id")
231 ->groupBy("{$subscriberTable}.id, {$segmentTable}.id");
232 }
233
234 if (!$segment) {
235 // if there are subscribers who do not belong to any segment, use
236 // a CASE function to group them under "Not In Segment"
237 $qb->addSelect("'" . __('Not In Segment', 'mailpoet') . "' AS segment_name")
238 ->leftJoin($subscriberTable, $subscriberTable, 's2', "{$subscriberTable}.id = s2.id")
239 ->leftJoin('s2', $subscriberSegmentTable, 'ssg2', "s2.id = ssg2.subscriber_id AND ssg2.status = :statusSubscribed AND {$segmentTable}.id <> ssg2.segment_id")
240 ->leftJoin('ssg2', $segmentTable, 'sg2', 'ssg2.segment_id = sg2.id AND sg2.deleted_at IS NULL')
241 ->andWhere("({$subscriberSegmentTable}.status != :statusSubscribed OR {$subscriberSegmentTable}.id IS NULL OR {$segmentTable}.deleted_at IS NOT NULL)")
242 ->andWhere('sg2.id IS NULL')
243 ->setParameter('statusSubscribed', SubscriberEntity::STATUS_SUBSCRIBED);
244 } elseif ($segment->isStatic()) {
245 $qb->addSelect("{$segmentTable}.name AS segment_name")
246 ->andWhere("{$subscriberSegmentTable}.segment_id = :segmentId")
247 ->setParameter('segmentId', $segment->getId());
248 } else {
249 // Dynamic segments don't have a relation to the segment table,
250 // So we need to use a placeholder
251 $qb->addSelect(":segmentName AS segment_name")
252 ->setParameter('segmentName', $segment->getName())
253 ->groupBy("{$subscriberTable}.id");
254 $qb = $this->filterHandler->apply($qb, $segment);
255 }
256
257 $statement = $qb->execute();
258 return $statement instanceof Result ? $statement->fetchAll() : [];
259 }
260
261 private function createSubscribersQueryBuilder(int $limit, int $offset): QueryBuilder {
262 $subscriberSegmentTable = $this->getTableName(SubscriberSegmentEntity::class);
263 $subscriberTable = $this->getTableName(SubscriberEntity::class);
264
265 return $this->entityManager->getConnection()->createQueryBuilder()
266 ->select("
267 {$subscriberTable}.first_name,
268 {$subscriberTable}.last_name,
269 {$subscriberTable}.email,
270 {$subscriberTable}.subscribed_ip,
271 {$subscriberTable}.confirmed_at,
272 {$subscriberTable}.confirmed_ip,
273 {$subscriberTable}.created_at,
274 {$subscriberTable}.last_subscribed_at,
275 {$subscriberTable}.tracking_consent,
276 {$subscriberTable}.tracking_consent_updated_at,
277 {$subscriberTable}.tracking_consent_method,
278 {$subscriberTable}.tracking_consent_copy,
279 {$subscriberTable}.status AS global_status,
280 {$subscriberSegmentTable}.status AS list_status
281 ")
282 ->from($subscriberTable)
283 ->leftJoin($subscriberTable, $subscriberSegmentTable, $subscriberSegmentTable, "{$subscriberTable}.id = {$subscriberSegmentTable}.subscriber_id")
284 ->andWhere("{$subscriberTable}.deleted_at IS NULL")
285 ->orderBy("{$subscriberTable}.id")
286 ->setFirstResult($offset)
287 ->setMaxResults($limit);
288 }
289
290 private function addSubscriberCustomFieldsToQueryBuilder(QueryBuilder $qb): QueryBuilder {
291 $segmentsTable = $this->getTableName(SubscriberEntity::class);
292 $customFieldsTable = $this->getTableName(CustomFieldEntity::class);
293 $subscriberCustomFieldTable = $this->getTableName(SubscriberCustomFieldEntity::class);
294
295 $customFields = $this->entityManager->getConnection()->createQueryBuilder()
296 ->select("{$customFieldsTable}.*")
297 ->from($customFieldsTable)
298 ->execute();
299
300 $customFields = $customFields->fetchAll();
301
302 foreach ($customFields as $customField) {
303 if (!is_array($customField) || !is_numeric($customField['id'] ?? null)) {
304 continue;
305 }
306 $rawId = (int)$customField['id'];
307 $customFieldId = "customFieldId{$rawId}export";
308 $qb->addSelect("MAX(CASE WHEN {$customFieldsTable}.id = :{$customFieldId} THEN {$subscriberCustomFieldTable}.value END) AS :{$customFieldId}")
309 ->setParameter($customFieldId, $rawId);
310 }
311
312 $qb->leftJoin($segmentsTable, $subscriberCustomFieldTable, $subscriberCustomFieldTable, "{$segmentsTable}.id = {$subscriberCustomFieldTable}.subscriber_id")
313 ->leftJoin($subscriberCustomFieldTable, $customFieldsTable, $customFieldsTable, "{$customFieldsTable}.id = {$subscriberCustomFieldTable}.custom_field_id");
314
315 return $qb;
316 }
317
318 private function notifyCreations(string $className, array $columns, array $data): void {
319 if ($className === SubscriberEntity::class) {
320 $ids = $this->getIdsByEmail($className, $columns, $data);
321 $this->subscriberChangesNotifier->subscribersCreated($ids);
322 }
323 }
324
325 private function notifyUpdates(string $className, array $columns, array $data): array {
326 if ($className === SubscriberEntity::class) {
327 $ids = $this->getIdsByEmail($className, $columns, $data);
328 $this->subscriberChangesNotifier->subscribersUpdated($ids);
329 return $ids;
330 }
331 return [];
332 }
333
334 private function getCountChangedSubscriberIdsForBulkUpdate(string $className, array $columns, array $updatedSubscriberIds, array $restoredSubscriberIds): array {
335 if ($className !== SubscriberEntity::class) {
336 return [];
337 }
338
339 $subscriberIds = $restoredSubscriberIds;
340 if (in_array('status', $columns, true)) {
341 $subscriberIds = array_merge($subscriberIds, $updatedSubscriberIds);
342 }
343
344 return array_values(array_unique(array_map('intval', $subscriberIds)));
345 }
346
347 /**
348 * @param class-string<object> $className
349 */
350 private function getIdsByEmail(string $className, array $columns, array $data): array {
351 $tableName = $this->getTableName($className);
352 $emailIndex = array_search('email', $columns);
353 if ($emailIndex === false) {
354 return [];
355 }
356 $emails = [];
357 foreach ($data as $item) {
358 $emails[] = $item[$emailIndex];
359 }
360 // get ids for updated/created rows
361 return $this->entityManager->getConnection()->executeQuery("
362 SELECT id
363 FROM {$tableName}
364 WHERE email IN (:emails)
365 ", ['emails' => $emails], ['emails' => ArrayParameterType::STRING])->fetchFirstColumn();
366 }
367
368 private function getDeletedSubscriberIdsByEmail(array $columns, array $data): array {
369 $emailColumnIndex = array_search('email', $columns, true);
370 if ($emailColumnIndex === false) {
371 return [];
372 }
373
374 $emails = array_map(function(array $row) use ($emailColumnIndex): string {
375 return (string)$row[$emailColumnIndex];
376 }, $data);
377
378 if (!$emails) {
379 return [];
380 }
381
382 $subscriberTable = $this->getTableName(SubscriberEntity::class);
383 $subscriberIds = $this->entityManager->getConnection()->executeQuery(
384 "SELECT `id`
385 FROM {$subscriberTable}
386 WHERE `email` IN (:emails)
387 AND `deleted_at` IS NOT NULL",
388 [
389 'emails' => $emails,
390 ],
391 [
392 'emails' => ArrayParameterType::STRING,
393 ]
394 )->fetchFirstColumn();
395
396 return array_values(array_map('intval', array_filter($subscriberIds, static function($id): bool {
397 return is_int($id) || (is_string($id) && ctype_digit($id));
398 })));
399 }
400 }
401