PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 10.11.1
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v10.11.1
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 / ExportHelper.php

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

578 lines 19.5 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\MailClass;
7 use AcyMailing\Core\AcymObject;
8
9 class ExportHelper extends AcymObject
10 {
11 private string $eol = "\r\n";
12 private string $before = '"';
13 private string $after = '"';
14
15 public function setDownloadHeaders(string $filename = 'export', string $extension = 'csv'): void
16 {
17 acym_header('Pragma: public');
18 acym_header('Expires: 0');
19 acym_header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
20
21 acym_header('Content-Type: application/force-download');
22 acym_header('Content-Type: application/octet-stream');
23 acym_header('Content-Type: application/download');
24
25 acym_header('Content-Disposition: attachment; filename='.$filename.'.'.$extension);
26 acym_header('Content-Transfer-Encoding: binary');
27 }
28
29 public function exportTemplate(object $template): void
30 {
31 $name = preg_replace('#[^a-z0-9]#Uis', '_', $template->name);
32 $name = preg_replace('#_+#s', '_', $name);
33 $exportFolder = ACYM_ROOT.ACYM_MEDIA_FOLDER.'tmp'.DS.$name;
34 acym_createFolder($exportFolder);
35
36 $template->body = acym_absoluteURL($template->body);
37 $images = [];
38 preg_match_all('#<img[^>]* src="([^"]+)"#Uis', $template->body, $images);
39
40 if (!empty($images[1])) {
41 $imagesFolder = $exportFolder.DS.'images';
42 acym_createFolder($imagesFolder);
43
44 $replace = [];
45 foreach ($images[1] as $oneImage) {
46 if (isset($replace[$oneImage])) continue;
47
48 $location = str_replace(
49 [ACYM_LIVE, '/'],
50 [ACYM_ROOT, DS],
51 $oneImage
52 );
53 if (strpos($location, 'http') === 0) continue;
54
55 if (!file_exists($location)) continue;
56
57 $filename = basename($location);
58 while (file_exists($imagesFolder.DS.$filename)) {
59 $filename = rand(0, 99).$filename;
60 }
61
62 acym_copyFile($location, $imagesFolder.DS.$filename);
63 $replace[$oneImage] = 'images/'.$filename;
64 }
65
66 $template->body = str_replace(array_keys($replace), $replace, $template->body);
67 }
68
69 $structure = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
70 <html xmlns="http://www.w3.org/1999/xhtml">
71 <head>
72 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
73
74 $dataToCopy = [
75 'fromname',
76 'fromemail',
77 'replyname',
78 'replyemail',
79 'subject',
80 'settings',
81 ];
82 foreach ($dataToCopy as $oneData) {
83 if (empty($template->$oneData)) continue;
84 $structure .= "\n".'<meta name="'.$oneData.'" content="'.acym_escape($template->$oneData).'" />';
85 }
86
87 $structure .= "\n".'<title>'.$template->name.'</title>
88 </head>
89 <body>
90 '.$template->body.'
91 </body>
92 </html>';
93
94 acym_writeFile($exportFolder.DS.'template.html', $structure);
95
96 $thumbnail = acym_getMailThumbnail($template->thumbnail);
97 $thumbnail = str_replace(ACYM_LIVE, ACYM_ROOT, $thumbnail);
98 acym_copyFile($thumbnail, $exportFolder.DS.'thumbnail.png');
99
100 if (!empty($template->stylesheet)) {
101 acym_createFolder($exportFolder.DS.'css');
102 acym_writeFile($exportFolder.DS.'css'.DS.'custom.css', $template->stylesheet);
103 }
104
105 $zipFiles = [];
106 $folders = acym_getFolders($exportFolder, '.', true, true);
107 $folders[] = $exportFolder;
108 foreach ($folders as $folder) {
109 $files = acym_getFiles($folder, '.', false, true);
110 foreach ($files as $file) {
111 $posSlash = strrpos($file, '/');
112 $posASlash = strrpos($file, '\\');
113 $pos = ($posSlash < $posASlash) ? $posASlash : $posSlash;
114 if (!empty($pos)) $file = substr_replace($file, DS, $pos, 1);
115
116 $data = acym_fileGetContent($file);
117 $zipFiles[] = [
118 'name' => str_replace(
119 $exportFolder.DS,
120 '',
121 $file
122 ),
123 'data' => $data,
124 ];
125 }
126 }
127
128 acym_createArchive($exportFolder, $zipFiles);
129
130 acym_deleteFolder($exportFolder);
131
132 $this->setDownloadHeaders($name, 'zip');
133 echo acym_fileGetContent($exportFolder.'.zip');
134 acym_deleteFile($exportFolder.'.zip');
135
136 exit;
137 }
138
139 public function exportStatsFormattedCSV(string $mailName, array $globalDonutsData, array $globaline, string $timeLinechart): void
140 {
141 acym_displayErrors();
142
143 if ($timeLinechart === 'month') {
144 $timeLinechart = acym_translation('ACYM_MONTHLY_STATS');
145 } elseif ($timeLinechart === 'week') {
146 $timeLinechart = acym_translation('ACYM_WEEKLY_STATS');
147 } else {
148 $timeLinechart = acym_translation('ACYM_DAILY_STATS');
149 }
150
151 $separator = '","';
152
153 $csvLines = [];
154 $csvLines[] = $this->before.$mailName.$this->after;
155 $csvLines[] = $this->eol;
156
157 $globalDonutsTitle = [
158 acym_translation('ACYM_SUCCESSFULLY_SENT'),
159 acym_translation('ACYM_OPEN_RATE'),
160 acym_translation('ACYM_CLICK_RATE'),
161 acym_translation('ACYM_BOUNCE_RATE'),
162 acym_translation('ACYM_UNSUBSCRIBE_RATE'),
163 ];
164
165 $csvLines[] = $this->before.implode($separator, $globalDonutsTitle).$this->after;
166 $csvLines[] = $this->before.implode($separator, $globalDonutsData).$this->after;
167 $csvLines[] = $this->eol;
168
169 $csvLines[] = $this->before.$timeLinechart.$separator.acym_translation('ACYM_OPEN').$separator.acym_translation('ACYM_CLICK').$this->after;
170
171 foreach ($globaline as $date => $value) {
172 $csvLines[] = $this->before.$date.$separator.$value['open'].$separator.$value['click'].$this->after;
173 }
174
175 $this->finishStatsExport($csvLines);
176 }
177
178 public function exportStatsFullCSV(string $query, array $columns, string $type = 'global'): void
179 {
180 $nbExport = $this->getExportLimit();
181 $mailsStats = acym_loadObjectList($query.' LIMIT '.intval($nbExport));
182 $mailClass = new MailClass();
183 $mailClass->exceptKeysDecode = ['name'];
184 $mailsStats = $mailClass->decode($mailsStats);
185 acym_displayErrors();
186
187 $csvSeparator = $this->config->get('csv_separator', ',');
188 $separator = '"'.$csvSeparator.'"';
189 $csvLines = [];
190
191 $csvLines[] = $this->before.implode($separator, $columns).$this->after;
192
193 $valueNeedNumber = ['click'];
194
195 foreach ($mailsStats as $mailStat) {
196 $oneLine = [];
197 foreach ($columns as $key => $trad) {
198 $key = strpos($key, '.') !== false ? explode('.', $key) : $key;
199 if (is_array($key)) {
200 $key = $key[1];
201 }
202
203 $line = in_array($key, $valueNeedNumber) && empty($mailStat->{$key}) ? 0 : $mailStat->{$key};
204 if (is_null($line)) {
205 $line = '';
206 }
207
208 if ($key === 'bounce_details' && !empty($line)) {
209 $line = @unserialize($line);
210 if ($line !== false) {
211 $formattedLine = '';
212 foreach ($line as $detailKey => $detailValue) {
213 $formattedLine .= $detailKey.': '.$detailValue.'; ';
214 }
215 $line = rtrim($formattedLine, '; ');
216 }
217 }
218
219 if (in_array($key, ['send_date', 'open_date', 'date_click']) && !empty($line)) {
220 $line = acym_date(acym_getTimeFromUTCDate($line), 'Y-m-d H:i:s');
221 }
222
223 $oneLine[] = htmlspecialchars($line, ENT_QUOTES, 'UTF-8');
224 }
225 $csvLines[] = $this->before.implode($separator, $oneLine).$this->after;
226 }
227
228 $this->finishStatsExport($csvLines, $type);
229 }
230
231 private function finishStatsExport(array $csvLines, string $type = 'global'): void
232 {
233 $final = implode($this->eol, $csvLines);
234
235 if (ACYM_CMS === 'wordpress') {
236 @ob_get_clean();
237 }
238 $filename = 'export_stats_'.$type.'_'.date('Y-m-d');
239 $this->setDownloadHeaders($filename);
240 echo $final;
241 }
242
243 private function getLists(): array
244 {
245 static $lists = null;
246 if (empty($lists)) {
247 $listClass = new ListClass();
248 $lists = $listClass->getAllWithIdName();
249 }
250
251 return $lists;
252 }
253
254 public function exportCSV(
255 string $query,
256 array $fieldsToExport,
257 array $customFieldsToExport,
258 array $specialFieldsToExport,
259 string $separator = ',',
260 string $charset = 'UTF-8',
261 string $exportFile = '',
262 int $flagToRemove = 0
263 ): string {
264 $nbExport = $this->getExportLimit();
265 acym_displayErrors();
266 $encodingClass = new EncodingHelper();
267 $excelSecure = $this->config->get('export_excelsecurity', 0);
268
269 if (!in_array($separator, [',', ';'])) $separator = ',';
270 $separator = '"'.$separator.'"';
271
272 $allFieldsToExport = array_merge($fieldsToExport, $customFieldsToExport);
273
274 foreach ($allFieldsToExport as $key => $field) {
275 $allFieldsToExport[$key] = $encodingClass->change($field, 'UTF-8', $charset);
276 }
277
278 if (in_array('subscribe_date', $specialFieldsToExport)) {
279 $lists = $this->getLists();
280 foreach ($lists as $oneList) {
281 $allFieldsToExport[] = acym_translation('ACYM_SUBSCRIPTION_DATE').' '.acym_translation($oneList);
282 }
283 }
284
285 if (in_array('unsubscribe_date', $specialFieldsToExport)) {
286 $lists = $this->getLists();
287 foreach ($lists as $oneList) {
288 $allFieldsToExport[] = acym_translation('ACYM_UNSUBSCRIPTION_DATE').' '.acym_translation($oneList);
289 }
290 }
291
292 $firstLine = $this->before.implode($separator, $allFieldsToExport).$this->after.$this->eol;
293
294 if (empty($exportFile)) {
295 @ob_get_clean();
296 $filename = 'export_'.date('Y-m-d');
297 $this->setDownloadHeaders($filename);
298 echo $firstLine;
299 } else {
300 preg_match('#^(.+/)[^/]+$#', $exportFile, $folder);
301 if (!empty($folder[1]) && !file_exists($folder[1])) acym_createDir($folder[1]);
302
303 $fp = fopen($exportFile, 'w');
304 if (false === $fp) {
305 if ($flagToRemove !== 0) {
306 $automationHelper = new AutomationHelper();
307 $automationHelper->removeFlag($flagToRemove);
308 }
309
310 return acym_translationSprintf('ACYM_FAIL_SAVE_FILE', $exportFile);
311 }
312
313 $error = fwrite($fp, $firstLine);
314 if (false === $error) {
315 if ($flagToRemove !== 0) {
316 $automationHelper = new AutomationHelper();
317 $automationHelper->removeFlag($flagToRemove);
318 }
319
320 return acym_translationSprintf('ACYM_UNWRITABLE_FILE', $exportFile);
321 }
322 }
323
324 $start = 0;
325 do {
326 try {
327 $users = acym_loadObjectList($query.' LIMIT '.intval($start).', '.intval($nbExport), 'id');
328 } catch (\Exception $e) {
329 $users = false;
330 }
331 $start += $nbExport;
332
333 if ($users === false) {
334 $errorLine = $this->eol.$this->eol.'Error: '.(isset($e) ? $e->getMessage() : acym_getDBError());
335
336 if (empty($exportFile)) {
337 echo $errorLine;
338 } else {
339 $error = fwrite($fp, $errorLine);
340 if (false === $error) {
341 if ($flagToRemove !== 0) {
342 $automationHelper = new AutomationHelper();
343 $automationHelper->removeFlag($flagToRemove);
344 }
345
346 return acym_translationSprintf('ACYM_UNWRITABLE_FILE', $exportFile);
347 }
348 }
349 }
350
351 if (empty($users)) break;
352
353 foreach ($users as $userID => $oneUser) {
354 unset($oneUser->id);
355
356 $data = get_object_vars($oneUser);
357
358 if (!empty($customFieldsToExport)) {
359 $fieldIDs = array_keys($customFieldsToExport);
360 acym_arrayToInteger($fieldIDs);
361
362 $userCustomFields = acym_loadObjectList(
363 'SELECT `field_id`, `value`
364 FROM #__acym_user_has_field
365 WHERE user_id = '.intval($userID).' AND field_id IN ('.implode(',', $fieldIDs).')',
366 'field_id'
367 );
368
369 foreach ($customFieldsToExport as $fieldID => $fieldName) {
370 if (empty($userCustomFields[$fieldID])) {
371 $data[] = '';
372 } else {
373 $data[] = acym_triggerCmsHook(
374 'onUserExportFieldData',
375 [$userCustomFields[$fieldID]->value, $fieldID, $fieldName],
376 false
377 );
378 }
379 }
380 unset($userCustomFields);
381 }
382
383 if (isset($lists)) {
384 $userSubscriptions = acym_loadObjectList(
385 'SELECT `list_id`, `subscription_date`, `unsubscribe_date`
386 FROM #__acym_user_has_list
387 WHERE user_id = '.intval($userID),
388 'list_id'
389 );
390
391 if (in_array('subscribe_date', $specialFieldsToExport)) {
392 foreach ($lists as $listId => $oneList) {
393 $data[] = $userSubscriptions[$listId]->subscription_date ?? '';
394 }
395 }
396
397 if (in_array('unsubscribe_date', $specialFieldsToExport)) {
398 foreach ($lists as $listId => $oneList) {
399 $data[] = $userSubscriptions[$listId]->unsubscribe_date ?? '';
400 }
401 }
402
403 unset($userSubscriptions);
404 }
405
406 foreach ($data as &$oneData) {
407 if (is_null($oneData)) {
408 continue;
409 }
410
411 if ($excelSecure == 1) {
412 $firstCharacter = substr($oneData, 0, 1);
413 if (in_array($firstCharacter, ['=', '+', '-', '@'])) {
414 $oneData = ' '.$oneData;
415 }
416 }
417
418 $oneData = htmlspecialchars($oneData);
419 }
420
421 $dataexport = implode($separator, $data);
422 unset($data);
423
424 $oneLine = $this->before.$encodingClass->change($dataexport, 'UTF-8', $charset).$this->after.$this->eol;
425 if (empty($exportFile)) {
426 echo $oneLine;
427 } else {
428 $error = fwrite($fp, $oneLine);
429 if (false === $error) {
430 if ($flagToRemove !== 0) {
431 $automationHelper = new AutomationHelper();
432 $automationHelper->removeFlag($flagToRemove);
433 }
434
435 return acym_translationSprintf('ACYM_UNWRITABLE_FILE', $exportFile);
436 }
437 }
438 }
439
440 unset($users);
441 } while (true);
442
443 if ($flagToRemove !== 0) {
444 $automationHelper = new AutomationHelper();
445 $automationHelper->removeFlag($flagToRemove);
446 }
447
448 if (!empty($exportFile)) fclose($fp);
449
450 return '';
451 }
452
453 private function getExportLimit(): int
454 {
455 $serverLimit = acym_bytes(ini_get('memory_limit'));
456 if ($serverLimit > 500000000) {
457 return 250000;
458 } elseif ($serverLimit > 150000000) {
459 return 50000;
460 } elseif ($serverLimit > 80000000) {
461 return 15000;
462 } else {
463 return 5000;
464 }
465 }
466
467 public function getExportChangesFileName(string $year, string $month, bool $extension = true): string
468 {
469 $filename = 'export_changes_'.$year.'_'.$month;
470
471 if ($extension) {
472 $filename .= '.csv';
473 }
474
475 return $filename;
476 }
477
478 public function getExportChangesFilePath(): string
479 {
480 $filename = $this->getExportChangesFileName(acym_date('now', 'Y'), acym_date('now', 'm'));
481
482 return acym_getLogPath($filename);
483 }
484
485 public function generateExportChangesFilePathConfigChanges(): string
486 {
487 $filename = $this->getExportChangesFileName(acym_date('now', 'Y'), acym_date('now', 'm'), false);
488
489 $i = 1;
490
491 $newFilename = $filename.'_'.$i.'.csv';
492
493 while (file_exists(acym_getLogPath($newFilename))) {
494 $i++;
495 $newFilename = $filename.'_'.$i.'.csv';
496 }
497
498 return acym_getLogPath($newFilename);
499 }
500
501 public function exportChanges(array $user, array $fieldsToExport, string $fieldName, $newValue, $oldValue): void
502 {
503 if (empty($user) || empty($fieldName)) {
504 return;
505 }
506
507 $exportFullPath = $this->getExportChangesFilePath();
508
509 $separator = '","';
510 $content = '';
511
512 if (!file_exists($exportFullPath)) {
513 $columnsHeader = [];
514 foreach ($fieldsToExport as $field) {
515 $columnsHeader[] = $field;
516 }
517 $columnsHeader = array_merge(
518 $columnsHeader,
519 [
520 acym_translation('ACYM_FIELD_MODIFIED'),
521 acym_translation('ACYM_NEW_VALUE'),
522 acym_translation('ACYM_OLD_VALUE'),
523 acym_translation('ACYM_DATE'),
524 ]
525 );
526 $content .= $this->before.implode($separator, $columnsHeader).$this->after;
527 }
528
529
530 $columns = [];
531
532 foreach ($fieldsToExport as $field) {
533 $columns[] = empty($user[$field]) ? '' : $user[$field];
534 }
535
536 $columns = array_merge(
537 $columns,
538 [
539 $fieldName,
540 $newValue,
541 $oldValue,
542 acym_date(),
543 ]
544 );
545
546 $content .= $this->eol;
547 $content .= $this->before.implode($separator, $columns).$this->after;
548
549 file_put_contents(
550 $exportFullPath,
551 $content,
552 FILE_APPEND
553 );
554 }
555
556 public function cleanExportChangesFile(): void
557 {
558 $exportFolder = acym_getLogPath();
559 if (!file_exists($exportFolder)) {
560 return;
561 }
562
563 $files = scandir($exportFolder);
564 if (empty($files)) {
565 return;
566 }
567
568 $filenameToSearch = $this->getExportChangesFileName(acym_date('2 month ago', 'Y'), acym_date('2 month ago', 'm'), false);
569 foreach ($files as $file) {
570 if (strpos($file, $filenameToSearch) === false) {
571 continue;
572 }
573
574 @unlink(acym_getLogPath($file));
575 }
576 }
577 }
578