PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.12
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.12
6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 3.6.65 All 196 releases
fluentform / app / Services / Transfer / TransferService.php

TransferService.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.12, at app/Services/Transfer/TransferService.php

699 lines 27.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentForm\App\Services\Transfer;
4
5 defined('ABSPATH') or die;
6
7 use Exception;
8 use FluentForm\App\Helpers\Helper;
9 use FluentForm\App\Models\Form;
10 use FluentForm\App\Models\FormMeta;
11 use FluentForm\App\Models\Submission;
12 use FluentForm\App\Models\SubmissionMeta;
13 use FluentForm\App\Modules\Acl\Acl;
14 use FluentForm\App\Modules\Form\FormDataParser;
15 use FluentForm\App\Modules\Form\FormFieldsParser;
16 use FluentForm\App\Services\FormBuilder\ShortCodeParser;
17 use FluentForm\Framework\Foundation\App;
18 use FluentForm\Framework\Http\Request\File;
19 use FluentForm\Framework\Support\Arr;
20
21 class TransferService
22 {
23 public static function sanitizeImportedMetaValue($metaKey, $metaValue)
24 {
25 if (!is_string($metaValue)) {
26 return $metaValue;
27 }
28
29 if ($metaKey === '_custom_form_css') {
30 return fluentformSanitizeCSS($metaValue);
31 }
32
33 if ($metaKey === '_custom_form_js') {
34 return fluentform_kses_js($metaValue);
35 }
36
37 $decoded = json_decode($metaValue);
38 if (is_array($decoded) || is_object($decoded)) {
39 self::sanitizeJsonNode($decoded);
40 $encoded = wp_json_encode($decoded);
41 return $encoded ?: $metaValue;
42 }
43
44 return wp_kses_post($metaValue);
45 }
46
47 private static function sanitizeJsonNode(&$node)
48 {
49 if (is_array($node)) {
50 foreach ($node as &$value) {
51 self::sanitizeJsonNode($value);
52 }
53 unset($value);
54 } elseif (is_object($node)) {
55 foreach (get_object_vars($node) as $key => $value) {
56 self::sanitizeJsonNode($value);
57 $node->{$key} = $value;
58 }
59 } elseif (is_string($node)) {
60 $node = wp_kses_post($node);
61 }
62 }
63
64 public static function exportForms($formIds)
65 {
66 $result = Form::with(['formMeta'])
67 ->whereIn('id', $formIds)
68 ->get();
69 $forms = [];
70 foreach ($result as $item) {
71 $form = json_decode($item);
72 $formMetaFiltered = array_filter($form->form_meta, function ($item) {
73 return ($item->meta_key !== '_total_views');
74 });
75 $form->metas = $formMetaFiltered;
76 $form->form_fields = json_decode($form->form_fields);
77 $forms[] = $form;
78 }
79
80 $fileName = 'fluentform-export-forms-' . count($forms) . '-' . date('d-m-Y') . '.json';
81
82 header('Content-disposition: attachment; filename=' . $fileName);
83
84 header('Content-type: application/json');
85
86 echo json_encode(array_values($forms)); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $forms is escaped before being passed in.
87
88 die();
89 }
90
91 /**
92 * Build the notice shown when imported custom JS/CSS was skipped because the
93 * importer lacks unfiltered_html. Returns an empty string when nothing was skipped.
94 *
95 * @param int $skippedForms
96 * @param int $totalForms
97 * @return string
98 */
99 protected static function restrictedCodeNotice($skippedForms, $totalForms)
100 {
101 if (!$skippedForms) {
102 return '';
103 }
104
105 if ($totalForms < 2) {
106 return __('Custom JS and CSS were not imported because your account cannot add custom code. Ask an administrator to add it.', 'fluentform');
107 }
108
109 return sprintf(
110 /* translators: 1: number of forms whose custom code was skipped, 2: total number of imported forms */
111 __('Custom JS and CSS were not imported for %1$d of %2$d forms because your account cannot add custom code. Ask an administrator to add it.', 'fluentform'),
112 $skippedForms,
113 $totalForms
114 );
115 }
116
117 /**
118 * @param File $file The uploaded JSON file
119 * @param bool $applyDefaultStyle Whether to apply default style settings to imported forms
120 * @throws Exception
121 */
122 public static function importForms($file, $applyDefaultStyle = false)
123 {
124 if ($file instanceof File) {
125 $forms = \json_decode($file->getContents(), true);
126 $insertedForms = [];
127 $restrictedCodeForms = 0;
128 if ($forms && is_array($forms)) {
129 foreach ($forms as $formItem) {
130 $formFields = json_encode([]);
131 if ($fields = Arr::get($formItem, 'form', '')) {
132 $formFields = json_encode($fields);
133 } elseif ($fields = Arr::get($formItem, 'form_fields', '')) {
134 $formFields = json_encode($fields);
135 } else {
136 throw new Exception(esc_html__('You have a faulty JSON file, please export the Fluent Forms again.', 'fluentform'));
137 }
138
139 // SECURITY (FINDING-07): the editor save path routes form_fields through
140 // Updater::sanitizeFields (skipped only for unfiltered_html users), but import
141 // stored them verbatim, so an importer without unfiltered_html could plant
142 // stored XSS (e.g. a field label of <img onerror=...>). Apply the same
143 // recursive HTML sanitizer used for imported meta values unless the importer
144 // may author raw HTML.
145 if (!fluentformCanUnfilteredHTML()) {
146 $decodedFields = json_decode($formFields, true);
147 if (is_array($decodedFields)) {
148 static::sanitizeJsonNode($decodedFields);
149 $formFields = wp_json_encode($decodedFields) ?: $formFields;
150 }
151 }
152
153 $formTitle = sanitize_text_field(Arr::get($formItem, 'title'));
154 $form = [
155 'title' => $formTitle ?: 'Blank Form',
156 'form_fields' => $formFields,
157 'status' => sanitize_text_field(Arr::get($formItem, 'status', 'published')),
158 'has_payment' => sanitize_text_field(Arr::get($formItem, 'has_payment', 0)),
159 'type' => sanitize_text_field(Arr::get($formItem, 'type', 'form')),
160 'created_by' => get_current_user_id(),
161 ];
162
163 if (Arr::get($formItem, 'conditions')) {
164 $form['conditions'] = Arr::get($formItem, 'conditions');
165 }
166
167 if (isset($formItem['appearance_settings'])) {
168 $form['appearance_settings'] = Arr::get($formItem, 'appearance_settings');
169 }
170
171 $formId = Form::insertGetId($form);
172 $insertedForms[$formId] = [
173 'title' => $form['title'],
174 'edit_url' => admin_url('admin.php?page=fluent_forms&route=editor&form_id=' . $formId),
175 ];
176
177 $skippedCustomCode = false;
178
179 if (isset($formItem['metas'])) {
180 foreach ($formItem['metas'] as $metaData) {
181 $metaKey = sanitize_text_field(Arr::get($metaData, 'meta_key'));
182 $metaValue = Arr::get($metaData, 'value');
183 // SECURITY (FINDING-08): Customizer::store() refuses to save custom
184 // JS/CSS without unfiltered_html; import must honor the same boundary.
185 // Sanitizing _custom_form_js via fluentform_kses_js is insufficient
186 // because the value is JS *code* executed inside a <script> block (kses
187 // only strips <script> tags), so skip these keys entirely for importers
188 // who cannot author raw JS/CSS.
189 if (
190 in_array($metaKey, ['_custom_form_js', '_custom_form_css'], true)
191 && !fluentformCanUnfilteredHTML()
192 ) {
193 $skippedCustomCode = true;
194 continue;
195 }
196 if ('ffc_form_settings_generated_css' == $metaKey || 'ffc_form_settings_meta' == $metaKey) {
197 $metaValue = str_replace('ff_conv_app_' . Arr::get($formItem, 'id'), 'ff_conv_app_' . $formId, $metaValue);
198 }
199 $metaValue = static::sanitizeImportedMetaValue($metaKey, $metaValue);
200 $settings = [
201 'form_id' => $formId,
202 'meta_key' => $metaKey,
203 'value' => $metaValue,
204 ];
205 FormMeta::insert($settings);
206 }
207 } else {
208 $oldKeys = [
209 'formSettings',
210 'notifications',
211 'mailchimp_feeds',
212 'slack',
213 ];
214 foreach ($oldKeys as $key) {
215 if (isset($formItem[$key])) {
216 FormMeta::persist($formId, $key, json_encode(Arr::get($formItem, $key)));
217 }
218 }
219 }
220
221 if ($skippedCustomCode) {
222 $restrictedCodeForms++;
223 }
224
225 do_action('fluentform/form_imported', $formId);
226
227 // Apply default style if requested
228 if ($applyDefaultStyle) {
229 do_action('fluentform/inserted_new_form', $formId, $form);
230 }
231 }
232
233 return ([
234 'message' => __('You form has been successfully imported.', 'fluentform'),
235 'inserted_forms' => $insertedForms,
236 'restricted_code_notice' => static::restrictedCodeNotice($restrictedCodeForms, count($insertedForms)),
237 ]);
238 }
239 }
240 throw new Exception(esc_html__('You have a faulty JSON file, please export the Fluent Forms again.', 'fluentform'));
241 }
242
243 public static function exportEntries($args)
244 {
245 if (!defined('FLUENTFORM_EXPORTING_ENTRIES')) {
246 define('FLUENTFORM_EXPORTING_ENTRIES', true);
247 }
248
249 $formId = Acl::verifyFormId(Arr::get($args, 'form_id'));
250 Acl::verify('fluentform_entries_viewer', $formId);
251
252 $tableName = Arr::get($args, 'table');
253 try {
254 $form = Form::findOrFail($formId);
255 } catch (Exception $e) {
256 exit('No Form Found');
257 }
258 $type = sanitize_key(Arr::get($args, 'format', 'csv'));
259 if (!in_array($type, ['csv', 'ods', 'xlsx', 'json'])) {
260 exit('Invalid requested format');
261 }
262 if ('json' == $type) {
263 self::exportAsJSON($form, $args);
264 }
265 if (!defined('FLUENTFORM_DOING_CSV_EXPORT')) {
266 define('FLUENTFORM_DOING_CSV_EXPORT', true);
267 }
268 $formInputs = FormFieldsParser::getEntryInputs($form, ['admin_label', 'raw']);
269 $inputLabels = FormFieldsParser::getAdminLabels($form, $formInputs);
270 $selectedLabels = Arr::get($args, 'fields_to_export');
271 if (is_string($selectedLabels) && Helper::isJson($selectedLabels)) {
272 $selectedLabels = \json_decode($selectedLabels, true);
273 }
274 $selectedLabels = fluentFormSanitizer($selectedLabels);
275
276 $withNotes = isset($args['with_notes']);
277
278 //filter out unselected fields
279 if (!empty($selectedLabels)) {
280 foreach ($inputLabels as $key => $value) {
281 if (!in_array($key, $selectedLabels) && isset($inputLabels[$key])) {
282 unset($inputLabels[$key]);
283 }
284 }
285 }
286
287 $submissions = self::getSubmissions($args);
288 $submissions = FormDataParser::parseFormEntries($submissions, $form, $formInputs);
289 $parsedShortCodes = [];
290 $exportData = [];
291 $selectedShortcodes = self::getSelectedExportShortcodes($args, $form);
292 $legacyShortcodeHeaders = self::getLegacyExportShortcodeHeaders();
293
294 // Preload notes for all submissions in a single query to avoid N+1
295 $notesMap = [];
296 if ($withNotes && count($submissions)) {
297 $submissionIds = array_map(function ($s) {
298 return is_object($s) ? $s->id : $s['id'];
299 }, $submissions->toArray());
300 $allNotes = SubmissionMeta::whereIn('response_id', $submissionIds)
301 ->where('meta_key', '_notes')
302 ->get();
303 foreach ($allNotes as $note) {
304 $notesMap[$note->response_id][] = $note->value;
305 }
306 }
307
308 foreach ($submissions as $submission) {
309
310 $submission->response = json_decode($submission->response, true);
311
312 $temp = [];
313 foreach ($inputLabels as $field => $label) {
314
315 //format tabular grid data for CSV/XLSV/ODS export
316 if (isset($formInputs[$field]['element']) && 'tabular_grid' === $formInputs[$field]['element']) {
317 $gridRawData = Arr::get($submission->response, $field);
318 $content = Helper::getTabularGridFormatValue($gridRawData, Arr::get($formInputs, $field), ' | ');
319 } elseif (isset($formInputs[$field]['element']) && 'subscription_payment_component' === $formInputs[$field]['element']) {
320 //resolve plane name for subscription field
321 $planIndex = Arr::get($submission->user_inputs, $field);
322 $planLabel = Arr::get($formInputs, "{$field}.raw.settings.subscription_options.{$planIndex}.name");
323 if ($planLabel) {
324 $content = $planLabel;
325 } else {
326 $content = self::getFieldExportContent($submission, $field);
327 }
328 } else {
329 $content = self::getFieldExportContent($submission, $field);
330 if (Arr::get($formInputs, $field . '.element') === 'input_number' && is_numeric($content)) {
331 $content = $content + 0;
332 }
333 }
334 $temp[] = Helper::sanitizeForCSV($content);
335 }
336
337 if (!empty($selectedShortcodes)) {
338 $regularShortcodes = self::getRegularExportShortcodes($selectedShortcodes, $legacyShortcodeHeaders);
339
340 if (!empty($regularShortcodes)) {
341 $parsedShortCodes = ShortCodeParser::parse(
342 $regularShortcodes,
343 $submission->id,
344 $submission->response,
345 $form,
346 false,
347 true
348 );
349 }
350
351 // SECURITY (FINDING-17): shortcode-export values (which include submitter-controlled
352 // {inputs.*} content) bypassed the CSV formula guard applied to regular columns.
353 // Sanitize each so a leading = - + @ etc. cannot execute when opened in a spreadsheet.
354 $shortcodeValues = self::getSelectedShortcodeExportValues(
355 $selectedShortcodes,
356 $parsedShortCodes,
357 $legacyShortcodeHeaders,
358 $submission
359 );
360 $shortcodeValues = array_map(function ($v) {
361 return is_scalar($v) ? Helper::sanitizeForCSV((string) $v) : $v;
362 }, $shortcodeValues);
363 $temp = array_merge($temp, $shortcodeValues);
364 }
365 if ($withNotes) {
366 $noteValues = isset($notesMap[$submission->id]) ? $notesMap[$submission->id] : [];
367 if (!empty($noteValues)) {
368 // SECURITY (FINDING-17): notes are submitter-influenceable and were exported raw.
369 $temp[] = Helper::sanitizeForCSV(implode(", ", $noteValues));
370 }
371 }
372
373 $temp = apply_filters('fluentform/export_entry_metadata', $temp, $submission, $form, $args);
374
375 $exportData[] = $temp;
376 }
377
378 $extraLabels = [];
379
380 $extraLabels = self::getSelectedShortcodeExportLabels(
381 $selectedShortcodes,
382 $parsedShortCodes,
383 $legacyShortcodeHeaders
384 );
385
386 $inputLabels = array_merge($inputLabels, $extraLabels);
387 if ($withNotes) {
388 $inputLabels[] = __('Notes', 'fluentform');
389 }
390 $inputLabels = apply_filters('fluentform/export_entry_metadata_labels', $inputLabels, $form, $args);
391
392 // SECURITY (FINDING-17): sanitize the header row too — field/shortcode labels can start with
393 // a formula lead character (=, +, -, @) and were exported unguarded.
394 $headerRow = array_map(function ($v) {
395 return is_scalar($v) ? Helper::sanitizeForCSV((string) $v) : $v;
396 }, array_values($inputLabels));
397 $data = array_merge([$headerRow], $exportData);
398
399 $data = apply_filters('fluentform/export_data', $data, $form, $exportData, $inputLabels);
400 $fileName = self::getReadableExportFileName($form->title);
401 self::downloadOfficeDoc($data, $type, $fileName);
402 }
403
404 private static function getFieldExportContent($submission, $fieldName)
405 {
406 return trim(
407 wp_strip_all_tags(
408 FormDataParser::formatValue(
409 Arr::get($submission->user_inputs, $fieldName)
410 )
411 )
412 );
413 }
414
415 private static function getSelectedExportShortcodes($args, $form)
416 {
417 $selectedShortcodes = fluentFormSanitizer(Arr::get($args, 'shortcodes_to_export', []));
418
419 if (!Arr::has($args, 'shortcodes_to_export_defined') && empty($selectedShortcodes)) {
420 return self::getDefaultExportShortcodes($form);
421 }
422
423 return $selectedShortcodes;
424 }
425
426 private static function getDefaultExportShortcodes($form)
427 {
428 $defaults = [
429 [
430 'label' => __('Submission ID', 'fluentform'),
431 'value' => '{submission.id}',
432 ],
433 [
434 'label' => __('Submission Create Date', 'fluentform'),
435 'value' => '{submission.created_at}',
436 ],
437 [
438 'label' => __('Submission Status', 'fluentform'),
439 'value' => '{submission.status}',
440 ],
441 ];
442
443 if ($form->has_payment) {
444 $defaults[] = [
445 'label' => __('Payment Status', 'fluentform'),
446 'value' => '{payment.payment_status}',
447 ];
448 $defaults[] = [
449 'label' => __('Payment Total', 'fluentform'),
450 'value' => '{payment.payment_total}',
451 ];
452 $defaults[] = [
453 'label' => __('Currency', 'fluentform'),
454 'value' => '{submission.currency}',
455 ];
456 }
457
458 return $defaults;
459 }
460
461 private static function getLegacyExportShortcodeHeaders()
462 {
463 return [
464 '{submission.id}' => 'entry_id',
465 '{submission.status}' => 'entry_status',
466 '{submission.created_at}' => 'created_at',
467 '{payment.payment_status}' => 'payment_status',
468 '{submission.payment_status}' => 'payment_status',
469 '{payment.payment_total}' => 'payment_total',
470 '{submission.payment_total}' => 'payment_total',
471 '{submission.currency}' => 'currency',
472 ];
473 }
474
475 private static function getRegularExportShortcodes($selectedShortcodes, $legacyShortcodeHeaders)
476 {
477 $regularShortcodes = [];
478
479 foreach ($selectedShortcodes as $index => $shortcode) {
480 if (!isset($legacyShortcodeHeaders[Arr::get($shortcode, 'value')])) {
481 $regularShortcodes[$index] = $shortcode;
482 }
483 }
484
485 return $regularShortcodes;
486 }
487
488 private static function getSelectedShortcodeExportValues($selectedShortcodes, $parsedShortCodes, $legacyShortcodeHeaders, $submission)
489 {
490 $values = [];
491
492 foreach ($selectedShortcodes as $index => $shortcode) {
493 $shortcodeValue = Arr::get($shortcode, 'value');
494
495 if (!isset($legacyShortcodeHeaders[$shortcodeValue])) {
496 $values[] = Arr::get($parsedShortCodes, $index . '.value');
497 continue;
498 }
499
500 $values[] = self::getLegacyExportValue($legacyShortcodeHeaders[$shortcodeValue], $submission);
501 }
502
503 return $values;
504 }
505
506 private static function getSelectedShortcodeExportLabels($selectedShortcodes, $parsedShortCodes, $legacyShortcodeHeaders)
507 {
508 $labels = [];
509
510 foreach ($selectedShortcodes as $index => $shortcode) {
511 $shortcodeValue = Arr::get($shortcode, 'value');
512
513 if (isset($legacyShortcodeHeaders[$shortcodeValue])) {
514 $labels[] = $legacyShortcodeHeaders[$shortcodeValue];
515 continue;
516 }
517
518 $labels[] = Arr::get($parsedShortCodes, $index . '.label');
519 }
520
521 return $labels;
522 }
523
524 private static function getLegacyExportValue($header, $submission)
525 {
526 $legacyValueResolvers = [
527 'entry_id' => function ($submission) {
528 return $submission->id ?? '';
529 },
530 'entry_status' => function ($submission) {
531 return $submission->status ?? '';
532 },
533 'created_at' => function ($submission) {
534 return $submission->created_at ?? '';
535 },
536 'payment_status' => function ($submission) {
537 return $submission->payment_status ?? '';
538 },
539 'payment_total' => function ($submission) {
540 return round(($submission->payment_total ?? 0) / 100, 1);
541 },
542 'currency' => function ($submission) {
543 return $submission->currency ?? '';
544 },
545 ];
546
547 if (!isset($legacyValueResolvers[$header])) {
548 return '';
549 }
550
551 return $legacyValueResolvers[$header]($submission);
552 }
553
554 private static function exportAsJSON($form, $args)
555 {
556 $formInputs = FormFieldsParser::getEntryInputs($form, ['admin_label', 'raw']);
557 $submissions = self::getSubmissions($args);
558 $submissions = FormDataParser::parseFormEntries($submissions, $form, $formInputs);
559 foreach ($submissions as $submission) {
560 $submission->response = json_decode($submission->response, true);
561 }
562 self::sendDownloadHeaders('application/json', self::getReadableExportFileName($form->title) . '.json');
563 echo json_encode($submissions); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $submissions is escaped before being passed in.
564 exit();
565 }
566
567 private static function getReadableExportFileName($formTitle)
568 {
569 $sanitizedTitle = sanitize_file_name(wp_strip_all_tags((string) $formTitle));
570
571 if (!$sanitizedTitle) {
572 $sanitizedTitle = 'export';
573 }
574
575 return $sanitizedTitle . '-' . date('Y-m-d');
576 }
577
578 private static function sendDownloadHeaders($contentType, $fileName)
579 {
580 $safeFileName = basename((string) $fileName);
581 $encodedFileName = rawurlencode($safeFileName);
582
583 header('Content-Type: ' . $contentType);
584 header(
585 'Content-Disposition: attachment; ' .
586 'filename="' . $encodedFileName . '"; ' .
587 'filename*=UTF-8\'\'' . $encodedFileName
588 );
589 }
590
591 private static function getSubmissions($args)
592 {
593 $tableName = Arr::get($args, 'table');
594
595 if ($tableName) {
596 $allowedTables = [
597 'fluentform_submissions',
598 'fluentform_draft_submissions',
599 ];
600 if (!in_array($tableName, $allowedTables, true)) {
601 wp_send_json([
602 'message' => __('Invalid table name for export.', 'fluentform'),
603 ], 422);
604 }
605 $query = wpFluent()->table($tableName)
606 ->where('form_id', (int) Arr::get($args, 'form_id'))
607 ->orderBy('id', Helper::sanitizeOrderValue(Arr::get($args, 'sort_by', 'DESC')));
608
609 $searchString = Arr::get($args, 'search');
610 if ($searchString) {
611 global $wpdb;
612 $escaped = $wpdb->esc_like($searchString);
613 $query->where(function ($q) use ($escaped) {
614 $q->where('id', 'LIKE', "%{$escaped}%")
615 ->orWhere('response', 'LIKE', "%{$escaped}%");
616 });
617 }
618 } else {
619 $query = (new Submission())->customQuery($args);
620 }
621
622 $entries = fluentFormSanitizer(Arr::get($args, 'entries', []));
623 $query->when(is_array($entries) && (count($entries) > 0), function ($q) use ($entries) {
624 return $q->whereIn('id', $entries);
625 });
626
627 if (Arr::get($args, 'advanced_filter')) {
628 $query = apply_filters('fluentform/apply_entries_advance_filter', $query, $args);
629 }
630
631 return $query->get();
632 }
633
634 private static function downloadOfficeDoc($data, $type = 'csv', $fileName = null)
635 {
636 $data = array_map(function ($item) {
637 return array_map(function ($itemValue) {
638 if (is_array($itemValue)) {
639 $itemValue = implode(', ', $itemValue);
640 }
641
642 return is_string($itemValue)
643 ? Helper::sanitizeForCSV($itemValue)
644 : $itemValue;
645 }, $item);
646 }, $data);
647 // Load Composer autoloader for OpenSpout
648 require_once FLUENTFORM_DIR_PATH . '/vendor/autoload.php';
649 $fileName = ($fileName) ? $fileName . '.' . $type : 'export-data-' . date('d-m-Y') . '.' . $type;
650
651 // Create writer based on type
652 switch (strtolower($type)) {
653 case 'csv':
654 $writer = \OpenSpout\Writer\Common\Creator\WriterEntityFactory::createCSVWriter();
655 break;
656 case 'xlsx':
657 $writer = \OpenSpout\Writer\Common\Creator\WriterEntityFactory::createXLSXWriter();
658 break;
659 case 'ods':
660 $writer = \OpenSpout\Writer\Common\Creator\WriterEntityFactory::createODSWriter();
661 break;
662 default:
663 throw new \Exception(sprintf('Unsupported file type: %s', esc_html($type)));
664 }
665 $writer->openToBrowser($fileName);
666
667 $rows = self::getOfficeDocRows($data, $type);
668
669 $writer->addRows($rows);
670 $writer->close();
671 die();
672 }
673
674 private static function getOfficeDocRows($data, $type)
675 {
676 if (strtolower($type) !== 'xlsx') {
677 return array_map(function ($rowData) {
678 return \OpenSpout\Writer\Common\Creator\WriterEntityFactory::createRowFromArray($rowData);
679 }, $data);
680 }
681
682 $dateStyle = (new \OpenSpout\Writer\Common\Creator\Style\StyleBuilder())
683 ->setFormat('yyyy-mm-dd hh:mm:ss')
684 ->build();
685
686 return array_map(function ($rowData) use ($dateStyle) {
687 $cells = array_map(function ($cellValue) use ($dateStyle) {
688 if ($cellValue instanceof \DateTimeInterface) {
689 return \OpenSpout\Writer\Common\Creator\WriterEntityFactory::createCell($cellValue, $dateStyle);
690 }
691
692 return \OpenSpout\Writer\Common\Creator\WriterEntityFactory::createCell($cellValue);
693 }, $rowData);
694
695 return \OpenSpout\Writer\Common\Creator\WriterEntityFactory::createRow($cells);
696 }, $data);
697 }
698 }
699