PluginProbe
UiCore Elements – Free widgets and templates for Elementor / 1.3.17
UiCore Elements – Free widgets and templates for Elementor v1.3.17
1.3.17 1.3.16 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 All 41 releases
uicore-elements / includes / utils / form-service.php

form-service.php in UiCore Elements – Free widgets and templates for Elementor 1.3.17, at includes/utils/form-service.php

940 lines 34.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace UiCoreElements\Utils;
4
5 use UiCoreELements\Utils\Newsletter_Services as Services;
6
7 class Email_Exception extends \Exception {}
8 class Redirect_Exception extends \Exception {}
9 class Submit_Exception extends \Exception {}
10 class Newsletter_Service_Exception extends \Exception {}
11
12 defined('ABSPATH') || exit();
13
14 /**
15 * Handles the form submissions and responses
16 */
17
18 class Contact_Form_Service
19 {
20
21 protected $form_data,
22 $settings,
23 $files;
24
25 public function __construct($form_data, $settings, $files)
26 {
27 $this->form_data = $form_data;
28 $this->settings = $settings;
29 $this->files = $files;
30 }
31
32 public function handle()
33 {
34
35 $data = [];
36 $responses = [];
37
38 // Checks for reCAPTCHA validation
39 if (isset($this->form_data['grecaptcha_token']) && !empty($this->form_data['grecaptcha_token'])) {
40
41 $recaptcha = $this->validate_recaptcha($this->form_data['grecaptcha_token'], $this->form_data['grecaptcha_version']);
42
43 if (!$recaptcha['success']) {
44 return [
45 'status' => 'error',
46 'data' => [
47 'message' => esc_html__('reCAPTCHA validation failed.', 'uicore-elements'),
48 ]
49 ];
50 }
51 }
52
53 // Check for honeypot spam
54 if (!$this->validate_spam()) {
55 return [
56 'status' => 'success',
57 'data' => [
58 'message' => $this->get_response_message('success') // Fakes a successfull submission
59 ]
60 ];
61 }
62
63 // Run all registered submit actions
64 if (isset($this->settings['submit_actions']) && !empty($this->settings['submit_actions'])) {
65 foreach ($this->settings['submit_actions'] as $action) {
66 try {
67 switch ($action) {
68 case 'email':
69 $data = $this->send_mail($action);
70 $responses['email'] = $data['response'];
71 break;
72
73 case 'email_2':
74 $data = $this->send_mail($action, $data);
75 $responses['email'] = $data['response'];
76 break;
77
78 case 'redirect':
79 $responses['redirect'] = $this->redirect();
80 break;
81
82 case 'popup':
83 $responses['popup'] = $this->popup();
84 break;
85
86 case 'webhook':
87 $responses['webhook'] = $this->webhook();
88 break;
89
90 case 'mailchimp':
91 $responses['mailchimp'] = $this->newsletter_service('mailchimp');
92 break;
93
94 case 'brevo':
95 $responses['brevo'] = $this->newsletter_service('brevo');
96 break;
97
98 case 'kit':
99 $responses['kit'] = $this->newsletter_service('kit');
100 break;
101
102 case 'moosend':
103 $responses['moosend'] = $this->newsletter_service('moosend');
104 break;
105
106 case 'getresponse':
107 $responses['getresponse'] = $this->newsletter_service('getresponse');
108 break;
109
110 case 'mailerlite':
111 $responses['mailerlite'] = $this->newsletter_service('mailerlite');
112 break;
113
114 default:
115 throw new Submit_Exception(esc_html__('Unknown submit action: ', 'uicore-elements') . $action . esc_html__('. Check your settings.', 'uicore-elements'));
116 }
117 } catch (Email_Exception $e) {
118 $responses['email'] = [
119 'status' => false,
120 'message' => $e->getMessage()
121 ];
122 } catch (Redirect_Exception $e) {
123 $responses['redirect'] = [
124 'status' => 'error',
125 'message' => $e->getMessage()
126 ];
127 } catch (Newsletter_Service_Exception $e) {
128 $responses['newsletter_service'] = [
129 'status' => 'error',
130 'service' => $action,
131 'message' => $e->getMessage()
132 ];
133 } catch (Submit_Exception $e) {
134 $responses['submit'] = [
135 'status' => 'error',
136 'message' => $e->getMessage()
137 ];
138 }
139 // We're avoiding throwing exception for mailchimp because would require a specific validation function, and is to much for now
140 }
141
142 // There's no need to continue without a submit action enabled
143 } else {
144 return [
145 'status' => 'error',
146 'data' => [
147 'message' => esc_html__('No submit action enabled.', 'uicore-elements')
148 ]
149 ];
150 }
151
152 // Consider `current_user_can( 'manage_options' )` as filter to return more specific messages on frontend (not tested)
153
154 // Since attachments may be used up to two times (both emails), they need to be deleted only after processing submits
155 if (isset($data['attachments']) && !empty($data['attachments']['files'])) {
156 register_shutdown_function('unlink', $data['attachments']['files']);
157 }
158
159 $output = $this->build_frontend_responses($responses);
160
161 return [
162 'status' => $output['status'],
163 'data' => $output['data'],
164 ];
165 }
166
167 /**
168 * Mail submition
169 */
170 protected function send_mail(string $action, array $data = [])
171 {
172
173 $attachments = isset($data['attachments']) ? $data['attachments'] : []; // Check if there's attachments from previous mail submit action
174
175 $mail_data = $this->compose_mail_data($action, $attachments); // build mail data
176
177 // Check if there's any attachment error before sending mail
178 if (!empty($mail_data['attachments']['errors'])) {
179 // throwing exceptions here will block proper data flow. Is best directly returning the error on email action
180 return [
181 'response' => [
182 'status' => false,
183 'message' => $mail_data['attachments']['errors']
184 ],
185 ];
186 }
187
188 $email = wp_mail(
189 $mail_data['email']['to'],
190 $mail_data['email']['subject'],
191 $mail_data['email']['message'],
192 $mail_data['email']['headers'],
193 $mail_data['email']['attachments']
194 );
195
196 return [
197 'response' => [
198 'status' => $email ? 'success' : 'error',
199 'message' => $email ? $this->get_response_message('success') : $this->get_response_message('mail_error')
200 ],
201 'attachments' => $mail_data['attachments'] // Return attachments for deletion and error handling
202 ];
203 }
204 protected function compose_mail_data(string $action, array $attachments = [])
205 {
206
207 // Set short vars for the data
208 $settings = $this->settings;
209 $data = $this->form_data;
210 $files = $this->files;
211
212 $slug = $action == 'email_2' ? '_2' : ''; // Update controls slugs based on the mail submit type
213 $line_break = $settings['email_content_type' . $slug] === 'html' ? '<br>' : "\n"; // Set line break type
214
215 // Replace shortcodes by form data
216 $content = $this->replace_content_shortcode($settings['email_content' . $slug], $line_break);
217
218 // Adds the metadata to content
219 $content = $this->compose_metadata($content, $settings['form_metadata' . $slug], $line_break);
220
221 // Set empty attachments to avoid undefined errors for widgets without attachment options
222 if ($data['widget_type'] !== 'contact-form') {
223 $attachments = ['files' => '', 'errors' => ''];
224 } else {
225 $attachments = !empty($attachments) ? $attachments : $this->prepare_attachments($files); // If theres attachments from previous submit action, use it, otherwhise prepare it from $files,
226 }
227
228 // Validate and replace fields shortcodes
229 $mail_to = $this->replace_content_shortcode($this->validate_field($settings['email_to' . $slug], 'Recipient (to)'));
230 $mail_subject = $this->replace_content_shortcode($this->validate_field($settings['email_subject' . $slug], 'Subject'));
231 $mail_name = $this->replace_content_shortcode($this->validate_field($settings['email_from_name' . $slug], 'From Name'));
232 $mail_from = $this->replace_content_shortcode($this->validate_field($settings['email_from' . $slug], 'From'));
233 $mail_reply = $this->replace_content_shortcode($this->validate_field($settings['email_reply_to' . $slug], 'Reply To'));
234
235 // Build the data
236 $mail_data = [
237 'to' => $mail_to,
238 'subject' => $mail_subject,
239 'message' => $content,
240 'headers' => [
241 'Content-Type: text/' . $settings['email_content_type' . $slug] . '; charset=UTF-8',
242 'From: ' . $mail_name . ' <' . $mail_from . '>',
243 'Reply-To: ' . $mail_reply,
244 ],
245 'attachments' => $attachments['files']
246 ];
247
248 // Build optional data
249 if (!empty($settings['email_to_cc' . $slug])) {
250 $mail_data['headers'][] = 'Cc: ' . $this->replace_content_shortcode($settings['email_to_cc' . $slug]);
251 }
252 if (!empty($settings['email_to_bcc' . $slug])) {
253 $mail_data['headers'][] = 'Bcc: ' . $this->replace_content_shortcode($settings['email_to_bcc' . $slug]);
254 }
255
256 return [
257 'email' => $mail_data,
258 'attachments' => $attachments
259 ];
260 }
261 protected function replace_content_shortcode(string $content, string $line_break = '')
262 {
263
264 // Set short vars for the data
265 $fields = $this->get_setting_fields();
266 $form_data = $this->form_data;
267
268 // [all-fieds] shortcode replacement
269 if (false !== strpos($content, '[all-fields]')) {
270 $text = '';
271 // Return formated text as key: value
272 foreach ($form_data['form_fields'] as $key => $field) {
273 $field_value = is_array($field) ? implode(', ', $field) : $field;
274 $text .= !empty($field_value) ? sprintf('%s: %s', $key, $field_value) . $line_break : '';
275 }
276 $content = str_replace('[all-fields]', $text, $content);
277 }
278
279 // Custom [field id="{id}"] shortcode replacement
280 foreach ($fields as $field) {
281 $shortcode = '[field id="' . $field['custom_id'] . '"]';
282 $value = isset($form_data['form_fields'][$field['custom_id']]) ? $form_data['form_fields'][$field['custom_id']] : '';
283 $value = is_array($value) ? implode(', ', $value) : $value;
284 $content = str_replace($shortcode, $value, $content);
285 }
286
287 // Replaces all manual line breaks from content
288 if (!empty($line_break)) {
289 $content = str_replace(array("\r\n", "\r", "\n"), $line_break, $content);
290 }
291
292 return $content;
293 }
294 protected function prepare_attachments(array $files)
295 {
296 $attachments = [];
297 $errors = '';
298
299 if (!isset($files['form_fields']) || empty($files['form_fields'])) {
300 return [
301 'files' => '',
302 'errors' => ''
303 ];
304 }
305
306 // Requires wp_handle_upload() file if unavailable
307 if (! function_exists('wp_handle_upload')) {
308 require_once(ABSPATH . 'wp-admin/includes/file.php');
309 }
310
311 // Check if theres a valid file to upload
312 foreach ($files['form_fields']['tmp_name'] as $input => $value) {
313 if ($files['form_fields']['error'][$input] !== UPLOAD_ERR_NO_FILE) {
314 $file = [
315 'name' => $files['form_fields']['name'][$input],
316 'type' => $files['form_fields']['type'][$input],
317 'tmp_name' => $files['form_fields']['tmp_name'][$input],
318 'error' => $files['form_fields']['error'][$input],
319 'size' => $files['form_fields']['size'][$input],
320 ];
321
322 // Handle the file upload
323 $uploaded_file = wp_handle_upload($file, ['test_form' => false]);
324
325 if (!isset($uploaded_file['error'])) {
326 $attachments = $uploaded_file['file'];
327 } else {
328 // Since throwing exceptions here will block the proper data flow, we return the error and let send_mail() handle it
329 $errors = esc_html__('Failed to upload file: ', 'uicore-elements') . $uploaded_file['error'];
330 }
331
332 // Break after processing the first valid file
333 break;
334 }
335 }
336
337 return [
338 'files' => $attachments,
339 'errors' => $errors
340 ];
341 }
342 protected function compose_metadata(string $content, array $metadada, string $line_break)
343 {
344
345 if (empty($metadada)) {
346 return $content;
347 }
348
349 $content = $content . $line_break . $line_break . '--' . $line_break . $line_break; // Adds spacing between content and metadata
350
351 foreach ($metadada as $meta) {
352 switch ($meta) {
353 case 'date':
354 $content .= sprintf('%s: %s', 'Date', wp_date('Y-m-d') . $line_break);
355 break;
356
357 case 'time':
358 $content .= sprintf('%s: %s', 'Time', wp_date('H:i:s') . $line_break);
359 break;
360
361 case 'remote_ip':
362 $content .= isset($_SERVER['REMOTE_ADDR'])
363 ? sprintf('%s: %s', 'IP', sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) . $line_break)
364 : '';
365 break;
366
367 case 'user_agent':
368 $content .= isset($_SERVER['HTTP_USER_AGENT'])
369 ? sprintf('%s: %s', 'User Agent', sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) . $line_break)
370 : '';
371 break;
372
373 case 'page_url':
374 $content .= isset($_SERVER['HTTP_REFERER'])
375 ? sprintf('%s: %s', 'Page URL', sanitize_text_field(wp_unslash($_SERVER['HTTP_REFERER'])) . $line_break)
376 : '';
377 break;
378 }
379 }
380
381 return $content;
382 }
383
384 /**
385 * Extra submissions
386 */
387 protected function redirect()
388 {
389
390 $validation = $this->validate_url($this->settings['redirect_to']);
391
392 // Above function exception blocks this execution
393 return [
394 'status' => 'success',
395 'url' => esc_url($validation['url']),
396 'delay' => 1500,
397 'message' => esc_html($this->get_response_message('redirect'))
398 ];
399 }
400 protected function popup()
401 {
402 $action = $this->settings['popup_action'];
403
404 if ($action === 'open') {
405 return [
406 'status' => 'success',
407 'action' => sanitize_text_field($action),
408 'id' => sanitize_text_field($this->settings['open_popup']),
409 'message' => sanitize_text_field($this->get_response_message('popup'))
410 ];
411 }
412
413 return [
414 'status' => 'success',
415 'action' => sanitize_text_field($action),
416 'message' => sanitize_text_field($this->get_response_message('popup'))
417 ];
418 }
419 protected function webhook(): array
420 {
421 $request = $this->build_webhook_request();
422
423 $response = wp_remote_post($request['url'], $request['args']);
424
425 if (is_wp_error($response)) {
426 return [
427 'status' => 'error',
428 'message' => $response->get_error_message(),
429 ];
430 }
431
432 $status_code = (int) wp_remote_retrieve_response_code($response);
433
434 if ($status_code < 200 || $status_code >= 300) {
435 $body = wp_remote_retrieve_body($response);
436 $body = is_string($body) ? trim(wp_strip_all_tags($body)) : '';
437 $body = $body ? wp_html_excerpt($body, 160, '...') : esc_html__('Unexpected response from webhook endpoint.', 'uicore-elements');
438
439 return [
440 'status' => 'error',
441 'message' => sprintf(
442 /* translators: 1: HTTP status code, 2: Response body excerpt. */
443 esc_html__('Webhook request failed with HTTP %1$s: %2$s', 'uicore-elements'),
444 $status_code,
445 $body
446 ),
447 ];
448 }
449
450 return [
451 'status' => 'success',
452 'message' => esc_html__('Webhook sent successfully.', 'uicore-elements'),
453 ];
454 }
455 protected function newsletter_service(string $service)
456 {
457 $settings = $this->settings;
458 $data = $this->get_submission_data_to_service_fields();
459
460 $api_key = get_option('uicore_elements_newsletter_service_key');
461 $fields = $data['fields'];
462 $list = array_map('trim', explode(',', $settings['mailchimp_audience_id']));
463 $custom_fields = $data['custom_fields'];
464
465 $instance = new Services($api_key, $fields, $list, $custom_fields);
466 $response = $instance->handle($service);
467
468 return $response;
469 }
470
471 /**
472 * Validations
473 */
474 protected function validate_recaptcha(string $token, string $version)
475 {
476
477 // Check if secret and site key are set
478 if (!get_option('uicore_elements_recaptcha_secret_key') || !get_option('uicore_elements_recaptcha_site_key')) {
479 return [
480 'success' => false,
481 'message' => esc_html__('reCAPTCHA API keys are not set.', 'uicore-elements')
482 ];
483 }
484
485 $data = [
486 'secret' => get_option('uicore_elements_recaptcha_secret_key'),
487 'response' => sanitize_text_field($token)
488 ];
489
490 $response = wp_remote_post("https://www.google.com/recaptcha/api/siteverify", [
491 'body' => $data,
492 'timeout' => 15,
493 ]);
494
495 if (is_wp_error($response)) {
496 return [
497 'success' => false,
498 'message' => $response->get_error_message()
499 ];
500 }
501
502 $captcha = json_decode(wp_remote_retrieve_body($response));
503
504 if ($version === 'V3') {
505 return ['success' => ($captcha->success && $captcha->score >= 0.5) ? true : false];
506 }
507
508 // V2 default
509 return ['success' => $captcha->success];
510 }
511 protected function validate_spam()
512 {
513 // `ui-e-h-p` is the key for the honeypot
514 return (isset($this->form_data['ui-e-h-p']) && !empty($this->form_data['ui-e-h-p'])) ? false : true;
515 }
516 protected function validate_url(string $url)
517 {
518 if (empty($url)) {
519 throw new Redirect_Exception(esc_html($this->get_response_message('redirect_no_url')));
520 }
521
522 return [
523 'status' => true,
524 'url' => $url,
525 ];
526 }
527 protected function validate_webhook(string $url)
528 {
529 if (empty($url)) {
530 throw new Submit_Exception(esc_html__('Webhook URL is empty.', 'uicore-elements'));
531 }
532
533 $url = esc_url_raw($url);
534
535 if (!$url || !wp_http_validate_url($url)) {
536 throw new Submit_Exception(esc_html__('Webhook URL is invalid.', 'uicore-elements'));
537 }
538
539 return $url;
540 }
541 protected function validate_field(string $field, string $label)
542 {
543 if (empty($field)) {
544 throw new Submit_Exception(esc_html($this->get_response_message('empty_field', esc_html($label))));
545 }
546 return $field;
547 }
548
549 /**
550 * Helpers
551 */
552 protected function get_setting_fields()
553 {
554 // Used to determine if the widget fields are repeaters with custom IDS or fixed fields, and if fixed fields
555 // compose them into an array similar to repeaters, to simplify shortcode replacement function
556
557 switch ($this->form_data['widget_type']) {
558
559 case 'newsletter':
560 return [
561 ['custom_id' => 'email'],
562 ['custom_id' => 'name'],
563 ];
564 break;
565
566 // Dynamic fields values
567 default:
568 return $this->settings['form_fields'];
569 break;
570 }
571 }
572 protected function get_submission_data_to_service_fields(): array
573 {
574 $settings = $this->settings;
575 $widget = $this->form_data['widget_type'];
576
577 // Newsletter widget works only with email and perpahs name
578 if ($widget === 'newsletter') {
579 return [
580 'fields' => [
581 'email' => $this->replace_content_shortcode('[field id="email"]'),
582 'name' => $this->replace_content_shortcode('[field id="name"]'),
583 ],
584 'custom_fields' => []
585 ];
586 }
587
588 // Contact Form case has custom fields and allows fields ID change
589
590 $repeater_fields = $settings['newsletter_service_custom_fields'];
591
592 $fields = [
593 'email' => 'mailchimp_email_id',
594 'name' => 'mailchimp_fname_id',
595 'last_name' => 'mailchimp_lname_id',
596 'phone' => 'mailchimp_phone_id',
597 'birthday' => 'mailchimp_birthday_id',
598 ];
599
600 $custom_fields = [];
601
602 // Build custom field stack
603 if (is_array($repeater_fields) && !empty($repeater_fields)) {
604 foreach ($repeater_fields as $field) {
605 $custom_fields[$field['field_name']] = [
606 'value' => $this->replace_content_shortcode($field['field_id']), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
607 'method' => $this->get_custom_field_sanitization_method($field['field_type']), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
608 ];
609 }
610 }
611
612 // Remove not used fields from the stack, and extract the content of used fields, if they're using shortcodes
613 foreach ($fields as $data => $field) {
614 if (isset($settings[$field]) && !empty($settings[$field])) {
615 $fields[$data] = $this->replace_content_shortcode($settings[$field]);
616 } else if ($data !== 'email' && isset($field[$data])) {
617 unset($field[$data]);
618 }
619 }
620
621 return [
622 'fields' => $fields,
623 'custom_fields' => $custom_fields
624 ];
625 }
626 protected function get_custom_field_sanitization_method(string $type): string
627 {
628 // Values are default wp or custom methods from our newsletter services class,
629 // while $type should be field type options from form component class
630 switch ($type) {
631 case 'email':
632 return 'sanitize_email';
633 case 'phone':
634 return 'sanitize_phone';
635 case 'birthday':
636 return 'sanitize_birthday';
637 default:
638 return 'sanitize_text_field';
639 }
640 }
641 protected function all_submissions_succedded($responses)
642 {
643 foreach ($responses as $submission => $data) {
644 // Redirect and popup actions failure shouldn't return `error` to main status because is not properly a submission action, so we skip it.
645 if (in_array($submission, ['redirect', ' popup'])) {
646 continue;
647 }
648 if (
649 isset($data['status']) &&
650 (
651 $data['status'] === 'error' ||
652 $data['status'] === false
653 )
654 ) {
655 return false;
656 }
657 }
658 return true;
659 }
660 public static function get_form_submit_options()
661 {
662
663 $options = [
664 'email' => esc_html__('Email', 'uicore-elements'),
665 'email_2' => esc_html__('Email 2', 'uicore-elements'),
666 'redirect' => esc_html__('Redirect', 'uicore-elements'),
667 'webhook' => esc_html__('Webhook', 'uicore-elements')
668 ];
669
670 // Uicore Framework dependent actions
671 if (defined('UICORE_ASSETS')) {
672 $options['popup'] = esc_html('Popup', 'uicore-elements');
673 }
674
675 $services = Services::get_services_list();
676
677 if (is_array($services) || empty($services)) {
678 $options = array_merge($options, $services);
679 }
680
681 return $options;
682 }
683
684 /**
685 * Webhook Helpers
686 */
687 protected function build_webhook_request(): array
688 {
689 $url = $this->replace_content_shortcode($this->validate_field($this->settings['webhook_url'] ?? '', 'Webhook URL'));
690 $url = $this->validate_webhook($url);
691
692 $advanced_data = ($this->settings['webhook_advanced_data'] ?? '') === 'true';
693 $args = [
694 'timeout' => 15,
695 ];
696
697 if ($advanced_data) {
698 $args['headers'] = [
699 'Content-Type' => 'application/json; charset=UTF-8',
700 ];
701 $args['body'] = wp_json_encode($this->get_advanced_webhook_payload());
702 } else {
703 $args['body'] = [
704 'widget_type' => sanitize_text_field($this->form_data['widget_type'] ?? ''),
705 'fields' => $this->get_submission_fields_payload(),
706 'meta' => $this->get_webhook_meta_payload(),
707 ];
708 }
709
710 return [
711 'url' => $url,
712 'args' => $args,
713 ];
714 }
715 protected function get_advanced_webhook_payload(): array
716 {
717 return $this->sanitize_recursive_payload($this->form_data);
718 }
719 protected function get_webhook_meta_payload(): array
720 {
721 $meta = [
722 'widget_id' => sanitize_text_field($this->form_data['widget_id'] ?? ''),
723 'submitted_at' => wp_date('c'),
724 ];
725
726 if (!empty($this->form_data['post_id'])) {
727 $meta['post_id'] = absint($this->form_data['post_id']);
728 }
729
730 if (isset($_SERVER['HTTP_REFERER'])) {
731 $meta['page_url'] = esc_url_raw(wp_unslash($_SERVER['HTTP_REFERER']));
732 }
733
734 return $meta;
735 }
736 protected function get_submission_fields_payload(): array
737 {
738 $payload = [];
739
740 foreach ($this->get_setting_fields() as $field) {
741 if (empty($field['custom_id'])) {
742 continue;
743 }
744
745 $field_id = $field['custom_id'];
746 $value = $this->form_data['form_fields'][$field_id] ?? '';
747
748 if (is_array($value)) {
749 $payload[$field_id] = array_map('sanitize_text_field', $value);
750 continue;
751 }
752
753 $payload[$field_id] = sanitize_text_field($value);
754 }
755
756 return $payload;
757 }
758 protected function sanitize_recursive_payload($value)
759 {
760 if (is_array($value)) {
761 foreach ($value as $key => $item) {
762 $value[sanitize_text_field((string) $key)] = $this->sanitize_recursive_payload($item);
763 if ((string) $key !== sanitize_text_field((string) $key)) {
764 unset($value[$key]);
765 }
766 }
767
768 return $value;
769 }
770
771 return sanitize_text_field((string) $value);
772 }
773
774 /**
775 * Responses
776 */
777 // Also used by form widget(s), therefore public and static
778 public static function get_default_messages()
779 {
780 return [
781 // main messages
782 'success' => esc_html__('Your submission was successful.', 'uicore-elements'),
783 'error' => esc_html__('Your submission failed because of an error.', 'uicore-elements'),
784 'mail_error' => esc_html__('Failed to send email.', 'uicore-elements'),
785 'required' => esc_html__('Fill all required fields.', 'uicore-elements'),
786 'redirect' => esc_html__('Redirecting...', 'uicore-elements'),
787 ];
788 }
789 protected function get_response_message(string $status, string $dinamic_data = '')
790 {
791 // non-customizable messages (for settings debugging only)
792 $default_messages = [
793 'invalid_status' => esc_html__('Invalid status message.', 'uicore-elements'),
794 'redirect_no_url' => esc_html__('Redirection failed. No URL set.', 'uicore-elements'),
795 'empty_field' => esc_html__('The following field is empty: ', 'uicore-elements') . $dinamic_data,
796 ];
797
798 if ($this->settings['custom_messages'] === 'yes') {
799 $messages = [
800 'success' => esc_html($this->settings['success_message']),
801 'error' => esc_html($this->settings['error_message']),
802 'mail_error' => esc_html($this->settings['mail_error_message']),
803 'redirect' => esc_html($this->settings['redirect_message']),
804 ];
805 } else {
806 $messages = self::get_default_messages();
807 }
808
809 $messages = array_merge($default_messages, $messages);
810
811 return isset($messages[$status]) ? $messages[$status] : $messages['invalid_status'];
812 }
813 protected function build_frontend_responses($responses)
814 {
815 $data = [];
816
817 // Mail response
818 if (isset($responses['email']) && $responses['email']['status'] !== 'success') {
819 $data['email'] = $responses['email'];
820 }
821
822 // Mail attachment response - is always an error
823 if (isset($responses['email']) && isset($responses['email']['attachment'])) {
824 $data['attachment'] = $responses['attachment'];
825 }
826
827 // Submit Actions response - is always an error
828 if (isset($responses['submit'])) {
829 $data['submit'] = $responses['submit'];
830 }
831
832 if (isset($responses['webhook']) && $responses['webhook']['status'] !== 'success') {
833 $data['submit'] = $responses['webhook'];
834 }
835
836 // Newsletter Services responses
837 $data = $this->build_services_responses($responses);
838
839 // Main response
840 $status = $this->all_submissions_succedded($responses) ? 'success' : 'error';
841 $data['message'] = $this->get_response_message($status);
842
843 // Below responses should work only if all previous submitions hasn't failed
844 if (isset($responses['redirect']) && $status === 'success') {
845 $data['redirect'] = $responses['redirect'];
846 }
847 if (isset($responses['popup']) && $status === 'success') {
848 $data['popup'] = $responses['popup'];
849 }
850
851 // The only successfull response that should be sent is 'main' and 'redirect'
852 return [
853 'status' => $status,
854 'data' => $data
855 ];
856 }
857
858 protected function build_services_responses($responses)
859 {
860 $success = true;
861 $service = '';
862 $status = '';
863 $detail = '';
864
865 if (isset($responses['mailchimp'])) {
866 // We know mailchimp failed if status is an integer
867 // If one response fail, in multiple list cases, returns the failure
868 foreach ($responses['mailchimp'] as $response) {
869 if (is_int($response['status'])) {
870 $success = false;
871 $service = 'Mailchimp';
872 $status = 'HTTP: ' . $response['status'];
873 $detail = $response['detail'];
874 break;
875 }
876 }
877 } else if (isset($responses['brevo'])) {
878 // We know Brevo failed if 'code' is set in the response
879 if (isset($responses['brevo']['code'])) {
880 $success = false;
881 $service = 'Brevo';
882 $status = 'Error: ' . $responses['brevo']['code'];
883 $detail = $responses['brevo']['message'] ?? '';
884 }
885 } else if (isset($responses['getresponse'])) {
886 // We know GetResponse failed if 'httpStatus' is set in the response
887 foreach ($responses['getresponse'] as $response) {
888 if (isset($response['httpStatus'])) {
889 $success = false;
890 $service = 'GetResponse';
891 $status = 'HTTP: ' . $response['httpStatus'];
892 $detail = 'Error: ' . $response['code'] . ' - ' . $response['message'];
893 break;
894 }
895 }
896 } else if (isset($responses['kit'])) {
897 // We know Kit failed if 'error' is set in the response
898 foreach ($responses['kit'] as $response) {
899 if (isset($response['error'])) {
900 $success = false;
901 $service = 'Kit';
902 $status = 'Error: ' . $response['error'];
903 $detail = $response['message'];
904 break;
905 }
906 }
907 } else if (isset($responses['moosend'])) {
908 // We know Moosend failed if 'Error' is set in the response
909 foreach ($responses['moosend'] as $response) {
910 if (!empty($response['Error'])) {
911 $success = false;
912 $service = 'Moosend';
913 $status = 'HTTP: ' . $response['Code'];
914 $detail = $response['Error'];
915 break;
916 }
917 }
918 } else if (isset($responses['mailerlite'])) {
919 // We know MailerLite failed if 'message' is set in the response
920 if (isset($responses['mailerlite']['message'])) {
921 $success = false;
922 $service = 'MailerLite';
923 $status = 'Error';
924 $detail = $responses['mailerlite']['message'] ?? '';
925 }
926 }
927
928 // Return the error response if the service fails
929 if ($success === false) {
930 $data['newsletter_service'] = [
931 'status' => 'error',
932 /* translators: 1: Service Provider, 2: Service Error code, 3: Service Status Response */
933 'message' => sprintf(esc_html__('[%1$s] %2$s - %3$s', 'uicore-elements'), $service, $status, $detail)
934 ];
935 }
936
937 return [];
938 }
939 }
940