PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.0.3
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.0.3
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
acymailing / back / Helpers / ImportHelper.php

ImportHelper.php in AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress 11.0.3, at back/Helpers/ImportHelper.php

1,305 lines 53.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace AcyMailing\Helpers;
4
5 use AcyMailing\Classes\ListClass;
6 use AcyMailing\Classes\UserClass;
7 use AcyMailing\Core\AcymObject;
8
9 class ImportHelper extends AcymObject
10 {
11 //Handle messages to say that X users have been subscribed to list Y
12 private array $subscribedUsers = [];
13 private array $importUserInLists = [];
14 private array $allUserIds = [];
15 private array $columns = [];
16 private int $totalTry = 0;
17 private int $totalValid = 0;
18 private int $separatorsToRemove = 0;
19 private bool $forceConfirm = false;
20 private bool $generateName = true;
21 private bool $overwrite = false;
22 private string $header = '';
23 private string $separator = ',';
24
25 //Variables used to handle the import on filter
26 public string $tableName = '';
27 public array $dbWhere = [];
28 public array $fieldsMap = [];
29 public array $subscriptionHistory = [];
30 public array $importedUsersByEmail = [];
31
32 public function __construct()
33 {
34 parent::__construct();
35 //We do an import... so it might take a lot of times and it also might take some memory... so we increase those limits if we can!
36 acym_increasePerf();
37 }
38
39 public function file(): bool
40 {
41 //Step 1 : we copy the file in the correct directory
42 $importFile = acym_getVar('array', 'import_file', [], 'files');
43
44 if (empty($importFile['name'])) {
45 acym_enqueueMessage(acym_translation('ACYM_PLEASE_BROWSE_FILE_IMPORT'), 'error');
46
47 return false;
48 }
49
50 $extension = strtolower(acym_fileGetExt($importFile['name']));
51
52 if (!preg_match('#^(csv)$#Ui', $extension) || preg_match('#\.(php.?|.?htm.?|pl|py|jsp|asp|sh|cgi)$#Ui', $importFile['name'])) {
53 acym_enqueueMessage(acym_translationSprintf('ACYM_ACCEPTED_TYPE', esc_html($extension), $this->config->get('allowed_files')), 'error');
54
55 return false;
56 }
57
58 // Checking error upload
59 $fileError = $importFile['error'];
60 if ($fileError > 0) {
61 switch ($fileError) {
62 case 1:
63 case 2:
64 acym_enqueueMessage(acym_translation('ACYM_UPLOADED_FILE_EXCEED_MAX_FILESIZE_PHP'), 'error');
65
66 return false;
67 case 3:
68 acym_enqueueMessage(acym_translation('ACYM_FILE_UPLOADED_PARTIALLY'), 'error');
69
70 return false;
71 case 4:
72 acym_enqueueMessage(acym_translation('ACYM_NO_FILE_WAS_UPLOADED'), 'error');
73
74 return false;
75 default:
76 acym_enqueueMessage(acym_translationSprintf('ACYM_UNKNOWN_ERROR_UPLOADING_FILE', $fileError), 'error');
77
78 return false;
79 }
80 }
81
82 $uploadPath = $this->createUploadFolder();
83
84 $filename = uniqid('import_').'.csv';
85 acym_setVar('acym_import_filename', $filename);
86
87 if (!acym_uploadFile($importFile['tmp_name'], $uploadPath.$filename)) {
88 acym_enqueueMessage(
89 acym_translationSprintf(
90 'ACYM_FAIL_UPLOAD',
91 '<b><i>'.esc_html($importFile['tmp_name']).'</i></b>',
92 '<b><i>'.esc_html($uploadPath.$filename).'</i></b>'
93 ),
94 'error'
95 );
96 }
97
98 return true;
99 }
100
101 public function textarea(): bool
102 {
103 $content = acym_getVar('string', 'acym__users__import__from_text__textarea');
104 $path = $this->createUploadFolder();
105 $filename = uniqid('import_').'.csv';
106
107 acym_writeFile($path.$filename, $content);
108 acym_setVar('acym_import_filename', $filename);
109
110 return true;
111 }
112
113 public function cms(): bool
114 {
115 global $acymCmsUserVars;
116
117 //Update the users which already have a userid
118 $query = 'UPDATE IGNORE '.$acymCmsUserVars->table.' as b, #__acym_user as a SET a.email = b.'.$acymCmsUserVars->email.', a.name = b.'.$acymCmsUserVars->name.', a.active = 1 - b.'.$acymCmsUserVars->blocked.' WHERE a.cms_id = b.'.$acymCmsUserVars->id.' AND a.cms_id IS NOT NULL';
119 $nbUpdated = acym_query($query);
120
121 //Step 1 : update the existing ones.
122 //Update the users to give them a userid
123 $query = 'UPDATE IGNORE '.$acymCmsUserVars->table.' as b, #__acym_user as a SET a.cms_id = b.'.$acymCmsUserVars->id.' WHERE a.email = b.'.$acymCmsUserVars->email;
124 $affected = acym_query($query);
125 $nbUpdated += intval($affected);
126
127 acym_enqueueMessage(acym_translationSprintf('ACYM_IMPORT_UPDATE', $nbUpdated), 'success');
128
129 //Step 2 : delete the ones which don't exist any more
130 $query = 'SELECT a.id FROM #__acym_user as a LEFT JOIN '.$acymCmsUserVars->table.' as b on a.cms_id = b.'.$acymCmsUserVars->id.' WHERE b.'.$acymCmsUserVars->id.' IS NULL AND a.cms_id > 0';
131 $deletedSubid = acym_loadResultArray($query);
132
133 $query = 'SELECT a.id FROM #__acym_user as a LEFT JOIN '.$acymCmsUserVars->table.' as b on a.email = b.'.$acymCmsUserVars->email.' WHERE b.'.$acymCmsUserVars->id.' IS NULL AND a.cms_id > 0';
134 $deletedSubid = array_merge(acym_loadResultArray($query), $deletedSubid);
135
136 if (!empty($deletedSubid)) {
137 $userClass = new UserClass();
138 $deletedUsers = $userClass->delete($deletedSubid);
139 acym_enqueueMessage(acym_translationSprintf('ACYM_IMPORT_DELETE', $deletedUsers));
140 }
141
142 //Step 3 : insert the new ones
143 $time = time();
144 $formattedTime = acym_date($time, 'Y-m-d H:i:s');
145 $sourceImport = 'Import on '.$formattedTime;
146 $query = 'INSERT IGNORE INTO #__acym_user (`name`,`email`,`creation_date`,`active`,`cms_id`, `source`) SELECT `user`.`'.$acymCmsUserVars->name.'`,`user`.`'.$acymCmsUserVars->email.'`,`user`.`'.$acymCmsUserVars->registered.'`,1 - `user`.'.$acymCmsUserVars->blocked.',`user`.`'.$acymCmsUserVars->id.'`,\''.$sourceImport.'\' FROM '.$acymCmsUserVars->table.' AS `user` ';
147
148 $queryJoin = [];
149 $queryWhere = [];
150 if (ACYM_CMS === 'wordpress' && is_multisite()) {
151 $queryJoin[] = 'JOIN #__usermeta AS `meta` ON `user`.'.$acymCmsUserVars->id.'=`meta`.`user_id`';
152 $queryWhere[] = '`meta`.`meta_key`=\'#__capabilities\'';
153 }
154 $groups = acym_getVar('array', 'groups', []);
155 $this->config->saveConfig(['import_groups' => implode(',', $groups)]);
156 if (!empty($groups)) {
157 if (ACYM_CMS === 'joomla') {
158 acym_arrayToInteger($groups);
159 $queryJoin[] = ' JOIN #__user_usergroup_map AS `map` ON map.user_id = `user`.`'.$acymCmsUserVars->id.'`';
160 $queryWhere[] = '`map`.`group_id` IN ('.implode(', ', $groups).')';
161 } else {
162 if (!is_multisite()) {
163 $queryJoin[] = 'JOIN #__usermeta AS `meta` ON meta.user_id = `user`.`'.$acymCmsUserVars->id.'` AND `meta`.`meta_key` = "#__capabilities"';
164 }
165
166 foreach ($groups as $i => $oneGroup) {
167 $groups[$i] = acym_escapeDB('%'.strlen($oneGroup).':"'.$oneGroup.'"%');
168 }
169 $queryWhere[] = '`meta`.`meta_value` LIKE '.implode(' OR `meta`.`meta_value` LIKE ', $groups);
170 }
171 }
172
173 $query .= implode(' ', $queryJoin);
174 if (!empty($queryWhere)) {
175 $query .= ' WHERE ('.implode(') AND (', $queryWhere).')';
176 }
177
178 $insertedUsers = acym_query($query);
179
180 acym_query('UPDATE #__acym_configuration SET `value` = '.intval($time).' WHERE `name` = \'last_import\'');
181
182 acym_trigger('onAcymAfterCMSUserImport', [$sourceImport]);
183
184 acym_enqueueMessage(acym_translationSprintf('ACYM_IMPORT_NEW_SUBS', $insertedUsers), 'info');
185
186 //Step 4 : subscribe all the registered users to one or several lists
187 $lists = $this->getImportedLists();
188 $listsSubscribe = [];
189 if (!empty($lists)) {
190 foreach ($lists as $listid => $val) {
191 if (!empty($val)) {
192 $listsSubscribe[] = intval($listid);
193 }
194 }
195 }
196
197 if (empty($listsSubscribe)) {
198 return true;
199 }
200
201 $query = 'INSERT IGNORE INTO #__acym_user_has_list (`user_id`,`list_id`,`status`,`subscription_date`) ';
202 $query .= 'SELECT user.`id`, list.`id`, 1, '.acym_escapeDB(gmdate('Y-m-d H:i:s', time())).'
203 FROM #__acym_list AS list, #__acym_user AS user ';
204 $conditions = [];
205 $conditions[] = 'list.`id` IN ('.implode(',', $listsSubscribe).')';
206 $conditions[] = 'user.`cms_id` > 0';
207
208 if (!empty($groups)) {
209 if (ACYM_CMS === 'joomla') {
210 $query .= ' JOIN #__user_usergroup_map AS `map` ON map.user_id = `user`.`cms_id`';
211 $conditions[] = '`map`.`group_id` IN ('.implode(', ', $groups).')';
212 } else {
213 $query .= ' JOIN #__usermeta AS `meta` ON meta.user_id = `user`.`cms_id` AND `meta`.`meta_key` = "#__capabilities"';
214 $conditions[] = '(`meta`.`meta_value` LIKE '.implode(' OR `meta`.`meta_value` LIKE ', $groups).')';
215 }
216 }
217
218 $query .= ' WHERE '.implode(' AND ', $conditions);
219 $nbsubscribed = acym_query($query);
220 acym_enqueueMessage(acym_translationSprintf('ACYM_IMPORT_SUBSCRIPTION', $nbsubscribed));
221
222 return true;
223 }
224
225
226 public function database(bool $onlyImport = false): bool
227 {
228 $this->forceConfirm = acym_getVar('bool', 'import_confirmed_database', true);
229
230 $table = empty($this->tableName) ? trim(acym_getVar('string', 'tablename')) : $this->tableName;
231 $time = time();
232 $formattedTime = acym_date($time, 'Y-m-d H:i:s');
233
234 if (empty($table)) {
235 acym_enqueueMessage(acym_translation('ACYM_SPECIFYTABLE'), 'warning');
236
237 return false;
238 }
239
240 //We got a table... let's load the fields from this table
241 $fields = acym_getColumns($table, false, false);
242 if (empty($fields)) {
243 //there is no field... so we consider it was the wrong table
244 acym_enqueueMessage(acym_translation('ACYM_SPECIFYTABLE'), 'warning');
245
246 return false;
247 }
248
249 //Ok now we have some fields and now we have a table selected.
250 $equivalentFields = empty($this->fieldsMap) ? acym_getVar('array', 'fields', []) : $this->fieldsMap;
251
252 if (empty($equivalentFields['email'])) {
253 acym_enqueueMessage(acym_translation('ACYM_SPECIFYFIELDEMAIL'), 'warning');
254
255 return false;
256 }
257
258 $select = [];
259 //We check the data and create the query
260 foreach ($equivalentFields as $acyField => $tableField) {
261 $tableField = trim($tableField);
262 if (empty($tableField)) {
263 continue;
264 }
265 if (!in_array($tableField, $fields)) {
266 acym_enqueueMessage(acym_translationSprintf('ACYM_SPECIFYFIELD', $tableField, implode(' <br> ', $fields)), 'warning');
267
268 return false;
269 }
270 $select['`'.acym_secureDBColumn($acyField).'`'] = acym_secureDBColumn($tableField);
271 }
272
273 if (empty($select['`creation_date`'])) {
274 $select['`creation_date`'] = acym_escapeDB(acym_date('now', 'Y-m-d H:i:s'));
275 }
276
277 if ($this->forceConfirm && empty($select['`confirmed`'])) {
278 $select['`confirmed`'] = 1;
279 }
280
281 $sourceTxt = 'Import on '.$formattedTime;
282 $select['`source`'] = acym_escapeDB($sourceTxt);
283
284 $query = 'INSERT IGNORE INTO #__acym_user ('.implode(' , ', array_keys($select)).') SELECT '.implode(' , ', $select);
285 $query .= ' FROM '.acym_secureDBColumn($table).' WHERE '.acym_secureDBColumn($select['`email`']).' LIKE "%@%"';
286 if (!empty($this->dbWhere)) {
287 $query .= ' AND ( '.implode(' ) AND (', $this->dbWhere).' )';
288 }
289
290 $affectedRows = acym_query($query);
291
292 acym_query('UPDATE #__acym_configuration SET `value` = '.intval($time).' WHERE `name` = "last_import"');
293
294 acym_trigger('onAcymAfterDatabaseUserImport', [$sourceTxt]);
295
296 if ($onlyImport) {
297 return true;
298 }
299
300 acym_enqueueMessage(acym_translationSprintf('ACYM_IMPORT_NEW_SUBS', $affectedRows), 'info');
301
302
303 //Step 4 : subscribe all the registered users to one or several lists
304 $lists = $this->getImportedLists();
305 $listsSubscribe = [];
306 if (!empty($lists)) {
307 foreach ($lists as $listid => $val) {
308 if (!empty($val)) {
309 $listsSubscribe[] = intval($listid);
310 }
311 }
312 }
313
314 if (empty($listsSubscribe)) {
315 return true;
316 }
317
318 $query = 'INSERT IGNORE INTO #__acym_user_has_list (`user_id`,`list_id`,`status`,`subscription_date`) ';
319 $query .= 'SELECT user.`id`, list.`id`, 1, '.acym_escapeDB(gmdate('Y-m-d H:i:s', time())).'
320 FROM #__acym_list AS list, #__acym_user AS user
321 WHERE list.`id` IN ('.implode(',', $listsSubscribe).')
322 AND user.`email` IN (SELECT '.acym_secureDBColumn($select['`email`']).' FROM '.acym_secureDBColumn($table).')';
323
324 $nbsubscribed = acym_query($query);
325 acym_enqueueMessage(acym_translationSprintf('ACYM_IMPORT_SUBSCRIPTION', $nbsubscribed));
326
327 return true;
328 }
329
330 public function mailpoet(): bool
331 {
332 // Step 1 : import subscriber from mailpoet
333 $time = time();
334 $formattedTime = acym_date($time, 'Y-m-d H:i:s');
335 $sourceImport = 'import_on_'.$formattedTime.'_mailpoet';
336
337 $query = 'INSERT IGNORE INTO #__acym_user (`name`,`email`,`creation_date`,`active`, `confirmed`,`cms_id`, `source`)
338 SELECT CONCAT(`subscriber`.`first_name`, `subscriber`.`last_name`),
339 `subscriber`.`email`,
340 `subscriber`.`created_at`,
341 IF(`subscriber`.`status` = "inactive", 0, 1),
342 IF(`subscriber`.`status` = "subscribed", 1, 0),
343 `subscriber`.`wp_user_id`,
344 \''.$sourceImport.'\'
345 FROM #__mailpoet_subscribers AS `subscriber` ';
346 $listsMailpoet = acym_getVar('array', 'mailpoet_lists', []);
347 if (!empty($listsMailpoet)) {
348 acym_arrayToInteger($listsMailpoet);
349 $query .= ' JOIN #__mailpoet_subscriber_segment AS sub_segment ON `subscriber`.`id` = `sub_segment`.`subscriber_id` AND `sub_segment`.`segment_id` IN ('.implode(
350 ',',
351 $listsMailpoet
352 ).')';
353 }
354 $query .= ' WHERE `subscriber`.`deleted_at` IS NULL ON DUPLICATE KEY UPDATE `source`='.acym_escapeDB($sourceImport);
355
356 $insertedUsers = acym_query($query);
357
358 acym_query('UPDATE #__acym_configuration SET `value` = '.intval($time).' WHERE `name` = \'last_import\'');
359
360 acym_enqueueMessage(acym_translationSprintf('ACYM_IMPORT_NEW_SUBS', $insertedUsers), 'info');
361
362 // Step 2 : subscribe all the registered users to one or several lists
363 $lists = $this->getImportedLists();
364 $listsSubscribe = [];
365 if (!empty($lists)) {
366 foreach ($lists as $listid => $val) {
367 if (!empty($val)) {
368 $listsSubscribe[] = intval($listid);
369 }
370 }
371 }
372
373 if (empty($listsSubscribe)) {
374 return true;
375 }
376
377 $query = 'INSERT IGNORE INTO #__acym_user_has_list (`user_id`,`list_id`,`status`,`subscription_date`) ';
378 $query .= 'SELECT user.`id`, list.`id`, 1, '.acym_escapeDB(gmdate('Y-m-d H:i:s', time())).'
379 FROM #__acym_list AS list, #__acym_user AS user ';
380 $conditions = [];
381 $conditions[] = 'list.`id` IN ('.implode(',', $listsSubscribe).')';
382 $conditions[] = 'user.`source` = '.acym_escapeDB($sourceImport);
383
384 if (!empty($listsMailpoet)) {
385 $query .= ' JOIN #__mailpoet_subscribers AS subscriber ON subscriber.email = user.email';
386 $query .= ' JOIN #__mailpoet_subscriber_segment AS subscriber_segment ON subscriber.id = subscriber_segment.subscriber_id AND subscriber_segment.segment_id IN ('.implode(
387 ',',
388 $listsMailpoet
389 ).')';
390 }
391
392 $query .= ' WHERE '.implode(' AND ', $conditions);
393 $nbsubscribed = acym_query($query);
394 acym_enqueueMessage(acym_translationSprintf('ACYM_IMPORT_SUBSCRIPTION', $nbsubscribed));
395
396 return true;
397 }
398
399 public function finalizeImport(): bool
400 {
401 $filename = strtolower(acym_getVar('cmd', 'acym_import_filename'));
402 $extension = '.'.acym_fileGetExt($filename);
403 $this->forceConfirm = acym_getVar('bool', 'import_confirmed_generic', true);
404 $this->generateName = acym_getVar('bool', 'import_generate_generic', true);
405 $this->overwrite = acym_getVar('bool', 'import_overwrite_generic', true);
406
407 // Remember user's choices
408 $this->config->saveConfig(
409 [
410 'import_confirmed' => $this->forceConfirm,
411 'import_generate' => $this->generateName,
412 'import_overwrite' => $this->overwrite,
413 ]
414 );
415
416 $filename = str_replace(['.', ' '], '_', substr($filename, 0, strpos($filename, $extension))).$extension;
417 $uploadPath = ACYM_MEDIA.'import'.DS.$filename;
418
419 if (!file_exists($uploadPath) || !is_file($uploadPath)) {
420 acym_enqueueMessage(acym_translation('ACYM_UPLOADED_FILE_NOT_FOUND').' '.$uploadPath, 'error');
421
422 return false;
423 }
424
425 $importColumns = acym_getVar('string', 'import_columns');
426 if (empty($importColumns)) {
427 acym_enqueueMessage(acym_translation('ACYM_COLUMNS_NOT_FOUND'), 'error');
428
429 return false;
430 }
431
432 $contentFile = file_get_contents($uploadPath);
433
434 //We convert into the correct charset
435 if (acym_getVar('cmd', 'acyencoding', '') != '') {
436 $encodingHelper = new EncodingHelper();
437 $contentFile = $encodingHelper->change($contentFile, acym_getVar('cmd', 'acyencoding'), 'UTF-8');
438 }
439
440 $cutContent = str_replace(["\r\n", "\r"], "\n", $contentFile);
441 $allLines = explode("\n", $cutContent);
442
443 $listSeparators = ["\t", ';', ','];
444 $separator = ',';
445 foreach ($listSeparators as $sep) {
446 if (strpos($allLines[0], $sep) !== false) {
447 $separator = $sep;
448 break;
449 }
450 }
451
452 if (!empty($listsId)) {
453 $allLines[0] .= $sep;
454 }
455
456 $importColumns = str_replace(',', $separator, $importColumns);
457
458 if (strpos($allLines[0], '@')) {
459 $contentFile = $importColumns."\n".$contentFile;
460 } else {
461 $allLines[0] = $importColumns;
462 $contentFile = implode("\n", $allLines);
463 }
464
465 $this->handleContent($contentFile);
466
467 //We can now delete the file
468 acym_deleteFile($uploadPath);
469 $this->cleanImportFolder();
470
471 return true;
472 }
473
474 public function getImportedLists(): array
475 {
476 $listClass = new ListClass();
477 $listsId = json_decode(acym_getVar('string', 'acym__entity_select__selected'), true);
478 $newListName = acym_getVar('string', 'new_list');
479
480 if (empty($listsId) && empty($newListName)) {
481 return [];
482 }
483
484 $lists = [];
485
486 if (!empty($newListName)) {
487 $newList = new \stdClass();
488 $newList->name = $newListName;
489 $newList->active = 1;
490 $colors = '#'.substr(str_shuffle('ABCDEF0123456789'), 0, 6);
491 $newList->color = $colors;
492 $listid = $listClass->save($newList);
493 $lists[$listid] = 1;
494 }
495
496 if (!empty($listsId)) {
497 foreach ($listsId as $id) {
498 $lists[$id] = 1;
499 }
500 }
501
502 return $lists;
503 }
504
505 public function additionalDataUsersImport(bool $isGeneric): string
506 {
507 $buttonAddListId = $isGeneric ? 'acym__users__generic__import__create-list__button' : 'acym__users__import__create-list__button';
508 $buttonImportClass = $isGeneric ? 'acym__users__import__generic__import__button' : 'acym__users__import__button';
509 $buttonSkipId = $isGeneric ? 'acym__users__generic__import__skip__button' : 'acym__users__import__skip__button';
510 $buttonImportDataTask = $isGeneric ? 'listing' : '';
511
512 return '<div class="cell align-right grid-x margin-bottom-2">
513 <div id="acym__users__import__create-list" class="grid-x" style="display: none;">
514 <label for="acym__users__import__create-list__field" class="margin-right-1 acym_vcenter">'.acym_translation('ACYM_LIST_NAME').' : </label>
515 <div>
516 <input id="acym__users__import__create-list__field" type="text">
517 </div>
518 </div>
519 <button type="button" class="button button-secondary margin-left-1 acym_vcenter margin-right-2" id="'.$buttonAddListId.'">'.acym_translation(
520 'ACYM_CREATE_NEW_LIST'
521 ).'</button>
522 <i style="display: none;" class="acym_vcenter acymicon-circle-o-notch acymicon-spin" id="acym__users__import__create-list__loading-logo"></i>
523 </div>
524 <div class="cell align-right grid-x">
525 <button type="button" class="button-secondary button cell shrink margin-right-1" id="'.$buttonSkipId.'">'.acym_translation('ACYM_SKIP').'</button>
526 <button
527 type="button"
528 class="button-primary button acy_button_submit cell shrink margin-right-2 '.$buttonImportClass.'"
529 id="acym__entity_select__button__submit" data-task="'.$buttonImportDataTask.'">'.acym_translation('ACYM_SUBSCRIBE_USERS_TO_THESE_LISTS').'</button>
530 </div>';
531 }
532
533 private function createUploadFolder(): string
534 {
535 $folderPath = acym_cleanPath(ACYM_ROOT.trim(html_entity_decode(ACYM_MEDIA_FOLDER.'import'))).DS;
536 if ('wordpress' === ACYM_CMS) {
537 $folderPath = acym_cleanPath(WP_PLUGIN_DIR.DS.ACYM_COMPONENT.DS.'media'.DS.'import').DS;
538 }
539 if (!is_dir($folderPath)) {
540 acym_createDir($folderPath, true, true);
541 }
542
543 if (!acym_isWritable($folderPath)) {
544 acym_chmod($folderPath, 0755);
545 if (!acym_isWritable($folderPath)) {
546 acym_enqueueMessage(acym_translationSprintf('ACYM_WRITABLE_FOLDER', $folderPath), 'warning');
547 }
548 }
549
550 return $folderPath;
551 }
552
553 private function handleContent(string $contentFile): void
554 {
555 $timestamp = time();
556
557 //We convert the file into something valid so that mac or not will be Ok!
558 $contentFile = str_replace(["\r\n", "\r"], "\n", $contentFile);
559 $importLines = explode("\n", $contentFile);
560
561 //A little trick to take the second line if the first line is empty...
562 $i = 0;
563 $this->header = '';
564 $this->allUserIds = [];
565 while (empty($this->header) && $i < 10) {
566 $this->header = trim($importLines[$i]);
567 $i++;
568 }
569
570 if (strpos($this->header, '@') && !strpos($this->header, ',') && !strpos($this->header, ';') && !strpos($this->header, "\t")) {
571 //We have an @ in the first line so there is definitely an issue there and the first line is not the right one!
572 //And we don't have separators so we know the format
573 $this->header = 'email';
574 $i--;
575 }
576
577 //Step 3 : we make sure the header of the file is correct (so it does correspond to columns from the database)
578 if (!$this->autoDetectHeader()) {
579 acym_enqueueMessage(acym_translationSprintf('ACYM_IMPORT_HEADER', esc_html($this->header)), 'error');
580 acym_enqueueMessage(acym_translation('ACYM_IMPORT_EMAIL'), 'error');
581
582 return;
583 }
584
585 $encodingHelper = new EncodingHelper();
586 $userClass = new UserClass();
587 $listClass = new ListClass();
588
589 $allLists = $listClass->getAll('name');
590 $numberColumns = count($this->columns);
591 $countUsersBeforeImport = $userClass->getCountTotalUsers();
592 $importUsers = [];
593 $errorLines = [];
594
595 //Step 4 : we start importing the file as everything is OK
596 while (isset($importLines[$i])) {
597 // If there are quotes in the line, check if it is a broken line or not
598 if (strpos($importLines[$i], '"') !== false) {
599 $data = [];
600 $j = $i + 1;
601 // The position is the separator's one, -1 for the beginning as the line does not start by a separator
602 $position = -1;
603
604 // Concatenate 30 lines max
605 while ($j < ($i + 30)) {
606 // Test if the value is encapsulated by quotes
607 $quoteOpened = substr($importLines[$i], $position + 1, 1) == '"';
608
609 // If encapsulated, search the end of the value
610 if ($quoteOpened) {
611 $nextQuotePosition = strpos($importLines[$i], '"', $position + 2);
612 // If quotes in the value encapsulated by quotes... find the real closing quote...
613 while (
614 $nextQuotePosition !== false
615 && $nextQuotePosition + 1 != strlen($importLines[$i])
616 && substr($importLines[$i], $nextQuotePosition + 1, 1) != $this->separator
617 ) {
618 $nextQuotePosition = strpos($importLines[$i], '"', $nextQuotePosition + 1);
619 }
620
621 // If we didn't find the whole value, then concatenate the current line with the next one
622 if ($nextQuotePosition === false) {
623 // If end of import file, error...
624 if (!isset($importLines[$j])) {
625 break;
626 }
627
628 $importLines[$i] .= "\n".$importLines[$j];
629 $importLines[$i] = rtrim($importLines[$i], $this->separator);
630 unset($importLines[$j]);
631 $j++;
632 } else {
633 // Found the entire value, add it to data and move the position to the next separator
634
635 // If the quote is at the end of the line and the line is completed...
636 if (strlen($importLines[$i]) - 1 == $nextQuotePosition) {
637 $data[] = substr($importLines[$i], $position + 1);
638 break;
639 }
640 // ,"toto",value...
641 // 3456789
642 // Start in 3+1 so 4: the first quote
643 // Length = 9+1-(3+1) = 6, the length of "toto".
644 $data[] = substr($importLines[$i], $position + 1, $nextQuotePosition + 1 - ($position + 1));
645 $position = $nextQuotePosition + 1;
646 }
647 } else {
648 // If not encapsulated by quotes, search the next separator
649 $nextSeparatorPosition = strpos($importLines[$i], $this->separator, $position + 1);
650 // If not found, the line is completed
651 if ($nextSeparatorPosition === false) {
652 $data[] = substr($importLines[$i], $position + 1);
653 break;
654 } else { // If found the next separator, add the value in $data and change the position
655 $data[] = substr($importLines[$i], $position + 1, $nextSeparatorPosition - ($position + 1));
656 $position = $nextSeparatorPosition;
657 }
658 }
659 }
660
661 // We unset some lines, don't forget to change the remaining lines keys
662 $importLines = array_merge($importLines);
663 } else {
664 //We remove the separators from the end of the string as we don't need them... they will be added as empty string anyway during the import process.
665 $data = explode($this->separator, rtrim(trim($importLines[$i]), $this->separator));
666 }
667
668 //We clean it... maybe there are other arguments at the end we should remove
669 if (!empty($this->separatorsToRemove)) {
670 for ($b = $numberColumns + $this->separatorsToRemove - 1; $b >= $numberColumns; $b--) {
671 if (isset($data[$b]) && (strlen($data[$b]) == 0 || $data[$b] == ' ')) {
672 unset($data[$b]);
673 }
674 }
675 }
676
677 $i++;
678 //We don't handle empty lines...
679 if (empty($importLines[$i - 1])) {
680 continue;
681 }
682
683 $this->totalTry++;
684 //Lets try to fix it first...
685 if (count($data) > $numberColumns) {
686 $copy = $data;
687 foreach ($copy as $oneelem => $oneval) {
688 if (
689 !empty($oneval[0])
690 && $oneval[0] === '"'
691 && $oneval[strlen($oneval) - 1] !== '"'
692 && isset($copy[$oneelem + 1])
693 && $copy[$oneelem + 1][strlen($copy[$oneelem + 1]) - 1] == '"'
694 ) {
695 //We concat both with the separator
696 $data[$oneelem] = $copy[$oneelem].$this->separator.$copy[$oneelem + 1];
697 unset($data[$oneelem + 1]);
698 }
699 }
700
701 $data = array_values($data);
702 }
703
704 // Not enough columns found...
705 if (count($data) < $numberColumns) {
706 // If not enough info compared to the header... lets add them as empty! We don't care if the user does not specify everything...
707 for ($a = count($data); $a < $numberColumns; $a++) {
708 $data[$a] = '';
709 }
710 }
711
712 if (count($data) != $numberColumns) {
713 static $errorcount = 0;
714 if (empty($errorcount)) {
715 acym_enqueueMessage(acym_translationSprintf('ACYM_IMPORT_ARGUMENTS', $numberColumns), 'warning');
716 }
717 $errorcount++;
718
719 //If it's for the first line, we return so we don't check the rest and let the user fix it
720 if ($this->totalTry == 1) {
721 return;
722 }
723 if (empty($errorLines)) {
724 $errorLines[] = 'error,'.$importLines[0];
725 }
726 $errorLines[] = acym_translation('ACYM_IMPORT_ERROR_WRONG_NUMBER_ARGUMENTS').','.$importLines[$i - 1];
727 continue;
728 }
729
730 $newUser = new \stdClass();
731 $newUser->customfields = [];
732
733 // Handle email column first to be able to use it with listids and listname
734 $emailKey = array_search('email', $this->columns);
735 $newUser->email = trim(acym_stripTags($data[$emailKey]), '\'" ');
736 // Remove all whitespace type
737 $newUser->email = preg_replace("/\s+/u", '', $newUser->email);
738 if (!empty($newUser->email)) {
739 $newUser->email = acym_punycode($newUser->email);
740 }
741 $newUser->email = trim(str_replace([' ', "\t"], '', $encodingHelper->change($newUser->email, 'UTF-8', 'ISO-8859-1')));
742
743
744 if (!acym_isValidEmail($newUser->email)) {
745 static $errorcountfail = 0;
746 //We limit to 10 errors otherwise it may break the Joomla messaging system in terms of memory usage
747 if ($errorcountfail == 0) {
748 acym_enqueueMessage(acym_translation('ACYM_ADDRESSES_INVALID'), 'warning');
749 }
750 $errorcountfail++;
751 if (empty($errorLines)) {
752 $errorLines[] = 'error,'.$importLines[0];
753 }
754 $errorLines[] = acym_translation('ACYM_INVALID_EMAIL_ADDRESS').','.$importLines[$i - 1];
755 continue;
756 }
757
758 foreach ($data as $num => $value) {
759 // Already handled the email address
760 if ($num == $emailKey) continue;
761
762 $field = $this->columns[$num];
763
764 // Ignored
765 if ($field == 1) continue;
766
767 if ($field === 'listids') {
768 //We explode the listids separated by "-" and forach of them we add the user in the importUserInLists
769 $liststosub = explode('-', trim($value, '\'" '));
770 foreach ($liststosub as $onelistid) {
771 $this->importUserInLists[intval(trim($onelistid))][] = acym_escapeDB($newUser->email);
772 }
773 continue;
774 }
775
776 if ($field === 'listname') {
777 $liststosub = explode('-', trim($value, '\'" '));
778 foreach ($liststosub as $onelistName) {
779 if (empty($onelistName)) {
780 continue;
781 }
782 $onelistName = trim($onelistName);
783 if (empty($allLists[$onelistName])) {
784 $newList = new \stdClass();
785 $newList->name = $onelistName;
786 $newList->active = 1;
787 $colors = ['#3366ff', '#7240A4', '#7A157D', '#157D69', '#ECE649'];
788 $newList->color = $colors[acym_rand(0, count($colors) - 1)];
789 $listid = $listClass->save($newList);
790 $newList->id = $listid;
791 $allLists[$onelistName] = $newList;
792 }
793 $this->importUserInLists[intval($allLists[$onelistName]->id)][] = acym_escapeDB($newUser->email);
794 }
795 continue;
796 }
797
798 if (strpos($field, 'subscription_date_') === 0 || strpos($field, 'unsubscription_date_') === 0) {
799 $value = trim($value, '"');
800 if (strpos($field, 'subscription_date_') === 0) {
801 $listId = (int)str_replace('subscription_date_', '', $field);
802 $dateType = 'subscribed_date';
803 } else {
804 $listId = (int)str_replace('unsubscription_date_', '', $field);
805 $dateType = 'unsubscribed_date';
806 }
807
808 if (!isset($this->subscriptionHistory[$listId])) {
809 $this->subscriptionHistory[$listId] = [];
810 }
811
812 $email = $newUser->email;
813 $userFound = false;
814
815 foreach ($this->subscriptionHistory[$listId] as &$entry) {
816 if ($entry['email'] === $email) {
817 $entry[$dateType] = $value;
818 $userFound = true;
819 break;
820 }
821 }
822 unset($entry);
823
824 if (!$userFound) {
825 $this->subscriptionHistory[$listId][] = [
826 'email' => $email,
827 $dateType => $value,
828 ];
829 }
830
831 continue;
832 }
833
834
835 // If we assigned the data to an Acy custom field
836 if (strpos($field, 'cf_') === 0) {
837 $newUser->customfields[substr($field, 3)] = trim(acym_stripTags($value), '\'" ');
838 continue;
839 }
840
841 if ($value === 'null') {
842 $newUser->$field = '';
843 } else {
844 //We remove anything which should not be there (quotes or spaces or return char or html tags)
845 $newUser->$field = trim(acym_stripTags($value), '\'" ');
846 }
847 }
848
849 if (acym_isMultilingual() && empty($newUser->language)) {
850 $newUser->language = $this->config->get('multilingual_default', '');
851 }
852
853 //Everything is Ok... we can add the line
854 $importUsers[] = $newUser;
855 $this->totalValid++;
856
857 //Every 50 users, we handle it
858 if ($this->totalValid % 50 == 0) {
859 $this->insertUsers($importUsers, $timestamp);
860 $importUsers = [];
861 }
862 }
863
864 if (!empty($errorLines)) {
865 $filename = strtolower(acym_getVar('cmd', 'acym_import_filename', ''));
866 if (!empty($filename)) {
867 $extension = '.'.acym_fileGetExt($filename);
868 $filename = str_replace(['.', ' '], '_', substr($filename, 0, strpos($filename, $extension))).$extension;
869 $errorFile = implode("\n", $errorLines);
870 acym_writeFile(ACYM_MEDIA.'import'.DS.'error_'.$filename, $errorFile);
871 acym_enqueueMessage(
872 '<a target="_blank" href="'.acym_prepareAjaxURL((acym_isAdmin() ? '' : 'front').'users&task=downloadImport').'&filename=error_'.preg_replace(
873 '#\.[^.]*$#',
874 '',
875 $filename
876 ).'&'.acym_noTemplate().'" >'.acym_translation('ACYM_DOWNLOAD_IMPORT_ERRORS').'</a>',
877 'notice'
878 );
879 }
880 }
881
882 $this->insertUsers($importUsers, $timestamp);
883
884 acym_query('UPDATE #__acym_configuration SET `value` = '.intval($timestamp).' WHERE `name` = \'last_import\'');
885
886 // We could have imported empty values
887 acym_query('DELETE FROM #__acym_user_has_field WHERE `value` = ""');
888
889 $countUsersAfterImport = $userClass->getCountTotalUsers();
890 $totalInserted = $countUsersAfterImport - $countUsersBeforeImport;
891
892 $maybeUpdated = acym_translation('ACYM_NOT_UPDATED');
893 if ($this->overwrite) {
894 $maybeUpdated = acym_translation('ACYM_UPDATED');
895 }
896 $reportMsg = acym_translationSprintf(
897 'ACYM_IMPORT_REPORTING_UPDATED',
898 $this->totalTry,
899 $totalInserted,
900 $this->totalTry - $this->totalValid,
901 $this->totalValid - $totalInserted,
902 $maybeUpdated
903 );
904 acym_enqueueMessage($reportMsg, 'info');
905
906 $this->insertSubscriptionHistory();
907
908 // All users have been added properly into the database... we will now subscribe the users
909 $this->subscribeUsers();
910 }
911
912 private function insertUsers(array $users, int $timestamp): void
913 {
914 if (empty($users)) {
915 return;
916 }
917
918 $importedCols = array_keys(get_object_vars($users[0]));
919 unset($importedCols[array_search('customfields', $importedCols)]);
920 if ($this->forceConfirm) $importedCols[] = 'confirmed';
921
922 foreach ($users as $a => $oneUser) {
923 $this->checkData($users[$a], $timestamp);
924 }
925
926 $columns = reset($users);
927 $colNames = array_keys(get_object_vars($columns));
928 unset($colNames[array_search('customfields', $colNames)]);
929
930 if (!in_array('key', $colNames)) $colNames[] = 'key';
931
932 foreach ($colNames as $oneColumn) {
933 acym_secureDBColumn($oneColumn);
934 }
935
936 $queryInsertUsers = 'INSERT'.($this->overwrite ? '' : ' IGNORE').' INTO #__acym_user (`'.implode('`,`', $colNames).'`) VALUES (';
937 $values = [];
938 $customFieldsvalues = [];
939 $allemails = [];
940 foreach ($users as $oneUser) {
941 $value = [];
942 acym_trigger('onAcymBeforeUserImport', [&$oneUser]);
943
944 foreach ($oneUser as $map => $oneValue) {
945 if ($map === 'customfields') continue;
946
947 $oneValue = htmlspecialchars_decode($oneValue, ENT_QUOTES);
948
949 if ($map !== 'id') {
950 $oneValue = acym_escapeDB($oneValue);
951 if ($map === 'email') {
952 $allemails[] = $oneValue;
953 }
954 } else {
955 $oneValue = intval($oneValue);
956 }
957
958 $value[] = $oneValue;
959 }
960
961 if (!isset($oneUser->key)) $value[] = acym_escapeDB(acym_generateKey(14));
962 $values[] = implode(',', $value);
963
964 // Prepare import custom fields
965 if (!empty($oneUser->customfields)) $customFieldsvalues[$oneUser->email] = $oneUser->customfields;
966 }
967 acym_trigger('onAcymUserImport', [&$users]);
968
969 $queryInsertUsers .= implode('),(', $values).')';
970
971 if ($this->overwrite) {
972 $queryInsertUsers .= ' ON DUPLICATE KEY UPDATE ';
973 foreach ($importedCols as &$oneColumn) {
974 acym_secureDBColumn($oneColumn);
975 if ($oneColumn == 'key') {
976 $oneColumn = '`'.$oneColumn.'` = `'.$oneColumn.'`';
977 } else {
978 $oneColumn = '`'.$oneColumn.'` = VALUES(`'.$oneColumn.'`)';
979 }
980 }
981 $queryInsertUsers .= implode(',', $importedCols);
982 }
983
984 acym_query($queryInsertUsers);
985
986 $importedUsers = acym_loadObjectList('SELECT id, email FROM #__acym_user WHERE email IN ('.implode(',', $allemails).')', 'id');
987
988 $this->importedUsersByEmail = array_combine(array_column($importedUsers, 'email'), array_column($importedUsers, 'id'));
989
990 if (!empty($customFieldsvalues)) {
991 $insertValues = [];
992 foreach ($importedUsers as $one) {
993 if (empty($customFieldsvalues[$one->email])) continue;
994
995 foreach ($customFieldsvalues[$one->email] as $fieldId => $value) {
996 $value = htmlspecialchars_decode($value, ENT_QUOTES);
997 $insertValues[] = '('.intval($one->id).','.intval($fieldId).','.acym_escapeDB($value).')';
998 }
999 }
1000
1001 if (!empty($insertValues)) {
1002 $queryInsertCustomFields = 'INSERT'.($this->overwrite ? '' : ' IGNORE').' INTO #__acym_user_has_field (`user_id`, `field_id`, `value`) VALUES '.implode(
1003 ',',
1004 $insertValues
1005 );
1006 if ($this->overwrite) {
1007 $queryInsertCustomFields .= ' ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)';
1008 }
1009 acym_query($queryInsertCustomFields);
1010 }
1011 }
1012
1013
1014 $this->allUserIds = array_merge($this->allUserIds, array_keys($importedUsers));
1015 }
1016
1017 private function checkData(object &$user, int $timestamp)
1018 {
1019 //Created field
1020 if (empty($user->creation_date)) {
1021 $user->creation_date = time();
1022 }
1023
1024 if (is_numeric($user->creation_date)) {
1025 $user->creation_date = gmdate('Y-m-d H:i:s', $user->creation_date);
1026 }
1027
1028 if (!isset($user->active) || strlen($user->active) == 0) {
1029 $user->active = 1;
1030 }
1031
1032 if ((!isset($user->confirmed) || strlen($user->confirmed) == 0) && $this->forceConfirm) {
1033 $user->confirmed = 1;
1034 }
1035
1036 if (empty($user->source)) {
1037 $user->source = 'Import on '.acym_date($timestamp, 'Y-m-d H:i:s');
1038 }
1039
1040 //name field
1041 if (empty($user->name) && $this->generateName) {
1042 $user->name = ucwords(trim(str_replace(['.', '_', '-', 1, 2, 3, 4, 5, 6, 7, 8, 9, 0], ' ', substr($user->email, 0, strpos($user->email, '@')))));
1043 }
1044 }
1045
1046 private function autoDetectHeader(): bool
1047 {
1048 $this->separator = ',';
1049
1050 // Remove BOM characters if there is one
1051 $this->header = str_replace("\xEF\xBB\xBF", '', $this->header);
1052
1053 // Detect the separator
1054 $listSeparators = ["\t", ';', ','];
1055 foreach ($listSeparators as $sep) {
1056 if (strpos($this->header, $sep) !== false) {
1057 $this->separator = $sep;
1058 break;
1059 }
1060 }
1061
1062 $this->columns = explode($this->separator, $this->header);
1063
1064 // Clean the headers
1065 for ($i = count($this->columns) - 1; $i >= 0; $i--) {
1066 if (strlen($this->columns[$i]) == 0) {
1067 unset($this->columns[$i]);
1068 $this->separatorsToRemove++;
1069 }
1070 }
1071
1072 $columns = acym_getColumns('user');
1073 foreach ($columns as $i => $oneColumn) {
1074 $columns[$i] = strtolower($oneColumn);
1075 }
1076
1077 foreach ($this->columns as $i => $oneColumn) {
1078 $this->columns[$i] = strtolower(trim($oneColumn, '\'" '));
1079 if (in_array($this->columns[$i], ['listids', 'listname'])) continue;
1080 if (strpos($this->columns[$i], 'cf_') === 0) continue;
1081 if (strpos($this->columns[$i], 'subscription_date_') === 0) continue;
1082 if (strpos($this->columns[$i], 'unsubscription_date_') === 0) continue;
1083
1084 if (!in_array($this->columns[$i], $columns) && $this->columns[$i] != 1) {
1085 acym_enqueueMessage(
1086 acym_translationSprintf(
1087 'ACYM_IMPORT_ERROR_FIELD',
1088 '<b>'.esc_html($this->columns[$i]).'</b>',
1089 '<b>'.implode('</b> | <b>', array_diff($columns, ['id', 'cms_id'])).'</b>'
1090 ),
1091 'error'
1092 );
1093
1094 return false;
1095 }
1096 }
1097
1098 if (!in_array('email', $this->columns)) {
1099 return false;
1100 }
1101
1102 return true;
1103 }
1104
1105 private function cleanImportFolder(): void
1106 {
1107 $files = acym_getFiles(ACYM_MEDIA.'import', '.', false, true, []);
1108 foreach ($files as $oneFile) {
1109 if (acym_fileGetExt($oneFile) != 'csv') {
1110 continue;
1111 }
1112 if (filectime($oneFile) < time() - 86400) {
1113 acym_deleteFile($oneFile);
1114 }
1115 }
1116 }
1117
1118 private function subscribeUsers(): void
1119 {
1120 //All users are known as : $this->allUserIds
1121 if (empty($this->allUserIds)) {
1122 return;
1123 }
1124
1125 $subdate = gmdate('Y-m-d H:i:s', time());
1126
1127 $listClass = new ListClass();
1128 $lists = $this->getImportedLists();
1129
1130 if (!acym_isAdmin() && 'joomla' === ACYM_CMS) {
1131 $listClass = new ListClass();
1132 $listManagementId = $listClass->getfrontManagementList();
1133 if (empty($listManagementId)) {
1134 acym_redirect(acym_rootURI(), 'ACYM_UNABLE_TO_CREATE_MANAGEMENT_LIST', 'error');
1135 }
1136
1137 if (empty($lists)) {
1138 $lists = [$listManagementId => 1];
1139 } else {
1140 $lists[$listManagementId] = 1;
1141 }
1142 }
1143
1144 if (!empty($this->importUserInLists)) {
1145 //We import the users based on the subscription of each of them, specified in the listids or listname columns...
1146 foreach ($this->importUserInLists as $listid => $arrayEmails) {
1147 if (empty($listid)) continue;
1148
1149 $listid = (int)$listid;
1150 $query = 'INSERT IGNORE INTO #__acym_user_has_list (`list_id`,`user_id`,`subscription_date`,`status`) ';
1151 $query .= 'SELECT '.intval($listid).',`id`,'.acym_escapeDB($subdate).',1 FROM #__acym_user WHERE `email` IN (';
1152 $query .= implode(',', $arrayEmails).')';
1153 $nbsubscribed = acym_query($query);
1154 $nbsubscribed = intval($nbsubscribed);
1155
1156 if (isset($this->subscribedUsers[$listid])) {
1157 $this->subscribedUsers[$listid]->nbusers += $nbsubscribed;
1158 } else {
1159 $myList = $listClass->getOneById($listid);
1160 $this->subscribedUsers[$listid] = $myList;
1161 $this->subscribedUsers[$listid]->nbusers = $nbsubscribed;
1162 }
1163 }
1164 }
1165
1166 if (!empty($lists)) {
1167 //We do one query per list to avoid problem with huge queries and so in the mean time we can display info for each list
1168 foreach ($lists as $listid => $val) {
1169 if (empty($val)) {
1170 continue;
1171 }
1172
1173 if ($val == -1) {
1174 $dateColumn = 'unsubscribe_date';
1175 $status = 0;
1176 } else {
1177 $dateColumn = 'subscription_date';
1178 $status = 1;
1179 }
1180
1181 $nbsubscribed = 0;
1182 $listid = intval($listid);
1183 $query = 'INSERT IGNORE INTO #__acym_user_has_list (`list_id`,`user_id`,`'.$dateColumn.'`,`status`) VALUES ';
1184 $b = 0;
1185 $currentSubids = [];
1186 foreach ($this->allUserIds as $subid) {
1187 $subid = intval($subid);
1188 $currentSubids[] = $subid;
1189 $b++;
1190
1191 if ($b > 200) {
1192 $query = rtrim($query, ',');
1193 if ($val == -1) {
1194 $query .= ' ON DUPLICATE KEY UPDATE status = 0';
1195 $nbsubscribed = -acym_loadResult(
1196 'SELECT COUNT(*) FROM #__acym_listsub WHERE `list_id` = '.intval($listid).' AND status != -1 AND `user_id` IN ('.implode(',', $currentSubids).')'
1197 );
1198 }
1199 $affected = acym_query($query);
1200 $nbsubscribed += intval($affected);
1201 $b = 0;
1202 $currentSubids = [];
1203 $query = 'INSERT IGNORE INTO #__acym_user_has_list (`list_id`,`user_id`,`'.$dateColumn.'`,`status`) VALUES ';
1204 }
1205
1206 $query .= '('.intval($listid).','.intval($subid).','.acym_escapeDB($subdate).','.$status.'),';
1207 }
1208 $query = rtrim($query, ',');
1209 if ($val == -1) {
1210 $query .= ' ON DUPLICATE KEY UPDATE status = 0';
1211 // It could be empty if we imported exactly 200, 400, 600... users
1212 if (!empty($currentSubids)) {
1213 $nbsubscribed = -acym_loadResult(
1214 'SELECT COUNT(*) FROM #__acym_listsub WHERE `list_id` = '.intval($listid).' AND status != -1 AND `user_id` IN ('.implode(',', $currentSubids).')'
1215 );
1216 }
1217 }
1218
1219 $affected = acym_query($query);
1220 $nbsubscribed += intval($affected);
1221
1222 if (isset($this->subscribedUsers[$listid])) {
1223 $this->subscribedUsers[$listid]->nbusers += $nbsubscribed;
1224 } else {
1225 $myList = $listClass->getOneById($listid);
1226 $myList->status = $val;
1227 $this->subscribedUsers[$listid] = $myList;
1228 $this->subscribedUsers[$listid]->nbusers = $nbsubscribed;
1229 }
1230 }
1231 }
1232 }
1233
1234 public function insertSubscriptionHistory(): void
1235 {
1236 if (empty($this->subscriptionHistory)) {
1237 return;
1238 }
1239
1240 $listClass = new ListClass();
1241
1242 $userIds = array_values($this->importedUsersByEmail);
1243 $listIds = array_keys($this->subscriptionHistory);
1244
1245 $existingEntries = $listClass->getUserListEntries($userIds, $listIds);
1246
1247 foreach ($this->subscriptionHistory as $listId => $entries) {
1248 foreach ($entries as $entry) {
1249 if (empty($entry['subscribed_date']) && empty($entry['unsubscribed_date'])) {
1250 continue;
1251 }
1252
1253 $userId = $this->importedUsersByEmail[$entry['email']] ?? null;
1254 if (empty($userId)) {
1255 continue;
1256 }
1257
1258 $key = $userId.'_'.$listId;
1259 $existing = $existingEntries[$key] ?? null;
1260
1261 $subscriptionDate = !empty($entry['subscribed_date']) ? acym_escapeDB($entry['subscribed_date']) : 'NULL';
1262 $unsubscribeDate = !empty($entry['unsubscribed_date']) ? acym_escapeDB($entry['unsubscribed_date']) : 'NULL';
1263
1264 if (!empty($existing)) {
1265 if ($existing->status === 0) {
1266 continue;
1267 }
1268
1269 $fieldsToUpdate = [];
1270
1271 if (empty($existing->subscription_date)) {
1272 if ($unsubscribeDate != 'NULL' && ($subscriptionDate != 'NULL' && $unsubscribeDate > $subscriptionDate)) {
1273 $fieldsToUpdate[] = 'status ='. 0;
1274 $fieldsToUpdate[] = 'unsubscribe_date ='.$unsubscribeDate;
1275 }
1276
1277 $fieldsToUpdate[] = 'subscription_date ='.$subscriptionDate;
1278 } else {
1279 if ($unsubscribeDate != 'NULL' && ($existing->subscription_date != 'NULL' && $unsubscribeDate > $existing->subscription_date)) {
1280 $fieldsToUpdate[] = 'status ='. 0;
1281 $fieldsToUpdate[] = 'unsubscribe_date ='.$unsubscribeDate;
1282 }
1283 }
1284
1285 if (!empty($fieldsToUpdate)) {
1286 $query = 'UPDATE #__acym_user_has_list SET '.implode(', ', $fieldsToUpdate)
1287 .' WHERE user_id = '.$userId.' AND list_id = '.$listId;
1288 acym_query($query);
1289 }
1290 } else {
1291 if ($subscriptionDate === 'NULL' && $unsubscribeDate !== 'NULL') {
1292 $subscriptionDate = $unsubscribeDate;
1293 }
1294
1295 $status = ($unsubscribeDate !== 'NULL') ? 0 : 1;
1296
1297 $insertQuery = 'INSERT INTO #__acym_user_has_list (`list_id`, `user_id`, `subscription_date`, `status`, `unsubscribe_date`) VALUES '
1298 .'('.$listId.','.$userId.','.$subscriptionDate.','.$status.','.$unsubscribeDate.')';
1299 acym_query($insertQuery);
1300 }
1301 }
1302 }
1303 }
1304 }
1305