PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.1.0
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.1.0
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 +40 -7 10.10.2 → 11.1.0 View file →
@@ -13,16 +13,19 @@
13 13 private string $after = '"';
14 14
15 15 public function setDownloadHeaders(string $filename = 'export', string $extension = 'csv'): void
16 16 {
17 + // Fix for IE catching
17 18 acym_header('Pragma: public');
18 19 acym_header('Expires: 0');
19 20 acym_header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
20 21
22 + // force download dialog
21 23 acym_header('Content-Type: application/force-download');
22 24 acym_header('Content-Type: application/octet-stream');
23 25 acym_header('Content-Type: application/download');
24 26
27 + // Set file name and force the browser to display the save dialog
25 28 acym_header('Content-Disposition: attachment; filename='.$filename.'.'.$extension);
26 29 acym_header('Content-Transfer-Encoding: binary');
27 30 }
28 31
@@ -27,13 +30,15 @@
27 30 }
28 31
29 32 public function exportTemplate(object $template): void
30 33 {
34 + // 1 - Create a folder with the data to export
31 35 $name = preg_replace('#[^a-z0-9]#Uis', '_', $template->name);
32 36 $name = preg_replace('#_+#s', '_', $name);
33 37 $exportFolder = ACYM_ROOT.ACYM_MEDIA_FOLDER.'tmp'.DS.$name;
34 38 acym_createFolder($exportFolder);
35 39
40 + // 2 - Copy the images in the folder, and change the src in the <img> tags
36 41 $template->body = acym_absoluteURL($template->body);
37 42 $images = [];
38 43 preg_match_all('#<img[^>]* src="([^"]+)"#Uis', $template->body, $images);
39 44
@@ -49,15 +54,17 @@
49 54 [ACYM_LIVE, '/'],
50 55 [ACYM_ROOT, DS],
51 56 $oneImage
52 57 );
58 + // Still an http picture? Must be a picture out of the domain... so we just leave it that way
53 59 if (strpos($location, 'http') === 0) continue;
54 60
61 + // We can't find the image? We leave it that way
55 62 if (!file_exists($location)) continue;
56 63
57 64 $filename = basename($location);
58 65 while (file_exists($imagesFolder.DS.$filename)) {
59 - $filename = rand(0, 99).$filename;
66 + $filename = acym_rand(0, 99).$filename;
60 67 }
61 68
62 69 acym_copyFile($location, $imagesFolder.DS.$filename);
63 70 $replace[$oneImage] = 'images/'.$filename;
@@ -65,8 +72,9 @@
65 72
66 73 $template->body = str_replace(array_keys($replace), $replace, $template->body);
67 74 }
68 75
76 + // 3 - Copy the template's content
69 77 $structure = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
70 78 <html xmlns="http://www.w3.org/1999/xhtml">
71 79 <head>
72 80 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
@@ -80,9 +88,9 @@
80 88 'settings',
81 89 ];
82 90 foreach ($dataToCopy as $oneData) {
83 91 if (empty($template->$oneData)) continue;
84 - $structure .= "\n".'<meta name="'.$oneData.'" content="'.acym_escape($template->$oneData).'" />';
92 + $structure .= "\n".'<meta name="'.$oneData.'" content="'.esc_attr($template->$oneData).'" />';
85 93 }
86 94
87 95 $structure .= "\n".'<title>'.$template->name.'</title>
88 96 </head>
@@ -92,17 +100,20 @@
92 100 </html>';
93 101
94 102 acym_writeFile($exportFolder.DS.'template.html', $structure);
95 103
104 + // 4 - Copy the thumbnail
96 105 $thumbnail = acym_getMailThumbnail($template->thumbnail);
97 106 $thumbnail = str_replace(ACYM_LIVE, ACYM_ROOT, $thumbnail);
98 107 acym_copyFile($thumbnail, $exportFolder.DS.'thumbnail.png');
99 108
109 + // 5 - Handle custom stylesheet
100 110 if (!empty($template->stylesheet)) {
101 111 acym_createFolder($exportFolder.DS.'css');
102 112 acym_writeFile($exportFolder.DS.'css'.DS.'custom.css', $template->stylesheet);
103 113 }
104 114
115 + // 6 - Create the zip archive
105 116 $zipFiles = [];
106 117 $folders = acym_getFolders($exportFolder, '.', true, true);
107 118 $folders[] = $exportFolder;
108 119 foreach ($folders as $folder) {
@@ -107,8 +118,9 @@
107 118 $folders[] = $exportFolder;
108 119 foreach ($folders as $folder) {
109 120 $files = acym_getFiles($folder, '.', false, true);
110 121 foreach ($files as $file) {
122 + // Avoid J3 issue on thumbnail and index.php path, last separator was not DS
111 123 $posSlash = strrpos($file, '/');
112 124 $posASlash = strrpos($file, '\\');
113 125 $pos = ($posSlash < $posASlash) ? $posASlash : $posSlash;
114 126 if (!empty($pos)) $file = substr_replace($file, DS, $pos, 1);
@@ -126,11 +138,14 @@
126 138 }
127 139
128 140 acym_createArchive($exportFolder, $zipFiles);
129 141
142 + // 7 - Remove the temporary folder
130 143 acym_deleteFolder($exportFolder);
131 144
145 + // 8 - Download the zip
132 146 $this->setDownloadHeaders($name, 'zip');
147 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Downloading an export file.
133 148 echo acym_fileGetContent($exportFolder.'.zip');
134 149 acym_deleteFile($exportFolder.'.zip');
135 150
136 151 exit;
@@ -179,8 +194,9 @@
179 194 {
180 195 $nbExport = $this->getExportLimit();
181 196 $mailsStats = acym_loadObjectList($query.' LIMIT '.intval($nbExport));
182 197 $mailClass = new MailClass();
198 + // We don't want to decode the user's name
183 199 $mailClass->exceptKeysDecode = ['name'];
184 200 $mailsStats = $mailClass->decode($mailsStats);
185 201 acym_displayErrors();
186 202
@@ -221,8 +237,9 @@
221 237 }
222 238
223 239 $oneLine[] = htmlspecialchars($line, ENT_QUOTES, 'UTF-8');
224 240 }
241 + //We delete the double quote so it won't break the CSV
225 242 $csvLines[] = $this->before.implode($separator, $oneLine).$this->after;
226 243 }
227 244
228 245 $this->finishStatsExport($csvLines, $type);
@@ -234,10 +251,11 @@
234 251
235 252 if (ACYM_CMS === 'wordpress') {
236 253 @ob_get_clean();
237 254 }
238 - $filename = 'export_stats_'.$type.'_'.date('Y-m-d');
255 + $filename = 'export_stats_'.$type.'_'.gmdate('Y-m-d');
239 256 $this->setDownloadHeaders($filename);
257 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Exporting CSV data.
240 258 echo $final;
241 259 }
242 260
243 261 private function getLists(): array
@@ -288,19 +306,23 @@
288 306 $allFieldsToExport[] = acym_translation('ACYM_UNSUBSCRIPTION_DATE').' '.acym_translation($oneList);
289 307 }
290 308 }
291 309
310 + // This first line shows the column headers ("name", "email", etc...)
292 311 $firstLine = $this->before.implode($separator, $allFieldsToExport).$this->after.$this->eol;
293 312
294 313 if (empty($exportFile)) {
295 314 @ob_get_clean();
296 - $filename = 'export_'.date('Y-m-d');
315 + $filename = 'export_'.gmdate('Y-m-d');
297 316 $this->setDownloadHeaders($filename);
317 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Exporting CSV data.
298 318 echo $firstLine;
299 319 } else {
320 + // Make sure the folder exists
300 321 preg_match('#^(.+/)[^/]+$#', $exportFile, $folder);
301 322 if (!empty($folder[1]) && !file_exists($folder[1])) acym_createDir($folder[1]);
302 323
324 + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Using buffer for big exports.
303 325 $fp = fopen($exportFile, 'w');
304 326 if (false === $fp) {
305 327 if ($flagToRemove !== 0) {
306 328 $automationHelper = new AutomationHelper();
@@ -309,8 +331,9 @@
309 331
310 332 return acym_translationSprintf('ACYM_FAIL_SAVE_FILE', $exportFile);
311 333 }
312 334
335 + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite -- Using buffer for big exports.
313 336 $error = fwrite($fp, $firstLine);
314 337 if (false === $error) {
315 338 if ($flagToRemove !== 0) {
316 339 $automationHelper = new AutomationHelper();
@@ -330,13 +353,15 @@
330 353 }
331 354 $start += $nbExport;
332 355
333 356 if ($users === false) {
334 - $errorLine = $this->eol.$this->eol.'Error: '.(isset($e) ? $e->getMessage() : acym_getDBError());
357 + $errorLine = $this->eol.$this->eol.'Error: '.esc_html(isset($e) ? $e->getMessage() : acym_getDBError());
335 358
336 359 if (empty($exportFile)) {
360 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Exporting CSV data.
337 361 echo $errorLine;
338 362 } else {
363 + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite -- Using buffer for big exports.
339 364 $error = fwrite($fp, $errorLine);
340 365 if (false === $error) {
341 366 if ($flagToRemove !== 0) {
342 367 $automationHelper = new AutomationHelper();
@@ -347,8 +372,9 @@
347 372 }
348 373 }
349 374 }
350 375
376 + // There is no other user to export, end here
351 377 if (empty($users)) break;
352 378
353 379 foreach ($users as $userID => $oneUser) {
354 380 unset($oneUser->id);
@@ -407,8 +433,9 @@
407 433 if (is_null($oneData)) {
408 434 continue;
409 435 }
410 436
437 + // Not using empty() because 0 and '0' are valid values
411 438 if ($excelSecure == 1) {
412 439 $firstCharacter = substr($oneData, 0, 1);
413 440 if (in_array($firstCharacter, ['=', '+', '-', '@'])) {
414 441 $oneData = ' '.$oneData;
@@ -422,10 +449,12 @@
422 449 unset($data);
423 450
424 451 $oneLine = $this->before.$encodingClass->change($dataexport, 'UTF-8', $charset).$this->after.$this->eol;
425 452 if (empty($exportFile)) {
453 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Exporting CSV data.
426 454 echo $oneLine;
427 455 } else {
456 + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite -- Using buffer for big exports.
428 457 $error = fwrite($fp, $oneLine);
429 458 if (false === $error) {
430 459 if ($flagToRemove !== 0) {
431 460 $automationHelper = new AutomationHelper();
@@ -444,9 +473,12 @@
444 473 $automationHelper = new AutomationHelper();
445 474 $automationHelper->removeFlag($flagToRemove);
446 475 }
447 476
448 - if (!empty($exportFile)) fclose($fp);
477 + if (!empty($exportFile)) {
478 + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Using buffer for big exports.
479 + fclose($fp);
480 + }
449 481
450 482 return '';
451 483 }
452 484
@@ -451,8 +483,9 @@
451 483 }
452 484
453 485 private function getExportLimit(): int
454 486 {
487 + // Getting X users per batch based on the memory limit
455 488 $serverLimit = acym_bytes(ini_get('memory_limit'));
456 489 if ($serverLimit > 500000000) {
457 490 return 250000;
458 491 } elseif ($serverLimit > 150000000) {
@@ -570,8 +603,8 @@
570 603 if (strpos($file, $filenameToSearch) === false) {
571 604 continue;
572 605 }
573 606
574 - @unlink(acym_getLogPath($file));
607 + acym_deleteFile(acym_getLogPath($file));
575 608 }
576 609 }
577 610 }