PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.34.2
MailPoet – Newsletters, Email Marketing, and Automation v5.34.2
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 2 months ago Import 2 weeks ago PersonalDataExporters 2 months ago ImportExportFactory.php 2 months ago ImportExportRepository.php 2 months ago index.php 3 years ago
ImportExportRepository.php
397 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}.status AS global_status,
276 {$subscriberSegmentTable}.status AS list_status
277 ")
278 ->from($subscriberTable)
279 ->leftJoin($subscriberTable, $subscriberSegmentTable, $subscriberSegmentTable, "{$subscriberTable}.id = {$subscriberSegmentTable}.subscriber_id")
280 ->andWhere("{$subscriberTable}.deleted_at IS NULL")
281 ->orderBy("{$subscriberTable}.id")
282 ->setFirstResult($offset)
283 ->setMaxResults($limit);
284 }
285
286 private function addSubscriberCustomFieldsToQueryBuilder(QueryBuilder $qb): QueryBuilder {
287 $segmentsTable = $this->getTableName(SubscriberEntity::class);
288 $customFieldsTable = $this->getTableName(CustomFieldEntity::class);
289 $subscriberCustomFieldTable = $this->getTableName(SubscriberCustomFieldEntity::class);
290
291 $customFields = $this->entityManager->getConnection()->createQueryBuilder()
292 ->select("{$customFieldsTable}.*")
293 ->from($customFieldsTable)
294 ->execute();
295
296 $customFields = $customFields->fetchAll();
297
298 foreach ($customFields as $customField) {
299 if (!is_array($customField) || !is_numeric($customField['id'] ?? null)) {
300 continue;
301 }
302 $rawId = (int)$customField['id'];
303 $customFieldId = "customFieldId{$rawId}export";
304 $qb->addSelect("MAX(CASE WHEN {$customFieldsTable}.id = :{$customFieldId} THEN {$subscriberCustomFieldTable}.value END) AS :{$customFieldId}")
305 ->setParameter($customFieldId, $rawId);
306 }
307
308 $qb->leftJoin($segmentsTable, $subscriberCustomFieldTable, $subscriberCustomFieldTable, "{$segmentsTable}.id = {$subscriberCustomFieldTable}.subscriber_id")
309 ->leftJoin($subscriberCustomFieldTable, $customFieldsTable, $customFieldsTable, "{$customFieldsTable}.id = {$subscriberCustomFieldTable}.custom_field_id");
310
311 return $qb;
312 }
313
314 private function notifyCreations(string $className, array $columns, array $data): void {
315 if ($className === SubscriberEntity::class) {
316 $ids = $this->getIdsByEmail($className, $columns, $data);
317 $this->subscriberChangesNotifier->subscribersCreated($ids);
318 }
319 }
320
321 private function notifyUpdates(string $className, array $columns, array $data): array {
322 if ($className === SubscriberEntity::class) {
323 $ids = $this->getIdsByEmail($className, $columns, $data);
324 $this->subscriberChangesNotifier->subscribersUpdated($ids);
325 return $ids;
326 }
327 return [];
328 }
329
330 private function getCountChangedSubscriberIdsForBulkUpdate(string $className, array $columns, array $updatedSubscriberIds, array $restoredSubscriberIds): array {
331 if ($className !== SubscriberEntity::class) {
332 return [];
333 }
334
335 $subscriberIds = $restoredSubscriberIds;
336 if (in_array('status', $columns, true)) {
337 $subscriberIds = array_merge($subscriberIds, $updatedSubscriberIds);
338 }
339
340 return array_values(array_unique(array_map('intval', $subscriberIds)));
341 }
342
343 /**
344 * @param class-string<object> $className
345 */
346 private function getIdsByEmail(string $className, array $columns, array $data): array {
347 $tableName = $this->getTableName($className);
348 $emailIndex = array_search('email', $columns);
349 if ($emailIndex === false) {
350 return [];
351 }
352 $emails = [];
353 foreach ($data as $item) {
354 $emails[] = $item[$emailIndex];
355 }
356 // get ids for updated/created rows
357 return $this->entityManager->getConnection()->executeQuery("
358 SELECT id
359 FROM {$tableName}
360 WHERE email IN (:emails)
361 ", ['emails' => $emails], ['emails' => ArrayParameterType::STRING])->fetchFirstColumn();
362 }
363
364 private function getDeletedSubscriberIdsByEmail(array $columns, array $data): array {
365 $emailColumnIndex = array_search('email', $columns, true);
366 if ($emailColumnIndex === false) {
367 return [];
368 }
369
370 $emails = array_map(function(array $row) use ($emailColumnIndex): string {
371 return (string)$row[$emailColumnIndex];
372 }, $data);
373
374 if (!$emails) {
375 return [];
376 }
377
378 $subscriberTable = $this->getTableName(SubscriberEntity::class);
379 $subscriberIds = $this->entityManager->getConnection()->executeQuery(
380 "SELECT `id`
381 FROM {$subscriberTable}
382 WHERE `email` IN (:emails)
383 AND `deleted_at` IS NOT NULL",
384 [
385 'emails' => $emails,
386 ],
387 [
388 'emails' => ArrayParameterType::STRING,
389 ]
390 )->fetchFirstColumn();
391
392 return array_values(array_map('intval', array_filter($subscriberIds, static function($id): bool {
393 return is_int($id) || (is_string($id) && ctype_digit($id));
394 })));
395 }
396 }
397