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 / Import / Cli.php
mailpoet / lib / Subscribers / ImportExport / Import Last commit date
Cli.php 2 weeks ago Import.php 2 weeks ago MailChimp.php 2 months ago MailChimpDataMapper.php 2 months ago index.php 3 years ago
Cli.php
461 lines
1 <?php declare(strict_types = 1);
2
3 namespace MailPoet\Subscribers\ImportExport\Import;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\CustomFields\CustomFieldsRepository;
9 use MailPoet\Entities\CustomFieldEntity;
10 use MailPoet\Entities\SegmentEntity;
11 use MailPoet\Entities\SubscriberEntity;
12 use MailPoet\Newsletter\Options\NewsletterOptionsRepository;
13 use MailPoet\Segments\SegmentSaveController;
14 use MailPoet\Segments\SegmentsRepository;
15 use MailPoet\Segments\WP as SegmentsWP;
16 use MailPoet\Services\Validator;
17 use MailPoet\Subscribers\ImportExport\ImportExportRepository;
18 use MailPoet\Subscribers\SubscribersRepository;
19 use MailPoet\Tags\TagRepository;
20 use WP_CLI;
21
22 class Cli {
23 /** Subscriber fields that can appear as CSV columns, matched by their canonical name. */
24 private const BASE_FIELDS = [
25 'email',
26 'first_name',
27 'last_name',
28 'subscribed_ip',
29 'created_at',
30 'confirmed_at',
31 'confirmed_ip',
32 ];
33
34 private const NEW_SUBSCRIBER_STATUSES = [
35 SubscriberEntity::STATUS_SUBSCRIBED,
36 SubscriberEntity::STATUS_UNCONFIRMED,
37 SubscriberEntity::STATUS_UNSUBSCRIBED,
38 SubscriberEntity::STATUS_INACTIVE,
39 ];
40
41 private const EXISTING_SUBSCRIBER_STATUSES = [
42 Import::STATUS_DONT_UPDATE,
43 SubscriberEntity::STATUS_SUBSCRIBED,
44 SubscriberEntity::STATUS_UNSUBSCRIBED,
45 SubscriberEntity::STATUS_INACTIVE,
46 ];
47
48 private const DEFAULT_BATCH_SIZE = 2000;
49
50 /** @var SegmentsWP */
51 private $wpSegment;
52
53 /** @var CustomFieldsRepository */
54 private $customFieldsRepository;
55
56 /** @var ImportExportRepository */
57 private $importExportRepository;
58
59 /** @var NewsletterOptionsRepository */
60 private $newsletterOptionsRepository;
61
62 /** @var SubscribersRepository */
63 private $subscribersRepository;
64
65 /** @var TagRepository */
66 private $tagRepository;
67
68 /** @var Validator */
69 private $validator;
70
71 /** @var SegmentsRepository */
72 private $segmentsRepository;
73
74 /** @var SegmentSaveController */
75 private $segmentSaveController;
76
77 public function __construct(
78 SegmentsWP $wpSegment,
79 CustomFieldsRepository $customFieldsRepository,
80 ImportExportRepository $importExportRepository,
81 NewsletterOptionsRepository $newsletterOptionsRepository,
82 SubscribersRepository $subscribersRepository,
83 TagRepository $tagRepository,
84 Validator $validator,
85 SegmentsRepository $segmentsRepository,
86 SegmentSaveController $segmentSaveController
87 ) {
88 $this->wpSegment = $wpSegment;
89 $this->customFieldsRepository = $customFieldsRepository;
90 $this->importExportRepository = $importExportRepository;
91 $this->newsletterOptionsRepository = $newsletterOptionsRepository;
92 $this->subscribersRepository = $subscribersRepository;
93 $this->tagRepository = $tagRepository;
94 $this->validator = $validator;
95 $this->segmentsRepository = $segmentsRepository;
96 $this->segmentSaveController = $segmentSaveController;
97 }
98
99 public function initialize(): void {
100 if (!class_exists(WP_CLI::class)) {
101 return;
102 }
103
104 WP_CLI::add_command('mailpoet import', [$this, 'import'], [
105 'shortdesc' => 'Imports subscribers into MailPoet from a CSV file',
106 'synopsis' => [
107 [
108 'type' => 'positional',
109 'name' => 'file',
110 'description' => 'Path to the CSV file. The header row must use MailPoet field names (email, first_name, last_name, subscribed_ip, created_at, confirmed_at, confirmed_ip) or existing custom field names. An "email" column is required.',
111 'optional' => false,
112 ],
113 [
114 'type' => 'assoc',
115 'name' => 'segments',
116 'description' => 'Comma-separated segment IDs or names to add subscribers to. Names that do not exist are created.',
117 'optional' => true,
118 ],
119 [
120 'type' => 'assoc',
121 'name' => 'status',
122 'description' => 'Status for newly created subscribers.',
123 'optional' => true,
124 'default' => SubscriberEntity::STATUS_SUBSCRIBED,
125 'options' => self::NEW_SUBSCRIBER_STATUSES,
126 ],
127 [
128 'type' => 'flag',
129 'name' => 'update-existing',
130 'description' => 'Update the details of subscribers that already exist.',
131 'optional' => true,
132 ],
133 [
134 'type' => 'assoc',
135 'name' => 'existing-status',
136 'description' => 'Status to set on existing subscribers.',
137 'optional' => true,
138 'default' => Import::STATUS_DONT_UPDATE,
139 'options' => self::EXISTING_SUBSCRIBER_STATUSES,
140 ],
141 [
142 'type' => 'assoc',
143 'name' => 'tags',
144 'description' => 'Comma-separated tag names to assign to imported subscribers. Tags are created if they do not exist.',
145 'optional' => true,
146 ],
147 [
148 'type' => 'assoc',
149 'name' => 'batch-size',
150 'description' => 'Number of subscribers to process per batch.',
151 'optional' => true,
152 'default' => self::DEFAULT_BATCH_SIZE,
153 ],
154 [
155 'type' => 'flag',
156 'name' => 'dry-run',
157 'description' => 'Parse and validate the file and report what would be imported without writing anything.',
158 'optional' => true,
159 ],
160 ],
161 ]);
162 }
163
164 /**
165 * WP-CLI entry point. Translates CLI input/output; the work happens in run().
166 *
167 * @param string[] $args
168 * @param array<string, string> $assocArgs
169 */
170 public function import(array $args, array $assocArgs): void {
171 $options = [
172 'segments' => $this->parseList((string)($assocArgs['segments'] ?? '')),
173 'status' => (string)($assocArgs['status'] ?? SubscriberEntity::STATUS_SUBSCRIBED),
174 'existing_status' => (string)($assocArgs['existing-status'] ?? Import::STATUS_DONT_UPDATE),
175 'update_existing' => !empty($assocArgs['update-existing']),
176 'tags' => $this->parseList((string)($assocArgs['tags'] ?? '')),
177 'batch_size' => (int)($assocArgs['batch-size'] ?? self::DEFAULT_BATCH_SIZE),
178 'dry_run' => !empty($assocArgs['dry-run']),
179 ];
180
181 try {
182 $totals = $this->run((string)($args[0] ?? ''), $options, function (string $message): void {
183 WP_CLI::log($message);
184 });
185 } catch (\Exception $e) {
186 WP_CLI::error($e->getMessage());
187 return;
188 }
189
190 $rowsRead = $totals['rows'] + $totals['skipped'];
191 $skippedNotice = '';
192 if ($totals['skipped'] > 0) {
193 $skippedTemplate = $totals['skipped'] === 1
194 ? ' %d row was skipped because its column count did not match the header.'
195 : ' %d rows were skipped because their column count did not match the header.';
196 $skippedNotice = sprintf($skippedTemplate, $totals['skipped']);
197 }
198
199 if ($options['dry_run']) {
200 WP_CLI::success(sprintf(
201 'Dry run: %d rows read, %d subscribers with a valid email. Nothing was written.%s',
202 $rowsRead,
203 $totals['valid'],
204 $skippedNotice
205 ));
206 return;
207 }
208
209 WP_CLI::success(sprintf(
210 'Import finished: %d created, %d updated (out of %d rows).%s',
211 $totals['created'],
212 $totals['updated'],
213 $rowsRead,
214 $skippedNotice
215 ));
216 }
217
218 /**
219 * Parses the CSV file and imports the subscribers. Throws on invalid input.
220 * Free of any WP-CLI dependency so it can be unit/integration tested.
221 *
222 * @param array{segments: string[], status: string, existing_status: string, update_existing: bool, tags: string[], batch_size: int, dry_run: bool} $options
223 * @param callable(string): void|null $logger
224 * @return array{created: int, updated: int, valid: int, rows: int, skipped: int}
225 * @throws \RuntimeException
226 */
227 public function run(string $file, array $options, ?callable $logger = null): array {
228 $log = $logger ?? function (string $message): void {
229 };
230
231 if (!is_readable($file)) {
232 throw new \RuntimeException(sprintf('File "%s" does not exist or is not readable.', $file));
233 }
234 if (!in_array($options['status'], self::NEW_SUBSCRIBER_STATUSES, true)) {
235 throw new \RuntimeException(sprintf('Invalid status "%s". Allowed: %s.', $options['status'], implode(', ', self::NEW_SUBSCRIBER_STATUSES)));
236 }
237 if (!in_array($options['existing_status'], self::EXISTING_SUBSCRIBER_STATUSES, true)) {
238 throw new \RuntimeException(sprintf('Invalid existing status "%s". Allowed: %s.', $options['existing_status'], implode(', ', self::EXISTING_SUBSCRIBER_STATUSES)));
239 }
240 if ($options['batch_size'] < 1) {
241 throw new \RuntimeException('Batch size must be a positive integer.');
242 }
243
244 $segmentIds = $this->resolveSegments($options['segments'], $options['dry_run'], $log);
245
246 $handle = fopen($file, 'r');
247 if ($handle === false) {
248 throw new \RuntimeException(sprintf('Unable to open file "%s".', $file));
249 }
250
251 try {
252 $header = fgetcsv($handle, 0, ',', '"', '\\');
253 if (!is_array($header)) {
254 throw new \RuntimeException('The CSV file is empty or has no header row.');
255 }
256 $columns = $this->buildColumns($header);
257
258 $headerColumnCount = count($header);
259 $totals = ['created' => 0, 'updated' => 0, 'valid' => 0, 'rows' => 0, 'skipped' => 0];
260 $batch = [];
261 $lineNumber = 1; // header is line 1
262 while (is_array($row = fgetcsv($handle, 0, ',', '"', '\\'))) {
263 $lineNumber++;
264 if ($row === [null]) {
265 continue; // skip blank lines
266 }
267 // fgetcsv does not pad short rows or trim long ones. A row whose column
268 // count differs from the header would misalign the per-column arrays built
269 // in Import (a value landing on the wrong subscriber), so skip it and warn
270 // rather than guessing which columns are missing.
271 if (count($row) !== $headerColumnCount) {
272 $totals['skipped']++;
273 $log(sprintf(' Skipped line %d: expected %d column(s) but found %d.', $lineNumber, $headerColumnCount, count($row)));
274 continue;
275 }
276 $totals['rows']++;
277 $batch[] = $row;
278 if (count($batch) >= $options['batch_size']) {
279 $this->processBatch($batch, $columns, $segmentIds, $options, $totals, $log);
280 $batch = [];
281 }
282 }
283 if ($batch) {
284 $this->processBatch($batch, $columns, $segmentIds, $options, $totals, $log);
285 }
286 } finally {
287 fclose($handle);
288 }
289
290 return $totals;
291 }
292
293 /**
294 * @param array<int, array<int, string|null>> $batch
295 * @param array<string|int, array{index: int}> $columns
296 * @param int[] $segmentIds
297 * @param array{segments: string[], status: string, existing_status: string, update_existing: bool, tags: string[], batch_size: int, dry_run: bool} $options
298 * @param array{created: int, updated: int, valid: int, rows: int, skipped: int} $totals
299 * @param callable(string): void $log
300 */
301 private function processBatch(
302 array $batch,
303 array $columns,
304 array $segmentIds,
305 array $options,
306 array &$totals,
307 callable $log
308 ): void {
309 $data = [
310 'subscribers' => $batch,
311 'columns' => $columns,
312 'segments' => $segmentIds,
313 'tags' => $options['tags'],
314 'timestamp' => time(),
315 'newSubscribersStatus' => $options['status'],
316 'existingSubscribersStatus' => $options['existing_status'],
317 'updateSubscribers' => $options['update_existing'],
318 ];
319
320 $import = new Import(
321 $this->wpSegment,
322 $this->customFieldsRepository,
323 $this->importExportRepository,
324 $this->newsletterOptionsRepository,
325 $this->subscribersRepository,
326 $this->tagRepository,
327 $this->validator,
328 $data
329 );
330
331 if ($options['dry_run']) {
332 $valid = $import->validateSubscribersData($import->subscribersData);
333 $emails = is_array($valid) && isset($valid['email']) ? $valid['email'] : [];
334 $totals['valid'] += count($emails);
335 return;
336 }
337
338 $result = $import->process();
339 $totals['created'] += (int)$result['created'];
340 $totals['updated'] += (int)$result['updated'];
341 $log(sprintf(' Batch of %d rows: %d created, %d updated.', count($batch), $result['created'], $result['updated']));
342 }
343
344 /**
345 * Maps each CSV header to a subscriber field or custom field id.
346 *
347 * @param array<int, string|null> $header
348 * @return array<string|int, array{index: int}>
349 * @throws \RuntimeException
350 */
351 private function buildColumns(array $header): array {
352 $columns = [];
353 $unknown = [];
354 $duplicates = [];
355 $namesByField = [];
356 foreach ($header as $index => $name) {
357 $name = trim((string)$name);
358 if ($name === '') {
359 continue;
360 }
361 $field = $this->resolveField($name);
362 if ($field === null) {
363 $unknown[] = $name;
364 continue;
365 }
366 if (isset($columns[$field])) {
367 $duplicates[$field] = array_merge($namesByField[$field], [$name]);
368 continue;
369 }
370 $namesByField[$field] = [$name];
371 $columns[$field] = ['index' => $index];
372 }
373
374 if ($unknown) {
375 throw new \RuntimeException(sprintf(
376 'Unrecognized CSV column(s): %s. Use MailPoet field names (%s) or an existing custom field name.',
377 implode(', ', $unknown),
378 implode(', ', self::BASE_FIELDS)
379 ));
380 }
381
382 if ($duplicates) {
383 $details = array_map(function (array $names): string {
384 return implode(', ', $names);
385 }, $duplicates);
386 throw new \RuntimeException(sprintf(
387 'Duplicate CSV column(s) mapping to the same field: %s. Each field may only appear once in the header.',
388 implode('; ', $details)
389 ));
390 }
391
392 if (!isset($columns['email'])) {
393 throw new \RuntimeException('The CSV file must contain an "email" column.');
394 }
395
396 return $columns;
397 }
398
399 /**
400 * @return string|int|null Field name, custom field id, or null when unrecognized.
401 */
402 private function resolveField(string $header) {
403 if (in_array(strtolower($header), self::BASE_FIELDS, true)) {
404 return strtolower($header);
405 }
406 $customField = $this->customFieldsRepository->findOneBy(['name' => $header]);
407 if ($customField instanceof CustomFieldEntity) {
408 return $customField->getId();
409 }
410 return null;
411 }
412
413 /**
414 * Resolves segment IDs/names to IDs. Unknown names are created unless this is a dry run.
415 *
416 * @param string[] $segments
417 * @param bool $dryRun
418 * @param callable(string): void $log
419 * @return int[]
420 * @throws \RuntimeException
421 */
422 private function resolveSegments(array $segments, bool $dryRun, callable $log): array {
423 $ids = [];
424 foreach ($segments as $segment) {
425 if (ctype_digit($segment)) {
426 $entity = $this->segmentsRepository->findOneById((int)$segment);
427 if (!$entity instanceof SegmentEntity) {
428 throw new \RuntimeException(sprintf('Segment with ID "%s" does not exist.', $segment));
429 }
430 $ids[] = (int)$segment;
431 continue;
432 }
433 $entity = $this->segmentsRepository->findOneBy(['name' => $segment, 'type' => SegmentEntity::TYPE_DEFAULT]);
434 if ($entity instanceof SegmentEntity) {
435 $ids[] = (int)$entity->getId();
436 continue;
437 }
438 if ($dryRun) {
439 $log(sprintf('Segment "%s" would be created.', $segment));
440 continue;
441 }
442 $entity = $this->segmentSaveController->save(['name' => $segment]);
443 $log(sprintf('Created segment "%s" (ID %d).', $segment, (int)$entity->getId()));
444 $ids[] = (int)$entity->getId();
445 }
446 return array_values(array_unique($ids));
447 }
448
449 /**
450 * @return string[]
451 */
452 private function parseList(string $value): array {
453 if (trim($value) === '') {
454 return [];
455 }
456 return array_values(array_filter(array_map('trim', explode(',', $value)), function (string $item): bool {
457 return $item !== '';
458 }));
459 }
460 }
461