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
← All changes | includes/utils/form-service.php +506 -175 1.0.161.3.17 View file →
@@ -1,11 +1,14 @@
1 1 <?php
2 +
2 3 namespace UiCoreElements\Utils;
3 4
5 +use UiCoreELements\Utils\Newsletter_Services as Services;
6 +
4 7 class Email_Exception extends \Exception {}
5 8 class Redirect_Exception extends \Exception {}
6 9 class Submit_Exception extends \Exception {}
7 -class Mailchimp_Exception extends \Exception {}
10 +class Newsletter_Service_Exception extends \Exception {}
8 11
9 12 defined('ABSPATH') || exit();
10 13
11 14 /**
@@ -11,21 +14,24 @@
11 14 /**
12 15 * Handles the form submissions and responses
13 16 */
14 17
15 -class Contact_Form_Service {
18 +class Contact_Form_Service
19 +{
16 20
17 21 protected $form_data,
18 - $settings,
19 - $files;
22 + $settings,
23 + $files;
20 24
21 - public function __construct($form_data, $settings, $files) {
25 + public function __construct($form_data, $settings, $files)
26 + {
22 27 $this->form_data = $form_data;
23 28 $this->settings = $settings;
24 29 $this->files = $files;
25 30 }
26 31
27 - public function handle() {
32 + public function handle()
33 + {
28 34
29 35 $data = [];
30 36 $responses = [];
31 37
@@ -33,9 +39,9 @@
33 39 if (isset($this->form_data['grecaptcha_token']) && !empty($this->form_data['grecaptcha_token'])) {
34 40
35 41 $recaptcha = $this->validate_recaptcha($this->form_data['grecaptcha_token'], $this->form_data['grecaptcha_version']);
36 42
37 - if(!$recaptcha['success']){
43 + if (!$recaptcha['success']) {
38 44 return [
39 45 'status' => 'error',
40 46 'data' => [
41 47 'message' => esc_html__('reCAPTCHA validation failed.', 'uicore-elements'),
@@ -44,9 +50,9 @@
44 50 }
45 51 }
46 52
47 53 // Check for honeypot spam
48 - if(!$this->validate_spam()){
54 + if (!$this->validate_spam()) {
49 55 return [
50 56 'status' => 'success',
51 57 'data' => [
52 58 'message' => $this->get_response_message('success') // Fakes a successfull submission
@@ -63,9 +69,9 @@
63 69 $data = $this->send_mail($action);
64 70 $responses['email'] = $data['response'];
65 71 break;
66 72
67 - case 'email_2' :
73 + case 'email_2':
68 74 $data = $this->send_mail($action, $data);
69 75 $responses['email'] = $data['response'];
70 76 break;
71 77
@@ -72,12 +78,40 @@
72 78 case 'redirect':
73 79 $responses['redirect'] = $this->redirect();
74 80 break;
75 81
82 + case 'popup':
83 + $responses['popup'] = $this->popup();
84 + break;
85 +
86 + case 'webhook':
87 + $responses['webhook'] = $this->webhook();
88 + break;
89 +
76 90 case 'mailchimp':
77 - $responses['mailchimp'] = $this->mailchimp();
91 + $responses['mailchimp'] = $this->newsletter_service('mailchimp');
78 92 break;
79 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 +
80 114 default:
81 115 throw new Submit_Exception(esc_html__('Unknown submit action: ', 'uicore-elements') . $action . esc_html__('. Check your settings.', 'uicore-elements'));
82 116 }
83 117 } catch (Email_Exception $e) {
@@ -89,11 +123,12 @@
89 123 $responses['redirect'] = [
90 124 'status' => 'error',
91 125 'message' => $e->getMessage()
92 126 ];
93 - } catch (Mailchimp_Exception $e) {
94 - $responses['mailchimp'] = [
127 + } catch (Newsletter_Service_Exception $e) {
128 + $responses['newsletter_service'] = [
95 129 'status' => 'error',
130 + 'service' => $action,
96 131 'message' => $e->getMessage()
97 132 ];
98 133 } catch (Submit_Exception $e) {
99 134 $responses['submit'] = [
@@ -103,9 +138,9 @@
103 138 }
104 139 // We're avoiding throwing exception for mailchimp because would require a specific validation function, and is to much for now
105 140 }
106 141
107 - // There's no need to continue without a submit action enabled
142 + // There's no need to continue without a submit action enabled
108 143 } else {
109 144 return [
110 145 'status' => 'error',
111 146 'data' => [
@@ -116,9 +151,9 @@
116 151
117 152 // Consider `current_user_can( 'manage_options' )` as filter to return more specific messages on frontend (not tested)
118 153
119 154 // Since attachments may be used up to two times (both emails), they need to be deleted only after processing submits
120 - if ( isset($data['attachments']) && !empty($data['attachments']['files']) ) {
155 + if (isset($data['attachments']) && !empty($data['attachments']['files'])) {
121 156 register_shutdown_function('unlink', $data['attachments']['files']);
122 157 }
123 158
124 159 $output = $this->build_frontend_responses($responses);
@@ -131,9 +166,10 @@
131 166
132 167 /**
133 168 * Mail submition
134 169 */
135 - protected function send_mail(string $action, array $data = []){
170 + protected function send_mail(string $action, array $data = [])
171 + {
136 172
137 173 $attachments = isset($data['attachments']) ? $data['attachments'] : []; // Check if there's attachments from previous mail submit action
138 174
139 175 $mail_data = $this->compose_mail_data($action, $attachments); // build mail data
@@ -164,9 +200,10 @@
164 200 ],
165 201 'attachments' => $mail_data['attachments'] // Return attachments for deletion and error handling
166 202 ];
167 203 }
168 - protected function compose_mail_data(string $action, array $attachments = []) {
204 + protected function compose_mail_data(string $action, array $attachments = [])
205 + {
169 206
170 207 // Set short vars for the data
171 208 $settings = $this->settings;
172 209 $data = $this->form_data;
@@ -172,29 +209,29 @@
172 209 $data = $this->form_data;
173 210 $files = $this->files;
174 211
175 212 $slug = $action == 'email_2' ? '_2' : ''; // Update controls slugs based on the mail submit type
176 - $line_break = $settings['email_content_type'.$slug] === 'html' ? '<br>' : "\n"; // Set line break type
213 + $line_break = $settings['email_content_type' . $slug] === 'html' ? '<br>' : "\n"; // Set line break type
177 214
178 215 // Replace shortcodes by form data
179 - $content = $this->replace_content_shortcode( $settings['email_content'.$slug], $line_break );
216 + $content = $this->replace_content_shortcode($settings['email_content' . $slug], $line_break);
180 217
181 218 // Adds the metadata to content
182 - $content = $this->compose_metadata($content, $settings['form_metadata'.$slug], $line_break);
219 + $content = $this->compose_metadata($content, $settings['form_metadata' . $slug], $line_break);
183 220
184 221 // Set empty attachments to avoid undefined errors for widgets without attachment options
185 - if($data['widget_type'] !== 'contact-form') {
186 - $attachments = [ 'files' => '', 'errors' => '' ];
222 + if ($data['widget_type'] !== 'contact-form') {
223 + $attachments = ['files' => '', 'errors' => ''];
187 224 } else {
188 225 $attachments = !empty($attachments) ? $attachments : $this->prepare_attachments($files); // If theres attachments from previous submit action, use it, otherwhise prepare it from $files,
189 226 }
190 227
191 228 // Validate and replace fields shortcodes
192 - $mail_to = $this->replace_content_shortcode( $this->validate_field($settings['email_to'.$slug], 'Recipient (to)'));
193 - $mail_subject = $this->replace_content_shortcode( $this->validate_field($settings['email_subject'.$slug], 'Subject'));
194 - $mail_name = $this->replace_content_shortcode( $this->validate_field($settings['email_from_name'.$slug], 'From Name'));
195 - $mail_from = $this->replace_content_shortcode( $this->validate_field($settings['email_from'.$slug], 'From'));
196 - $mail_reply = $this->replace_content_shortcode( $this->validate_field($settings['email_reply_to'.$slug], 'Reply To'));
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'));
197 234
198 235 // Build the data
199 236 $mail_data = [
200 237 'to' => $mail_to,
@@ -200,10 +237,10 @@
200 237 'to' => $mail_to,
201 238 'subject' => $mail_subject,
202 239 'message' => $content,
203 240 'headers' => [
204 - 'Content-Type: text/' . $settings['email_content_type'.$slug] . '; charset=UTF-8',
205 - 'From: ' . $mail_name . ' <'.$mail_from.'>',
241 + 'Content-Type: text/' . $settings['email_content_type' . $slug] . '; charset=UTF-8',
242 + 'From: ' . $mail_name . ' <' . $mail_from . '>',
206 243 'Reply-To: ' . $mail_reply,
207 244 ],
208 245 'attachments' => $attachments['files']
209 246 ];
@@ -208,13 +245,13 @@
208 245 'attachments' => $attachments['files']
209 246 ];
210 247
211 248 // Build optional data
212 - if (!empty($settings['email_to_cc'.$slug])) {
213 - $mail_data['headers'][] = 'Cc: ' . $settings['email_to_cc'];
249 + if (!empty($settings['email_to_cc' . $slug])) {
250 + $mail_data['headers'][] = 'Cc: ' . $this->replace_content_shortcode($settings['email_to_cc' . $slug]);
214 251 }
215 - if (!empty($settings['email_to_bcc'.$slug])) {
216 - $mail_data['headers'][] = 'Bcc: ' . $settings['email_to_bcc'];
252 + if (!empty($settings['email_to_bcc' . $slug])) {
253 + $mail_data['headers'][] = 'Bcc: ' . $this->replace_content_shortcode($settings['email_to_bcc' . $slug]);
217 254 }
218 255
219 256 return [
220 257 'email' => $mail_data,
@@ -220,9 +257,10 @@
220 257 'email' => $mail_data,
221 258 'attachments' => $attachments
222 259 ];
223 260 }
224 - protected function replace_content_shortcode(string $content, string $line_break = ''){
261 + protected function replace_content_shortcode(string $content, string $line_break = '')
262 + {
225 263
226 264 // Set short vars for the data
227 265 $fields = $this->get_setting_fields();
228 266 $form_data = $this->form_data;
@@ -227,16 +265,16 @@
227 265 $fields = $this->get_setting_fields();
228 266 $form_data = $this->form_data;
229 267
230 268 // [all-fieds] shortcode replacement
231 - if ( false !== strpos( $content, '[all-fields]' ) ) {
269 + if (false !== strpos($content, '[all-fields]')) {
232 270 $text = '';
233 271 // Return formated text as key: value
234 - foreach ( $form_data['form_fields'] as $key => $field ) {
272 + foreach ($form_data['form_fields'] as $key => $field) {
235 273 $field_value = is_array($field) ? implode(', ', $field) : $field;
236 274 $text .= !empty($field_value) ? sprintf('%s: %s', $key, $field_value) . $line_break : '';
237 275 }
238 - $content = str_replace( '[all-fields]', $text, $content );
276 + $content = str_replace('[all-fields]', $text, $content);
239 277 }
240 278
241 279 // Custom [field id="{id}"] shortcode replacement
242 280 foreach ($fields as $field) {
@@ -246,19 +284,20 @@
246 284 $content = str_replace($shortcode, $value, $content);
247 285 }
248 286
249 287 // Replaces all manual line breaks from content
250 - if(!empty($line_break)){
251 - $content = str_replace( array( "\r\n", "\r", "\n" ), $line_break, $content );
288 + if (!empty($line_break)) {
289 + $content = str_replace(array("\r\n", "\r", "\n"), $line_break, $content);
252 290 }
253 291
254 292 return $content;
255 293 }
256 - protected function prepare_attachments(array $files) {
294 + protected function prepare_attachments(array $files)
295 + {
257 296 $attachments = [];
258 297 $errors = '';
259 298
260 - if( !isset($files['form_fields']) || empty($files['form_fields']) ) {
299 + if (!isset($files['form_fields']) || empty($files['form_fields'])) {
261 300 return [
262 301 'files' => '',
263 302 'errors' => ''
264 303 ];
@@ -264,10 +303,10 @@
264 303 ];
265 304 }
266 305
267 306 // Requires wp_handle_upload() file if unavailable
268 - if ( ! function_exists( 'wp_handle_upload' ) ) {
269 - require_once( ABSPATH . 'wp-admin/includes/file.php' );
307 + if (! function_exists('wp_handle_upload')) {
308 + require_once(ABSPATH . 'wp-admin/includes/file.php');
270 309 }
271 310
272 311 // Check if theres a valid file to upload
273 312 foreach ($files['form_fields']['tmp_name'] as $input => $value) {
@@ -299,9 +338,10 @@
299 338 'files' => $attachments,
300 339 'errors' => $errors
301 340 ];
302 341 }
303 - protected function compose_metadata(string $content, array $metadada, string $line_break){
342 + protected function compose_metadata(string $content, array $metadada, string $line_break)
343 + {
304 344
305 345 if (empty($metadada)) {
306 346 return $content;
307 347 }
@@ -308,27 +348,33 @@
308 348
309 349 $content = $content . $line_break . $line_break . '--' . $line_break . $line_break; // Adds spacing between content and metadata
310 350
311 351 foreach ($metadada as $meta) {
312 - switch($meta){
352 + switch ($meta) {
313 353 case 'date':
314 - $content .= sprintf( '%s: %s', 'Date', date('Y-m-d') . $line_break);
354 + $content .= sprintf('%s: %s', 'Date', wp_date('Y-m-d') . $line_break);
315 355 break;
316 356
317 - case 'time' :
318 - $content .= sprintf( '%s: %s', 'Time', date('H:i:s') . $line_break);
357 + case 'time':
358 + $content .= sprintf('%s: %s', 'Time', wp_date('H:i:s') . $line_break);
319 359 break;
320 360
321 361 case 'remote_ip':
322 - $content .= sprintf( '%s: %s', 'IP', $_SERVER['REMOTE_ADDR'] . $line_break);
362 + $content .= isset($_SERVER['REMOTE_ADDR'])
363 + ? sprintf('%s: %s', 'IP', sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) . $line_break)
364 + : '';
323 365 break;
324 366
325 367 case 'user_agent':
326 - $content .= sprintf( '%s: %s', 'User Agent', $_SERVER['HTTP_USER_AGENT'] . $line_break);
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 + : '';
327 371 break;
328 372
329 373 case 'page_url':
330 - $content .= sprintf( '%s: %s', 'Page URL', $_SERVER['HTTP_REFERER'] . $line_break);
374 + $content .= isset($_SERVER['HTTP_REFERER'])
375 + ? sprintf('%s: %s', 'Page URL', sanitize_text_field(wp_unslash($_SERVER['HTTP_REFERER'])) . $line_break)
376 + : '';
331 377 break;
332 378 }
333 379 }
334 380
@@ -337,88 +383,97 @@
337 383
338 384 /**
339 385 * Extra submissions
340 386 */
341 - protected function redirect() {
387 + protected function redirect()
388 + {
342 389
343 - $validation = $this->validate_url( $this->settings['redirect_to'] );
390 + $validation = $this->validate_url($this->settings['redirect_to']);
344 391
345 392 // Above function exception blocks this execution
346 393 return [
347 394 'status' => 'success',
348 - 'url' => esc_url( $validation['url'] ),
395 + 'url' => esc_url($validation['url']),
349 396 'delay' => 1500,
350 - 'message' => esc_html( $this->get_response_message('redirect') )
397 + 'message' => esc_html($this->get_response_message('redirect'))
351 398 ];
352 399 }
353 - protected function mailchimp() {
400 + protected function popup()
401 + {
402 + $action = $this->settings['popup_action'];
354 403
355 - // Get API data
356 - $key = get_option('uicore_elements_mailchimp_secret_key');
357 - $list_id = $this->settings['mailchimp_audience_id'];
358 - $server = explode('-', $key)[1]; // Server value can be found on API Key after the dash
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 + }
359 412
360 - $this->validate_mailchimp($key, $list_id);
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();
361 422
362 - // Check the widget type to determine the fields, before getting form data
363 - if( $this->form_data['widget_type'] === 'contact-form' ) {
423 + $response = wp_remote_post($request['url'], $request['args']);
364 424
365 - // Since contact form has custom IDs, we need to get the ID value from the settings
366 - $settings = $this->settings;
425 + if (is_wp_error($response)) {
426 + return [
427 + 'status' => 'error',
428 + 'message' => $response->get_error_message(),
429 + ];
430 + }
367 431
368 - $email = $this->form_data['form_fields'][$settings['mailchimp_email_id']];
369 - $merge_fields = [
370 - 'FNAME' => isset( $this->form_data['form_fields'][$settings['mailchimp_fname_id']] ) ? $this->form_data['form_fields'][$settings['mailchimp_fname_id']] : "",
371 - 'LNAME' => isset( $this->form_data['form_fields'][$settings['mailchimp_lname_id']] ) ? $this->form_data['form_fields'][$settings['mailchimp_lname_id']] : "",
372 - 'PHONE' => isset( $this->form_data['form_fields'][$settings['mailchimp_phone_id']] ) ? $this->form_data['form_fields'][$settings['mailchimp_phone_id']] : "",
373 - 'BIRTHDAY' => isset( $this->form_data['form_fields'][$settings['mailchimp_birthday_id']] ) ? $this->form_data['form_fields'][$settings['mailchimp_birthday_id']] : ""
374 - ];
432 + $status_code = (int) wp_remote_retrieve_response_code($response);
375 433
376 - // Else can only be newsletter widget
377 - } else {
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');
378 438
379 - // Since Newsletter has fixed field IDs, we get them directly
380 - $email = $this->form_data['form_fields']['email'];
381 - $merge_fields = [
382 - 'FNAME' => isset( $this->form_data['form_fields']['name'] ) ? $this->form_data['form_fields']['name'] : ""
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 + ),
383 447 ];
384 448 }
385 449
386 - // Build the request
387 - $url = 'https://' . esc_html($server) . '.api.mailchimp.com/3.0/lists/' . esc_html($list_id) . '/members/';
388 - $data = [
389 - "email_address" => $email,
390 - "status" => "subscribed",
391 - "merge_fields" => $merge_fields
450 + return [
451 + 'status' => 'success',
452 + 'message' => esc_html__('Webhook sent successfully.', 'uicore-elements'),
392 453 ];
454 + }
455 + protected function newsletter_service(string $service)
456 + {
457 + $settings = $this->settings;
458 + $data = $this->get_submission_data_to_service_fields();
393 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'];
394 464
395 - $request = curl_init();
396 - curl_setopt($request, CURLOPT_URL, $url);
397 - curl_setopt($request, CURLOPT_HTTPHEADER, [
398 - 'Content-Type: application/json',
399 - 'Authorization: Basic ' . base64_encode('anystring:' . $key)
400 - ]);
401 - curl_setopt($request, CURLOPT_POST, true);
402 - curl_setopt($request, CURLOPT_POSTFIELDS, json_encode($data));
403 - curl_setopt($request, CURLOPT_RETURNTRANSFER, true);
404 - $res = curl_exec($request);
465 + $instance = new Services($api_key, $fields, $list, $custom_fields);
466 + $response = $instance->handle($service);
405 467
406 - if(curl_errno($request)) {
407 - $res = curl_error($request);
408 - } else {
409 - $res = json_decode($res, true);
410 - }
411 -
412 - curl_close($request);
413 -
414 - return $res;
468 + return $response;
415 469 }
416 470
417 471 /**
418 472 * Validations
419 473 */
420 - protected function validate_recaptcha(string $token, string $version) {
474 + protected function validate_recaptcha(string $token, string $version)
475 + {
421 476
422 477 // Check if secret and site key are set
423 478 if (!get_option('uicore_elements_recaptcha_secret_key') || !get_option('uicore_elements_recaptcha_site_key')) {
424 479 return [
@@ -431,33 +486,38 @@
431 486 'secret' => get_option('uicore_elements_recaptcha_secret_key'),
432 487 'response' => sanitize_text_field($token)
433 488 ];
434 489
435 - $verify = curl_init();
436 - curl_setopt($verify, CURLOPT_URL, "https://www.google.com/recaptcha/api/siteverify");
437 - curl_setopt($verify, CURLOPT_POST, true);
438 - curl_setopt($verify, CURLOPT_POSTFIELDS, http_build_query($data));
439 - curl_setopt($verify, CURLOPT_SSL_VERIFYPEER, false);
440 - curl_setopt($verify, CURLOPT_RETURNTRANSFER, true);
441 - $res = curl_exec($verify);
490 + $response = wp_remote_post("https://www.google.com/recaptcha/api/siteverify", [
491 + 'body' => $data,
492 + 'timeout' => 15,
493 + ]);
442 494
443 - $captcha = json_decode($res);
495 + if (is_wp_error($response)) {
496 + return [
497 + 'success' => false,
498 + 'message' => $response->get_error_message()
499 + ];
500 + }
444 501
445 - if($version === 'V3') {
502 + $captcha = json_decode(wp_remote_retrieve_body($response));
503 +
504 + if ($version === 'V3') {
446 505 return ['success' => ($captcha->success && $captcha->score >= 0.5) ? true : false];
447 506 }
448 507
449 508 // V2 default
450 509 return ['success' => $captcha->success];
451 -
452 510 }
453 - protected function validate_spam() {
511 + protected function validate_spam()
512 + {
454 513 // `ui-e-h-p` is the key for the honeypot
455 - return ( isset($this->form_data['ui-e-h-p']) && !empty($this->form_data['ui-e-h-p']) ) ? false : true;
514 + return (isset($this->form_data['ui-e-h-p']) && !empty($this->form_data['ui-e-h-p'])) ? false : true;
456 515 }
457 - protected function validate_url(string $url) {
516 + protected function validate_url(string $url)
517 + {
458 518 if (empty($url)) {
459 - throw new Redirect_Exception( $this->get_response_message('redirect_no_url') );
519 + throw new Redirect_Exception(esc_html($this->get_response_message('redirect_no_url')));
460 520 }
461 521
462 522 return [
463 523 'status' => true,
@@ -463,26 +523,35 @@
463 523 'status' => true,
464 524 'url' => $url,
465 525 ];
466 526 }
467 - protected function validate_field(string $field, string $label) {
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 + {
468 543 if (empty($field)) {
469 - throw new Submit_Exception( $this->get_response_message('empty_field', $label) );
544 + throw new Submit_Exception(esc_html($this->get_response_message('empty_field', esc_html($label))));
470 545 }
471 546 return $field;
472 547 }
473 - protected function validate_mailchimp(string $key, string $list_id) {
474 - if (empty($key)) {
475 - throw new Mailchimp_Exception(esc_html__('Mailchimp API key is not set. Check Uicore Elements settings.', 'uicore-elements'));
476 - } else if (empty($list_id)) {
477 - throw new Mailchimp_Exception(esc_html__('Audience ID control is not set. Check your widget settings.', 'uicore-elements'));
478 - }
479 - }
480 548
481 549 /**
482 550 * Helpers
483 551 */
484 - protected function get_setting_fields() {
552 + protected function get_setting_fields()
553 + {
485 554 // Used to determine if the widget fields are repeaters with custom IDS or fixed fields, and if fixed fields
486 555 // compose them into an array similar to repeaters, to simplify shortcode replacement function
487 556
488 557 switch ($this->form_data['widget_type']) {
@@ -499,50 +568,240 @@
499 568 return $this->settings['form_fields'];
500 569 break;
501 570 }
502 571 }
503 - protected function all_submissions_succedded($responses) {
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 + {
504 643 foreach ($responses as $submission => $data) {
505 - // Redirect action failure shouldn't return `error` to main status because is not properly a submission action, so we skip it.
506 - if( $submission == 'redirect') {
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'])) {
507 646 continue;
508 647 }
509 - // Failed submissions returns `error` or bool `false` status
510 - if( $data['status'] === 'error' || $data['status'] === false ) {
648 + if (
649 + isset($data['status']) &&
650 + (
651 + $data['status'] === 'error' ||
652 + $data['status'] === false
653 + )
654 + ) {
511 655 return false;
512 656 }
513 657 }
514 658 return true;
515 659 }
660 + public static function get_form_submit_options()
661 + {
516 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 +
517 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 + /**
518 775 * Responses
519 776 */
520 777 // Also used by form widget(s), therefore public and static
521 - public static function get_default_messages(){
778 + public static function get_default_messages()
779 + {
522 780 return [
523 781 // main messages
524 - 'success' => esc_html__( 'Your submission was successful.', 'uicore-elements' ),
525 - 'error' => esc_html__( 'Your submission failed because of an error.', 'uicore-elements' ),
526 - 'mail_error' => esc_html__( 'Failed to send email.', 'uicore-elements' ),
527 - 'required' => esc_html__( 'Fill all required fields.', 'uicore-elements' ),
528 - 'redirect' => esc_html__( 'Redirecting...', 'uicore-elements' ),
529 - ];
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 + ];
530 788 }
531 - protected function get_response_message(string $status, string $dinamic_data = '') {
789 + protected function get_response_message(string $status, string $dinamic_data = '')
790 + {
532 791 // non-customizable messages (for settings debugging only)
533 792 $default_messages = [
534 - 'invalid_status' => esc_html__( 'Invalid status message.', 'uicore-elements' ),
535 - 'redirect_no_url' => esc_html__( 'Redirection failed. No URL set.', 'uicore-elements' ),
536 - 'empty_field' => esc_html__( 'The following field is empty.', 'uicore-elements') . $dinamic_data,
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,
537 796 ];
538 797
539 - if($this->settings['custom_messages'] === 'yes') {
798 + if ($this->settings['custom_messages'] === 'yes') {
540 799 $messages = [
541 - 'success' => $this->settings['success_message'],
542 - 'error' => $this->settings['error_message'],
543 - 'mail_error' => $this->settings['mail_error_message'],
544 - 'redirect' => $this->settings['redirect_message'],
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']),
545 804 ];
546 805 } else {
547 806 $messages = self::get_default_messages();
548 807 }
@@ -550,54 +809,45 @@
550 809 $messages = array_merge($default_messages, $messages);
551 810
552 811 return isset($messages[$status]) ? $messages[$status] : $messages['invalid_status'];
553 812 }
554 - protected function build_frontend_responses($responses) {
555 -
813 + protected function build_frontend_responses($responses)
814 + {
556 815 $data = [];
557 816
558 817 // Mail response
559 - if ( isset($responses['email']) && $responses['email']['status'] !== 'success' ) {
818 + if (isset($responses['email']) && $responses['email']['status'] !== 'success') {
560 819 $data['email'] = $responses['email'];
561 820 }
562 821
563 822 // Mail attachment response - is always an error
564 - if ( isset($responses['email']) && isset($responses['email']['attachment']) ) {
823 + if (isset($responses['email']) && isset($responses['email']['attachment'])) {
565 824 $data['attachment'] = $responses['attachment'];
566 825 }
567 826
568 - // Mailchimp response - Integer is also an error
569 - if ( isset($responses['mailchimp']) ) {
827 + // Submit Actions response - is always an error
828 + if (isset($responses['submit'])) {
829 + $data['submit'] = $responses['submit'];
830 + }
570 831
571 - // If Integer, is an error from mailchimp API so we pass their response
572 - if( is_int($responses['mailchimp']['status'])){
573 - $data['mailchimp'] = [
574 - 'status' => 'error',
575 - 'message' => sprintf( esc_html__('Mailchimp HTTP "%s" - "%s."', 'uicore-elements'), $responses['mailchimp']['status'], $responses['mailchimp']['detail'])
576 - ];
577 -
578 - // If error is from our validation
579 - } else if ( $responses['mailchimp']['status'] === 'error' ) {
580 - $data['mailchimp'] = [
581 - 'status' => $responses['mailchimp']['status'],
582 - 'message' => $responses['mailchimp']['message']
583 - ];
584 - }
832 + if (isset($responses['webhook']) && $responses['webhook']['status'] !== 'success') {
833 + $data['submit'] = $responses['webhook'];
585 834 }
586 835
587 - // Submit Actions response - is always an error
588 - if ( isset($responses['submit']) ) {
589 - $data['submit'] = $responses['submit'];
590 - }
836 + // Newsletter Services responses
837 + $data = $this->build_services_responses($responses);
591 838
592 839 // Main response
593 840 $status = $this->all_submissions_succedded($responses) ? 'success' : 'error';
594 841 $data['message'] = $this->get_response_message($status);
595 842
596 - // Redirect response (should work only if all previous submitions hasn't failed)
597 - if ( isset($responses['redirect']) && $status === 'success' ) {
843 + // Below responses should work only if all previous submitions hasn't failed
844 + if (isset($responses['redirect']) && $status === 'success') {
598 845 $data['redirect'] = $responses['redirect'];
599 846 }
847 + if (isset($responses['popup']) && $status === 'success') {
848 + $data['popup'] = $responses['popup'];
849 + }
600 850
601 851 // The only successfull response that should be sent is 'main' and 'redirect'
602 852 return [
603 853 'status' => $status,
@@ -604,5 +854,86 @@
604 854 'data' => $data
605 855 ];
606 856 }
607 857
608 -}
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 +}