PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.2
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.2
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.2, at app/Services/Transfer/TransferService.php

539 lines 21.0 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 exportForms($formIds)
24 {
25 $result = Form::with(['formMeta'])
26 ->whereIn('id', $formIds)
27 ->get();
28 $forms = [];
29 foreach ($result as $item) {
30 $form = json_decode($item);
31 $formMetaFiltered = array_filter($form->form_meta, function ($item) {
32 return ($item->meta_key !== '_total_views');
33 });
34 $form->metas = $formMetaFiltered;
35 $form->form_fields = json_decode($form->form_fields);
36 $forms[] = $form;
37 }
38
39 $fileName = 'fluentform-export-forms-' . count($forms) . '-' . date('d-m-Y') . '.json';
40
41 header('Content-disposition: attachment; filename=' . $fileName);
42
43 header('Content-type: application/json');
44
45 echo json_encode(array_values($forms)); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $forms is escaped before being passed in.
46
47 die();
48 }
49
50 /**
51 * @param File $file The uploaded JSON file
52 * @param bool $applyDefaultStyle Whether to apply default style settings to imported forms
53 * @throws Exception
54 */
55 public static function importForms($file, $applyDefaultStyle = false)
56 {
57 if ($file instanceof File) {
58 $forms = \json_decode($file->getContents(), true);
59 $insertedForms = [];
60 if ($forms && is_array($forms)) {
61 foreach ($forms as $formItem) {
62 $formFields = json_encode([]);
63 if ($fields = Arr::get($formItem, 'form', '')) {
64 $formFields = json_encode($fields);
65 } elseif ($fields = Arr::get($formItem, 'form_fields', '')) {
66 $formFields = json_encode($fields);
67 } else {
68 throw new Exception(esc_html__('You have a faulty JSON file, please export the Fluent Forms again.', 'fluentform'));
69 }
70 $formTitle = sanitize_text_field(Arr::get($formItem, 'title'));
71 $form = [
72 'title' => $formTitle ?: 'Blank Form',
73 'form_fields' => $formFields,
74 'status' => sanitize_text_field(Arr::get($formItem, 'status', 'published')),
75 'has_payment' => sanitize_text_field(Arr::get($formItem, 'has_payment', 0)),
76 'type' => sanitize_text_field(Arr::get($formItem, 'type', 'form')),
77 'created_by' => get_current_user_id(),
78 ];
79
80 if (Arr::get($formItem, 'conditions')) {
81 $form['conditions'] = Arr::get($formItem, 'conditions');
82 }
83
84 if (isset($formItem['appearance_settings'])) {
85 $form['appearance_settings'] = Arr::get($formItem, 'appearance_settings');
86 }
87
88 $formId = Form::insertGetId($form);
89 $insertedForms[$formId] = [
90 'title' => $form['title'],
91 'edit_url' => admin_url('admin.php?page=fluent_forms&route=editor&form_id=' . $formId),
92 ];
93
94 if (isset($formItem['metas'])) {
95 foreach ($formItem['metas'] as $metaData) {
96 $metaKey = sanitize_text_field(Arr::get($metaData, 'meta_key'));
97 $metaValue = Arr::get($metaData, 'value');
98 if ("ffc_form_settings_generated_css" == $metaKey || "ffc_form_settings_meta" == $metaKey) {
99 $metaValue = str_replace('ff_conv_app_' . Arr::get($formItem, 'id'), 'ff_conv_app_' . $formId, $metaValue);
100 }
101 if (is_string($metaValue)) {
102 $metaValue = wp_kses_post($metaValue);
103 }
104 $settings = [
105 'form_id' => $formId,
106 'meta_key' => $metaKey,
107 'value' => $metaValue,
108 ];
109 FormMeta::insert($settings);
110 }
111 } else {
112 $oldKeys = [
113 'formSettings',
114 'notifications',
115 'mailchimp_feeds',
116 'slack',
117 ];
118 foreach ($oldKeys as $key) {
119 if (isset($formItem[$key])) {
120 FormMeta::persist($formId, $key, json_encode(Arr::get($formItem, $key)));
121 }
122 }
123 }
124
125 do_action('fluentform/form_imported', $formId);
126
127 // Apply default style if requested
128 if ($applyDefaultStyle) {
129 do_action('fluentform/inserted_new_form', $formId, $form);
130 }
131 }
132
133 return ([
134 'message' => __('You form has been successfully imported.', 'fluentform'),
135 'inserted_forms' => $insertedForms,
136 ]);
137 }
138 }
139 throw new Exception(esc_html__('You have a faulty JSON file, please export the Fluent Forms again.', 'fluentform'));
140 }
141
142 public static function exportEntries($args)
143 {
144 if (!defined('FLUENTFORM_EXPORTING_ENTRIES')) {
145 define('FLUENTFORM_EXPORTING_ENTRIES', true);
146 }
147
148 $formId = Acl::verifyFormId(Arr::get($args, 'form_id'));
149 Acl::verify('fluentform_entries_viewer', $formId);
150
151 $tableName = Arr::get($args, 'table');
152 try {
153 $form = Form::findOrFail($formId);
154 } catch (Exception $e) {
155 exit('No Form Found');
156 }
157 $type = sanitize_key(Arr::get($args, 'format', 'csv'));
158 if (!in_array($type, ['csv', 'ods', 'xlsx', 'json'])) {
159 exit('Invalid requested format');
160 }
161 if ('json' == $type) {
162 self::exportAsJSON($form, $args);
163 }
164 if (!defined('FLUENTFORM_DOING_CSV_EXPORT')) {
165 define('FLUENTFORM_DOING_CSV_EXPORT', true);
166 }
167 $formInputs = FormFieldsParser::getEntryInputs($form, ['admin_label', 'raw']);
168 $inputLabels = FormFieldsParser::getAdminLabels($form, $formInputs);
169 $selectedLabels = Arr::get($args,'fields_to_export');
170 if (is_string($selectedLabels) && Helper::isJson($selectedLabels)) {
171 $selectedLabels = \json_decode($selectedLabels, true);
172 }
173 $selectedLabels = fluentFormSanitizer($selectedLabels);
174
175 $withNotes = isset($args['with_notes']);
176
177 //filter out unselected fields
178 if (!empty($selectedLabels)) {
179 foreach ($inputLabels as $key => $value) {
180 if (!in_array($key, $selectedLabels) && isset($inputLabels[$key])) {
181 unset($inputLabels[$key]);
182 }
183 }
184 }
185
186 $submissions = self::getSubmissions($args);
187 $submissions = FormDataParser::parseFormEntries($submissions, $form, $formInputs);
188 $parsedShortCodes = [];
189 $exportData = [];
190 $selectedShortcodes = self::getSelectedExportShortcodes($args, $form);
191 $legacyShortcodeHeaders = self::getLegacyExportShortcodeHeaders();
192
193 // Preload notes for all submissions in a single query to avoid N+1
194 $notesMap = [];
195 if ($withNotes && count($submissions)) {
196 $submissionIds = array_map(function ($s) { return is_object($s) ? $s->id : $s['id']; }, $submissions->toArray());
197 $allNotes = SubmissionMeta::whereIn('response_id', $submissionIds)
198 ->where('meta_key', '_notes')
199 ->get();
200 foreach ($allNotes as $note) {
201 $notesMap[$note->response_id][] = $note->value;
202 }
203 }
204
205 foreach ($submissions as $submission) {
206
207 $submission->response = json_decode($submission->response, true);
208
209 $temp = [];
210 foreach ($inputLabels as $field => $label) {
211
212 //format tabular grid data for CSV/XLSV/ODS export
213 if (isset($formInputs[$field]['element']) && "tabular_grid" === $formInputs[$field]['element']) {
214 $gridRawData = Arr::get($submission->response, $field);
215 $content = Helper::getTabularGridFormatValue($gridRawData, Arr::get($formInputs, $field), ' | ');
216 } elseif (isset($formInputs[$field]['element']) && "subscription_payment_component" === $formInputs[$field]['element']) {
217 //resolve plane name for subscription field
218 $planIndex = Arr::get($submission->user_inputs, $field);
219 $planLabel = Arr::get($formInputs, "{$field}.raw.settings.subscription_options.{$planIndex}.name");
220 if ($planLabel) {
221 $content = $planLabel;
222 } else {
223 $content = self::getFieldExportContent($submission, $field);
224 }
225 } else {
226 $content = self::getFieldExportContent($submission, $field);
227 if (Arr::get($formInputs, $field . '.element') === "input_number" && is_numeric($content)) {
228 $content = $content + 0;
229 }
230 }
231 $temp[] = Helper::sanitizeForCSV($content);
232 }
233
234 if (!empty($selectedShortcodes)) {
235 $regularShortcodes = self::getRegularExportShortcodes($selectedShortcodes, $legacyShortcodeHeaders);
236
237 if (!empty($regularShortcodes)) {
238 $parsedShortCodes = ShortCodeParser::parse(
239 $regularShortcodes,
240 $submission->id,
241 $submission->response,
242 $form,
243 false,
244 true
245 );
246 }
247
248 $temp = array_merge(
249 $temp,
250 self::getSelectedShortcodeExportValues(
251 $selectedShortcodes,
252 $parsedShortCodes,
253 $legacyShortcodeHeaders,
254 $submission
255 )
256 );
257 }
258 if ($withNotes) {
259 $noteValues = isset($notesMap[$submission->id]) ? $notesMap[$submission->id] : [];
260 if (!empty($noteValues)) {
261 $temp[] = implode(", ", $noteValues);
262 }
263 }
264
265 $temp = apply_filters('fluentform/export_entry_metadata', $temp, $submission, $form, $args);
266
267 $exportData[] = $temp;
268 }
269
270 $extraLabels = [];
271
272 $extraLabels = self::getSelectedShortcodeExportLabels(
273 $selectedShortcodes,
274 $parsedShortCodes,
275 $legacyShortcodeHeaders
276 );
277
278 $inputLabels = array_merge($inputLabels, $extraLabels);
279 if($withNotes){
280 $inputLabels[] = __('Notes','fluentform');
281 }
282 $inputLabels = apply_filters('fluentform/export_entry_metadata_labels', $inputLabels, $form, $args);
283
284 $data = array_merge([array_values($inputLabels)], $exportData);
285
286 $data = apply_filters('fluentform/export_data', $data, $form, $exportData, $inputLabels);
287 $fileName = sanitize_title($form->title, 'export', 'view') . '-' . date('Y-m-d');
288 self::downloadOfficeDoc($data, $type, $fileName);
289 }
290
291 private static function getFieldExportContent($submission, $fieldName)
292 {
293 return trim(
294 wp_strip_all_tags(
295 FormDataParser::formatValue(
296 Arr::get($submission->user_inputs, $fieldName)
297 )
298 )
299 );
300 }
301
302 private static function getSelectedExportShortcodes($args, $form)
303 {
304 $selectedShortcodes = fluentFormSanitizer(Arr::get($args, 'shortcodes_to_export', []));
305
306 if (!Arr::has($args, 'shortcodes_to_export_defined') && empty($selectedShortcodes)) {
307 return self::getDefaultExportShortcodes($form);
308 }
309
310 return $selectedShortcodes;
311 }
312
313 private static function getDefaultExportShortcodes($form)
314 {
315 $defaults = [
316 [
317 'label' => __('Submission ID', 'fluentform'),
318 'value' => '{submission.id}',
319 ],
320 [
321 'label' => __('Submission Create Date', 'fluentform'),
322 'value' => '{submission.created_at}',
323 ],
324 [
325 'label' => __('Submission Status', 'fluentform'),
326 'value' => '{submission.status}',
327 ],
328 ];
329
330 if ($form->has_payment) {
331 $defaults[] = [
332 'label' => __('Payment Status', 'fluentform'),
333 'value' => '{payment.payment_status}',
334 ];
335 $defaults[] = [
336 'label' => __('Payment Total', 'fluentform'),
337 'value' => '{payment.payment_total}',
338 ];
339 $defaults[] = [
340 'label' => __('Currency', 'fluentform'),
341 'value' => '{submission.currency}',
342 ];
343 }
344
345 return $defaults;
346 }
347
348 private static function getLegacyExportShortcodeHeaders()
349 {
350 return [
351 '{submission.id}' => 'entry_id',
352 '{submission.status}' => 'entry_status',
353 '{submission.created_at}' => 'created_at',
354 '{payment.payment_status}' => 'payment_status',
355 '{submission.payment_status}' => 'payment_status',
356 '{payment.payment_total}' => 'payment_total',
357 '{submission.payment_total}' => 'payment_total',
358 '{submission.currency}' => 'currency',
359 ];
360 }
361
362 private static function getRegularExportShortcodes($selectedShortcodes, $legacyShortcodeHeaders)
363 {
364 $regularShortcodes = [];
365
366 foreach ($selectedShortcodes as $index => $shortcode) {
367 if (!isset($legacyShortcodeHeaders[Arr::get($shortcode, 'value')])) {
368 $regularShortcodes[$index] = $shortcode;
369 }
370 }
371
372 return $regularShortcodes;
373 }
374
375 private static function getSelectedShortcodeExportValues($selectedShortcodes, $parsedShortCodes, $legacyShortcodeHeaders, $submission)
376 {
377 $values = [];
378
379 foreach ($selectedShortcodes as $index => $shortcode) {
380 $shortcodeValue = Arr::get($shortcode, 'value');
381
382 if (!isset($legacyShortcodeHeaders[$shortcodeValue])) {
383 $values[] = Arr::get($parsedShortCodes, $index . '.value');
384 continue;
385 }
386
387 $values[] = self::getLegacyExportValue($legacyShortcodeHeaders[$shortcodeValue], $submission);
388 }
389
390 return $values;
391 }
392
393 private static function getSelectedShortcodeExportLabels($selectedShortcodes, $parsedShortCodes, $legacyShortcodeHeaders)
394 {
395 $labels = [];
396
397 foreach ($selectedShortcodes as $index => $shortcode) {
398 $shortcodeValue = Arr::get($shortcode, 'value');
399
400 if (isset($legacyShortcodeHeaders[$shortcodeValue])) {
401 $labels[] = $legacyShortcodeHeaders[$shortcodeValue];
402 continue;
403 }
404
405 $labels[] = Arr::get($parsedShortCodes, $index . '.label');
406 }
407
408 return $labels;
409 }
410
411 private static function getLegacyExportValue($header, $submission)
412 {
413 $legacyValueResolvers = [
414 'entry_id' => function ($submission) {
415 return $submission->id ?? '';
416 },
417 'entry_status' => function ($submission) {
418 return $submission->status ?? '';
419 },
420 'created_at' => function ($submission) {
421 return $submission->created_at ?? '';
422 },
423 'payment_status' => function ($submission) {
424 return $submission->payment_status ?? '';
425 },
426 'payment_total' => function ($submission) {
427 return round(($submission->payment_total ?? 0) / 100, 1);
428 },
429 'currency' => function ($submission) {
430 return $submission->currency ?? '';
431 },
432 ];
433
434 if (!isset($legacyValueResolvers[$header])) {
435 return '';
436 }
437
438 return $legacyValueResolvers[$header]($submission);
439 }
440
441 private static function exportAsJSON($form, $args)
442 {
443 $formInputs = FormFieldsParser::getEntryInputs($form, ['admin_label', 'raw']);
444 $submissions = self::getSubmissions($args);
445 $submissions = FormDataParser::parseFormEntries($submissions, $form, $formInputs);
446 foreach ($submissions as $submission) {
447 $submission->response = json_decode($submission->response, true);
448 }
449 header('Content-disposition: attachment; filename=' . sanitize_title($form->title, 'export', 'view') . '-' . date('Y-m-d') . '.json');
450 header('Content-type: application/json');
451 echo json_encode($submissions); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $submissions is escaped before being passed in.
452 exit();
453 }
454
455 private static function getSubmissions($args)
456 {
457 $tableName = Arr::get($args, 'table');
458
459 if ($tableName) {
460 $allowedTables = [
461 'fluentform_submissions',
462 'fluentform_draft_submissions',
463 ];
464 if (!in_array($tableName, $allowedTables, true)) {
465 wp_send_json([
466 'message' => __('Invalid table name for export.', 'fluentform')
467 ], 422);
468 }
469 $query = wpFluent()->table($tableName)
470 ->where('form_id', (int) Arr::get($args, 'form_id'))
471 ->orderBy('id', Helper::sanitizeOrderValue(Arr::get($args, 'sort_by', 'DESC')));
472
473 $searchString = Arr::get($args, 'search');
474 if ($searchString) {
475 global $wpdb;
476 $escaped = $wpdb->esc_like($searchString);
477 $query->where(function ($q) use ($escaped) {
478 $q->where('id', 'LIKE', "%{$escaped}%")
479 ->orWhere('response', 'LIKE', "%{$escaped}%");
480 });
481 }
482 } else {
483 $query = (new Submission)->customQuery($args);
484 }
485
486 $entries = fluentFormSanitizer(Arr::get($args, 'entries', []));
487 $query->when(is_array($entries) && (count($entries) > 0), function ($q) use ($entries) {
488 return $q->whereIn('id', $entries);
489 });
490
491 if (Arr::get($args, 'advanced_filter')) {
492 $query = apply_filters('fluentform/apply_entries_advance_filter', $query, $args);
493 }
494
495 return $query->get();
496 }
497
498 private static function downloadOfficeDoc($data, $type = 'csv', $fileName = null)
499 {
500 $data = array_map(function ($item) {
501 return array_map(function ($itemValue) {
502 if (is_array($itemValue)) {
503 return implode(', ', $itemValue);
504 }
505 return $itemValue;
506 }, $item);
507 }, $data);
508 // Load Composer autoloader for OpenSpout
509 require_once FLUENTFORM_DIR_PATH . '/vendor/autoload.php';
510 $fileName = ($fileName) ? $fileName . '.' . $type : 'export-data-' . date('d-m-Y') . '.' . $type;
511
512 // Create writer based on type
513 switch (strtolower($type)) {
514 case 'csv':
515 $writer = \OpenSpout\Writer\Common\Creator\WriterEntityFactory::createCSVWriter();
516 break;
517 case 'xlsx':
518 $writer = \OpenSpout\Writer\Common\Creator\WriterEntityFactory::createXLSXWriter();
519 break;
520 case 'ods':
521 $writer = \OpenSpout\Writer\Common\Creator\WriterEntityFactory::createODSWriter();
522 break;
523 default:
524 throw new \Exception(sprintf('Unsupported file type: %s', esc_html($type)));
525 }
526 $writer->openToBrowser($fileName);
527
528 // Convert data arrays to Row objects for OpenSpout v3
529 $rows = array_map(function ($rowData) {
530 return \OpenSpout\Writer\Common\Creator\WriterEntityFactory::createRowFromArray($rowData);
531 }, $data);
532
533 $writer->addRows($rows);
534 $writer->close();
535 die();
536 }
537
538 }
539