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