PluginProbe ʕ •ᴥ•ʔ
Hustle – Email Marketing, Lead Generation, Optins, Popups / trunk
Hustle – Email Marketing, Lead Generation, Optins, Popups vtrunk
7.8.14 7.8.14.1 7.8.13 7.8.13.1 trunk 3.0 3.1 3.1.1 3.1.2 3.1.3 3.1.4 4.3.2 4.4.4 4.4.5 4.4.5.1 4.4.5.4 4.6 4.6.1.1 4.6.1.4 4.7.0.2 4.7.0.3 4.7.0.7 4.7.0.9 4.7.1.0 4.7.1.1 4.8.0.0 5.0.0 5.0.1 5.0.1.1 5.0.1.2 5.1 5.1.1 5.1.2 5.1.3 5.1.3.1 5.1.3.2 5.1.4 5.1.5 6.0 6.0.1 6.0.2 6.0.3 6.0.4.2 6.0.5 6.0.6.1 6.0.7 6.0.8.1 6.0.9 7.0.0.1 7.0.2 7.0.3 7.0.4 7.1.0 7.1.1 7.2.0 7.2.1 7.3.0 7.3.1 7.3.3 7.3.5 7.3.6 7.3.7 7.4.0 7.4.1 7.4.11 7.4.13 7.4.13.1 7.4.2 7.4.3 7.4.4 7.4.5 7.4.5.1 7.4.5.2 7.4.6 7.4.7 7.5.0 7.6.0 7.6.1 7.6.3 7.6.4 7.6.6 7.7.0 7.7.1 7.8.0 7.8.1 7.8.10 7.8.10.1 7.8.10.2 7.8.11 7.8.12 7.8.12.1 7.8.2 7.8.3 7.8.4 7.8.5 7.8.6 7.8.7 7.8.8 7.8.9 7.8.9.1 7.8.9.2 7.8.9.3
wordpress-popup / inc / front / hustle-module-front-ajax.php
wordpress-popup / inc / front Last commit date
non-sshare-styles 1 month ago class-hustle-decorator-non-sshare.php 3 months ago class-hustle-decorator-sshare.php 3 months ago class-hustle-decorator_abstract.php 3 years ago class-hustle-module-preview.php 1 year ago hustle-module-front-ajax.php 1 day ago hustle-module-front.php 1 day ago hustle-module-inline-style-queue.php 3 months ago hustle-module-renderer.php 1 month ago hustle-renderer-abstract.php 3 months ago hustle-renderer-sshare.php 1 day ago
hustle-module-front-ajax.php
1354 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Hustle_Module_Front_Ajax
4 *
5 * @package Hustle
6 */
7
8 /**
9 * Class Hustle_Module_Front_Ajax
10 */
11 class Hustle_Module_Front_Ajax {
12
13 /**
14 * Constructor
15 */
16 public function __construct() {
17
18 // When module is viewed.
19 add_action( 'wp_ajax_hustle_module_viewed', array( $this, 'module_viewed' ) );
20 add_action( 'wp_ajax_nopriv_hustle_module_viewed', array( $this, 'module_viewed' ) );
21
22 // When module form is submitted.
23 add_action( 'wp_ajax_hustle_module_form_submit', array( $this, 'submit_form' ) );
24 add_action( 'wp_ajax_nopriv_hustle_module_form_submit', array( $this, 'submit_form' ) );
25
26 // Update the number of shares of each network.
27 add_action( 'wp_ajax_hustle_update_network_shares', array( $this, 'get_networks_native_shares' ) );
28 add_action( 'wp_ajax_nopriv_hustle_update_network_shares', array( $this, 'get_networks_native_shares' ) );
29
30 // When a conversion happens.
31 add_action( 'wp_ajax_hustle_module_converted', array( $this, 'log_module_conversion' ) );
32 add_action( 'wp_ajax_nopriv_hustle_module_converted', array( $this, 'log_module_conversion' ) );
33
34 // Update the stored click counter.
35 add_action( 'wp_ajax_hustle_sshare_click_counted', array( $this, 'update_sshare_click_counter' ) );
36 add_action( 'wp_ajax_nopriv_hustle_sshare_click_counted', array( $this, 'update_sshare_click_counter' ) );
37
38 // Handles unsubscribe form submisisons.
39 add_action( 'wp_ajax_hustle_unsubscribe_form_submission', array( $this, 'unsubscribe_submit_form' ) );
40 add_action( 'wp_ajax_nopriv_hustle_unsubscribe_form_submission', array( $this, 'unsubscribe_submit_form' ) );
41
42 // Check the schedule (avoiding pages static cache).
43 add_action( 'wp_ajax_hustle_module_display_despite_static_cache', array( $this, 'module_display_despite_static_cache' ) );
44 add_action( 'wp_ajax_nopriv_hustle_module_display_despite_static_cache', array( $this, 'module_display_despite_static_cache' ) );
45 }
46
47 /**
48 * Replace value if it's a dinamic one based on submited fields
49 *
50 * @param string $value The current value.
51 * @param array $form_data The submitted form data.
52 * @return string final value
53 */
54 private function maybe_replace_to_field( $value, $form_data ) {
55 if ( ! empty( $value ) && '{' === $value[0] && '}' === substr( $value, -1 ) ) {
56 $field = trim( $value, '{}' );
57 $value = ! empty( $form_data[ $field ] ) ? $form_data[ $field ] : $value;
58 }
59
60 return $value;
61 }
62
63 /**
64 * Replace common placeholders and current field placeholders
65 *
66 * @param int $module_id Module ID.
67 * @param string $text Text.
68 * @param array $form_data Submitted data.
69 * @return string Replaced text
70 */
71 private function replace_placeholders( $module_id, $text, $form_data ) {
72 preg_match_all( '/\{[^}]*\}/', $text, $matches );
73
74 if ( ! empty( $matches[0] ) && is_array( $matches[0] ) ) {
75 $site_placeholders = array(
76 '{site_url}' => site_url(),
77 '{site_title}' => get_bloginfo( 'name' ),
78 );
79 $module = Hustle_Module_Model::new_instance( $module_id );
80 $local_list_settings = ! is_wp_error( $module ) ? $module->get_provider_settings( 'local_list' ) : '';
81 if ( ! empty( $local_list_settings['local_list_name'] ) ) {
82 $site_placeholders['{local_list}'] = $local_list_settings['local_list_name'];
83 }
84
85 foreach ( $matches[0] as $placeholder ) {
86 // find common placeholders.
87 if ( key_exists( $placeholder, $site_placeholders ) ) {
88 $value = $site_placeholders[ $placeholder ];
89 } else {
90 // find field placeholders.
91 $value = $this->maybe_replace_to_field( $placeholder, $form_data );
92 }
93
94 if ( $value !== $placeholder ) {
95 // replace if we found something.
96 $text = str_replace( $placeholder, $value, $text );
97 }
98 }
99 }
100
101 return $text;
102 }
103
104 /**
105 * Check the schedule
106 */
107 public function module_display_despite_static_cache() {
108 // Verify nonce for security.
109 $nonce = filter_input( INPUT_POST, '_nonce', FILTER_SANITIZE_SPECIAL_CHARS );
110 if ( ! $nonce || ! wp_verify_nonce( $nonce, 'hustle_display_check' ) ) {
111 wp_send_json_error( __( 'Invalid security token.', 'hustle' ) );
112 }
113
114 $module_id = filter_input( INPUT_POST, 'module_id', FILTER_VALIDATE_INT );
115 if ( ! $module_id ) {
116 wp_send_json_error( __( 'Invalid module ID!', 'hustle' ) );
117 }
118 $module = Hustle_Module_Collection::instance()->return_model_from_id( $module_id );
119 if (
120 is_wp_error( $module ) ||
121 (
122 $module instanceof Hustle_Module_Model &&
123 1 !== (int) $module->active
124 )
125 ) {
126 wp_send_json_error( __( 'Invalid module!', 'hustle' ) );
127 }
128 $is_scheduled = true;
129
130 // Check the schedule. Ssharing modules don't have schedules.
131 if ( Hustle_Module_Model::SOCIAL_SHARING_MODULE !== $module->module_type
132 && ! $module->get_settings()->is_currently_scheduled() ) {
133 $is_scheduled = false;
134 }
135
136 if ( ! $is_scheduled ) {
137 wp_send_json_success( array( 'display' => false ) );
138 }
139
140 $avoid_static_cache = Opt_In_Utils::is_static_cache_enabled();
141 $passed_conditions = true;
142 if ( $avoid_static_cache ) {
143 $sub_type = filter_input( INPUT_POST, 'subType', FILTER_SANITIZE_SPECIAL_CHARS );
144 $module->sub_type = $sub_type;
145 // Check visibility conditions.
146 $passed_conditions = $module->is_condition_allow();
147 }
148
149 wp_send_json_success( array( 'display' => $passed_conditions ) );
150 }
151
152 /**
153 * Send auto email if it sets
154 *
155 * @param Hustle_Module_Model $module Module.
156 */
157 public function send_automated_email( Hustle_Module_Model $module ) {
158 $module_id = $module->module_id;
159 $emails_settings = $module->get_emails()->to_array();
160
161 if ( empty( $emails_settings['automated_email'] ) || '1' !== $emails_settings['automated_email']
162 || empty( $emails_settings['email_time'] ) ) {
163 return;
164 }
165
166 $recipient = ! empty( $emails_settings['recipient'] ) ? $emails_settings['recipient'] : '';
167 $email_subject = ! empty( $emails_settings['email_subject'] ) ? $emails_settings['email_subject'] : '';
168 $email_body = ! empty( $emails_settings['email_body'] ) ? $emails_settings['email_body'] : '';
169
170 $data = filter_input( INPUT_POST, 'data', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );
171 parse_str( $data['form'], $form_data );
172
173 $to = apply_filters( 'hustle_automated_email_recipient', $recipient, $module_id, $data, $form_data );
174 $subject = apply_filters( 'hustle_automated_email_email_subject', $email_subject, $module_id, $data, $form_data );
175 $body = apply_filters( 'hustle_automated_email_email_body', $email_body, $module_id, $data, $form_data );
176
177 $to = $this->replace_placeholders( $module_id, $to, $form_data );
178 $body = $this->replace_placeholders( $module_id, $body, $form_data );
179
180 // Replace {hustle_unsubscribe_link} placeholder.
181 if ( false !== strpos( $body, '{hustle_unsubscribe_link}' ) ) {
182 if ( ! empty( $form_data['hustle_module_id'] ) ) {
183 $modules_id = array( $form_data['hustle_module_id'] );
184 $unsubscribe_url = Hustle_Mail::get_unsubscribe_link( $to, $modules_id );
185 } else {
186 $unsubscribe_url = '';
187 }
188 $body = str_replace( '{hustle_unsubscribe_link}', esc_url( $unsubscribe_url ), $body );
189 }
190
191 $subject = $this->replace_placeholders( $module_id, $subject, $form_data );
192
193 // Send the email right away if it's set as 'instant'.
194 if ( 'instant' === $emails_settings['email_time'] ) {
195 Hustle_Mail::send_email( $to, $subject, $body );
196 return;
197 }
198
199 // Adding the 4th parameter time() to prevent cron jobs from being ignored.
200 // According to wp_schedule_single_event docs:
201 // "Attempts to schedule an event after an event of the same name and $args will also be ignored".
202 $args = array( $to, $subject, $body, time() );
203 if ( 'delay' === $emails_settings['email_time'] ) {
204 $time = ! empty( $emails_settings['auto_email_time'] ) ? (int) $emails_settings['auto_email_time'] : '';
205 $unit = ! empty( $emails_settings['auto_email_unit'] ) ? $emails_settings['auto_email_unit'] : '';
206 // get delay rate.
207 switch ( $unit ) {
208 case 'days':
209 $rate = DAY_IN_SECONDS;
210 break;
211 case 'hours':
212 $rate = HOUR_IN_SECONDS;
213 break;
214 case 'minutes':
215 $rate = MINUTE_IN_SECONDS;
216 break;
217 default:
218 $rate = 1;
219 break;
220 }
221 $schedule_time = time() + $time * $rate;
222 } elseif ( 'schedule' === $emails_settings['email_time'] ) {
223 // time settings.
224 $time = ! empty( $emails_settings['time'] ) ? $emails_settings['time'] : '';
225 $day = ! empty( $emails_settings['day'] ) ? $emails_settings['day'] : '';
226 $time = $this->maybe_replace_to_field( $time, $form_data );
227 $day = $this->maybe_replace_to_field( $day, $form_data );
228
229 // delay settings.
230 $delay_time = ! empty( $emails_settings['schedule_auto_email_time'] ) ? (int) $emails_settings['schedule_auto_email_time'] : 0;
231 $delay_unit = ! empty( $emails_settings['schedule_auto_email_unit'] ) ? $emails_settings['schedule_auto_email_unit'] : '';
232
233 // get delay rate.
234 switch ( $delay_unit ) {
235 case 'days':
236 $delay_rate = DAY_IN_SECONDS;
237 break;
238 case 'hours':
239 $delay_rate = HOUR_IN_SECONDS;
240 break;
241 case 'minutes':
242 $delay_rate = MINUTE_IN_SECONDS;
243 break;
244 default:
245 $delay_rate = 1;
246 break;
247 }
248 // schedule time calculation.
249 $delay = $delay_time * $delay_rate;
250 // convert from local time to GMT.
251 $schedule_time = get_gmt_from_date( date( 'Y-m-d H:i:s', strtotime( $day . ' ' . $time ) ), 'U' );// phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
252 $schedule_time = $schedule_time + $delay;
253 if ( time() > $schedule_time ) {
254 return;
255 }
256 }
257 wp_schedule_single_event( $schedule_time, 'hustle_send_email', $args );
258 }
259
260 /**
261 * Submit form
262 */
263 public function submit_form() {
264 // Verify nonce for security.
265 check_ajax_referer( 'hustle_module_form_submit', 'nonce' );
266
267 Hustle_Provider_Autoload::initiate_providers();
268
269 if ( ! isset( $_POST['data']['module_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
270 return;
271 }
272 $module_id = sanitize_text_field( wp_unslash( $_POST['data']['module_id'] ) );// phpcs:ignore WordPress.Security.NonceVerification.Missing
273 $module = Hustle_Module_Model::new_instance( $module_id );
274
275 if ( is_wp_error( $module ) || ! $module instanceof Hustle_Module_Model || ! property_exists( $module, 'active' ) ) {
276 wp_send_json_error( __( 'Invalid module', 'hustle' ) );
277 }
278
279 if ( ! $module->active ) {
280 wp_send_json_error( __( 'Module is not active', 'hustle' ) );
281 }
282
283 // Action called before full form submit.
284 do_action( 'hustle_form_before_handle_submit', $module_id );
285
286 $response = $this->handle_form( $module_id );
287
288 // Filter submit form response.
289 $response = apply_filters( 'hustle_form_submit_response', $response, $module_id );
290
291 // Action called after full form submit.
292 do_action( 'hustle_form_after_handle_submit', $module_id, $response );
293
294 if ( is_array( $response ) && ! empty( $response ) ) {
295 if ( $response['success'] ) {
296 wp_send_json_success( $response );
297 }
298 }
299
300 wp_send_json_error( $response );
301 }
302
303 /**
304 * Handles the module's form submission process.
305 *
306 * @since 4.0
307 *
308 * @param int $module_id MOdule ID.
309 * @return array
310 */
311 private function handle_form( $module_id ) {
312
313 $data = filter_input( INPUT_POST, 'data', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY );
314 parse_str( $data['form'], $form_data );
315
316 // Default error message response.
317 $response = array(
318 'message' => __( 'Error saving form', 'hustle' ),
319 'errors' => array(),
320 'success' => false,
321 'behavior' => array(),
322 );
323 $module = Hustle_Module_Model::new_instance( $module_id );
324 if ( is_wp_error( $module ) ) {
325 return $response;
326 }
327
328 $fields = $module->get_form_fields();
329 $field_data_array = array();
330 $placeholder_array = array();
331 $submit_errors = array();
332 $required_fields = array();
333 $fields_to_validate = array();
334 $entry = new Hustle_Entry_Model();
335 $entry->entry_type = $module->module_type;
336 $entry->module_id = $module_id;
337
338 if ( ! is_null( $fields ) ) {
339 $form_data = Opt_In_Utils::validate_and_sanitize_fields( $form_data );
340 array_walk_recursive(
341 $form_data,
342 function ( &$value ) {
343 $value = strip_shortcodes( $value );
344 }
345 );
346
347 // Verify recaptcha first.
348 if ( isset( $fields['recaptcha'] ) ) {
349
350 $submit_errors = $this->validate_recaptcha( $form_data, $fields['recaptcha'] );
351
352 if ( ! empty( $submit_errors ) ) {
353 // Recaptcha failed. No need to check the other fields.
354 $fields = array();
355 }
356 }
357
358 // Verify Cloudflare Turnstile if present.
359 if ( isset( $fields['turnstile'] ) ) {
360
361 $submit_errors = $this->validate_turnstile( $form_data, $fields['turnstile'] );
362
363 if ( ! empty( $submit_errors ) ) {
364 // Turnstile failed. No need to check the other fields.
365 $fields = array();
366 }
367 }
368
369 foreach ( $fields as $field_name => $field_data ) {
370 $ignored_field_types = Hustle_Entry_Model::ignored_fields();
371 if ( in_array( $field_data['type'], $ignored_field_types, true ) ) {
372 continue;
373 }
374
375 if ( 'true' === $field_data['required'] ) {
376 $required_fields[] = $field_name;
377 }
378
379 if ( isset( $field_data['validate'] ) && 'true' === $field_data['validate'] ) {
380 $fields_to_validate[] = $field_name;
381 }
382
383 if ( 'hidden' === $field_data['type'] ) {
384 $form_data = self::update_hidden_value( $field_data, $form_data );
385 }
386
387 if ( isset( $form_data[ $field_name ] ) ) {
388 $value = sanitize_text_field( $form_data[ $field_name ] );
389 $field_data_array[] = array(
390 'name' => $field_name,
391 'value' => $value,
392 );
393 $placeholder = '{' . $field_name . '}';
394 $placeholder_array[ $placeholder ] = $value;
395 }
396 }
397 if ( empty( $submit_errors ) ) {
398 $submit_errors = $this->validate_fields( $form_data, $required_fields, $fields_to_validate, $fields );
399 $response['errors'] = $submit_errors;
400 }
401 }
402
403 if ( ! empty( $field_data_array ) && empty( $submit_errors ) ) {
404
405 // $_POST doesn't contain everything we need to pass. So do this merge instead.
406 $submitted_data = array_merge( $data, $form_data );
407
408 // Do a pre-flight validation with the providers before submitting any data.
409 // As of 4.0 we're only checking whether the user is already subscribed if enabled.
410 $integrations_settings = $module->get_integrations_settings()->to_array();
411
412 $allow_subscribed = '1' === $integrations_settings['allow_subscribed_users'];
413
414 // Unset technical data.
415 $formatted_submitted_data = Hustle_Provider_Utils::format_submitted_data_for_addon( $submitted_data );
416 // Do provider's validation.
417 $provider_error = $this->attach_addons_on_form_submit( $module_id, $formatted_submitted_data, $allow_subscribed );
418 if ( true !== $provider_error ) {
419 $response['errors'][] = $provider_error;
420 return $response;
421 }
422
423 $user_exists = Hustle_Entry_Model::is_email_subscribed_to_module_id( $module_id, $submitted_data['email'] );
424 if ( $user_exists ) {
425 $entry_id = Hustle_Entry_Model::get_email_subscribed_to_module_id( $module_id, $submitted_data['email'] );
426 $entry = new Hustle_Entry_Model( $entry_id );
427 $entry->entry_type = $module->module_type;
428 $entry->module_id = $module_id;
429 }
430
431 $active_integrations = array();
432
433 if ( isset( $integrations_settings['active_integrations'] ) && ! empty( $integrations_settings['active_integrations'] ) ) {
434 $active_integrations = explode( ',', $integrations_settings['active_integrations'] );
435 }
436
437 if ( in_array( 'local_list', $active_integrations, true ) ) {
438
439 if ( $user_exists || $entry->save() ) {
440
441 /**
442 * Check is tracking allowed
443 */
444 $settings = Hustle_Settings_Admin::get_privacy_settings();
445 $ip_tracking = ! isset( $settings['ip_tracking'] ) || 'on' === $settings['ip_tracking'];
446 if ( $ip_tracking ) {
447 $field_data_array[] = array(
448 'name' => 'hustle_ip',
449 'value' => Opt_In_Geo::get_user_ip(),
450 );
451 }
452
453 $active_integrations = $this->get_module_active_integrations_to_store( $module_id );
454 $field_data_array[] = array(
455 'name' => 'active_integrations',
456 'value' => $active_integrations,
457 );
458
459 // Filter data before saving to db.
460 $field_data_array = apply_filters( 'hustle_form_submit_field_data', $field_data_array, $module_id );
461
462 // Action before saving to db.
463 do_action( 'hustle_form_submit_before_set_fields', $entry, $module_id, $field_data_array );
464
465 $added_data_array = $this->attach_addons_add_entry_fields( $module_id, $module, $formatted_submitted_data, $field_data_array );
466 $added_data_array = array_merge( $field_data_array, $added_data_array );
467
468 $entry->set_fields( $added_data_array );
469
470 }
471 } else {
472
473 $active_integrations = $this->get_module_active_integrations_to_store( $module_id );
474 $field_data_array[] = array(
475 'name' => 'active_integrations',
476 'value' => $active_integrations,
477 );
478
479 // Filter data before saving to db.
480 $field_data_array = apply_filters( 'hustle_form_submit_field_data', $field_data_array, $module_id );
481
482 // Action before saving to db.
483 do_action( 'hustle_form_submit_before_set_fields', $entry, $module_id, $field_data_array );
484
485 $this->attach_addons_add_entry_fields( $module_id, $module, $formatted_submitted_data, $field_data_array );
486
487 }
488
489 $this->send_automated_email( $module );
490
491 $post_id = sanitize_text_field( $form_data['post_id'] );
492 $module_sub_type = isset( $form_data['hustle_sub_type'] ) ? $form_data['hustle_sub_type'] : null;
493 $this->maybe_log_conversion( $module, $post_id, $module_sub_type );
494
495 $emails_settings = $module->get_emails()->to_array();
496 $fields_array = wp_list_pluck( $field_data_array, 'value', 'name' );
497 $success_message = $this->parse_message_with_fields_placeholders( $emails_settings['success_message'], $placeholder_array );
498 $success_message = apply_filters( 'hustle_parsed_success_message', $success_message, $module_id, $module_sub_type, $form_data );
499
500 $redirect_url = $this->parse_message_with_fields_placeholders( $emails_settings['redirect_url'], $placeholder_array );
501
502 /**
503 * Filters the URL to redirect to on success.
504 *
505 * @since 4.4.1
506 *
507 * @param string $redirect_url The URL to redirect to. Will be passed trough esc_url_raw().
508 * @param string $module_id ID of the current module.
509 * @param string $module_sub_type Subtype of the current module.
510 * @param array $form_data The submitted data.
511 */
512 $redirect_url = apply_filters( 'hustle_success_redirect_url', $redirect_url, $module_id, $module_sub_type, $form_data );
513
514 /**
515 * Filters redirect tab on success
516 *
517 * @param string $redirect_tab Redirect tab ( sametab | newtab_thankyou | newtab_hide )
518 * @param string $module_id ID of the current module.
519 * @param string $module_sub_type Subtype of the current module.
520 * @param array $form_data The submitted data.
521 */
522 $redirect_tab = apply_filters( 'hustle_success_redirect_tab', $emails_settings['redirect_tab'], $module_id, $module_sub_type, $form_data );
523
524 $response = array(
525 'message' => do_shortcode( wp_kses_post( $success_message ) ),
526 'success' => true,
527 'errors' => array(),
528 'behavior' => array(
529 'after_submit' => $emails_settings['after_successful_submission'],
530 'url' => esc_url_raw( $redirect_url ), // Using raw here to honor url params.
531 'redirect_tab' => $redirect_tab,
532 ),
533 );
534
535 if ( ! empty( $emails_settings['automated_file'] ) && ! empty( $emails_settings['auto_download_file'] ) ) {
536 $explode = explode( DIRECTORY_SEPARATOR, $emails_settings['auto_download_file'] );
537 $file_name = end( $explode );
538
539 $response['behavior']['file'] = $emails_settings['auto_download_file'];
540 $response['behavior']['file_name'] = $file_name;
541 }
542
543 if (
544 ! empty( $emails_settings['notification_email'] ) &&
545 ! empty( $emails_settings['notification_email_recipient'] )
546 ) {
547 $notification_recipient = $this->replace_placeholders(
548 $module_id,
549 sanitize_email( $emails_settings['notification_email_recipient'] ),
550 $form_data
551 );
552 $notification_subject = $this->replace_placeholders(
553 $module_id,
554 sanitize_text_field( $emails_settings['notification_email_subject'] ),
555 $form_data
556 );
557 $notification_body = $this->replace_placeholders(
558 $module_id,
559 wp_kses_post( $emails_settings['notification_email_body'] ),
560 $form_data
561 );
562
563 // Send the notification email right away.
564 Hustle_Mail::send_email( $notification_recipient, $notification_subject, $notification_body );
565 }
566 }
567
568 if ( ! empty( $submit_errors ) ) {
569 $response = array(
570 'message' => $this->get_invalid_form_message( $fields ),
571 'success' => false,
572 'errors' => $submit_errors,
573 'behavior' => array(),
574 );
575 }
576
577 return $response;
578 }
579
580 /**
581 * Update hidden value because it can be changed by user
582 *
583 * @param array $field_data Field settings.
584 * @param array $form_data Form data.
585 * @return array
586 */
587 private static function update_hidden_value( $field_data, $form_data ) {
588 if ( ! empty( $field_data['name'] ) && ! empty( $field_data['default_value'] )
589 // skip some types because it returns wrong values on this state for them.
590 && ! in_array( $field_data['default_value'], array( 'query_parameter', 'embed_url', 'refer_url' ), true ) ) {
591 $form_data[ $field_data['name'] ] = Hustle_Module_Renderer::get_hidden_value( $field_data );
592 }
593
594 return $form_data;
595 }
596
597 /**
598 * Do recaptcha backend validation.
599 *
600 * @since 4.0
601 * @param array $form_data Form data.
602 * @param array $recaptcha_field Recaptcha field.
603 * @return array
604 * @throws Exception When reCAPTCHA validation failed.
605 */
606 private function validate_recaptcha( $form_data, $recaptcha_field ) {
607
608 $submit_errors = array();
609 $recaptcha_settings = Hustle_Settings_Admin::get_recaptcha_settings();
610 $recaptcha_version = empty( $recaptcha_field['version'] ) ? 'v2_checkbox' : $recaptcha_field['version'];
611 $recaptcha_secret = ! empty( $recaptcha_settings[ $recaptcha_version . '_secret_key' ] ) ? $recaptcha_settings[ $recaptcha_version . '_secret_key' ] : '';
612 $recaptcha_site = ! empty( $recaptcha_settings[ $recaptcha_version . '_site_key' ] ) ? $recaptcha_settings[ $recaptcha_version . '_site_key' ] : '';
613 $token = ! empty( $form_data['recaptcha-response'] ) ? $form_data['recaptcha-response'] : false;
614
615 if ( ! empty( $recaptcha_secret ) && ! empty( $recaptcha_site ) ) {
616
617 try {
618 $validation_message = ! empty( $recaptcha_field['validation_message'] )
619 ? esc_html( $recaptcha_field['validation_message'] ) : '';
620
621 if ( ! $token ) {
622 throw new Exception(
623 ! empty( $validation_message ) ? $validation_message
624 : esc_html__( 'reCAPTCHA must be verified to submit the form.', 'hustle' )
625 );
626 }
627
628 $remote_ip = filter_input( INPUT_SERVER, 'HTTP_X_FORWARDED_FOR', FILTER_SANITIZE_SPECIAL_CHARS );
629 if ( ! $remote_ip ) {
630 $remote_ip = filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_SANITIZE_SPECIAL_CHARS );
631 }
632 $response = wp_remote_get(
633 add_query_arg(
634 array(
635 'secret' => $recaptcha_secret,
636 'response' => $token,
637 'remoteip' => $remote_ip,
638 ),
639 'https://www.google.com/recaptcha/api/siteverify'
640 )
641 );
642
643 if ( is_wp_error( $response ) ) {
644 throw new Exception( $response->get_error_message() );
645 }
646
647 $response_body = ! empty( $response['body'] ) ? json_decode( $response['body'] ) : '';
648
649 if ( empty( $response_body ) || ! $response_body->success ) {
650 throw new Exception(
651 ! empty( $validation_message ) ? $validation_message
652 : esc_html__( 'reCAPTCHA validation failed. Please try again.', 'hustle' )
653 );
654 }
655
656 if ( 'v3_recaptcha' === $recaptcha_version ) {
657 $selected_score = $recaptcha_field['threshold'];
658
659 if ( $selected_score > $response_body->score ) {
660 throw new Exception(
661 ! empty( $validation_message ) ? $validation_message
662 : esc_html__( 'reCAPTCHA validation failed. The score is too low.', 'hustle' )
663 );
664 }
665 }
666 } catch ( Exception $e ) {
667 $submit_errors['recaptcha'] = $e->getMessage();
668 }
669 }
670
671 return $submit_errors;
672 }
673
674 /**
675 * Do Cloudflare Turnstile backend validation.
676 *
677 * @since 7.8.13
678 * @param array $form_data Form data.
679 * @param array $turnstile_field Turnstile field settings.
680 * @return array
681 *
682 * @throws Exception When Turnstile validation failed.
683 */
684 private function validate_turnstile( $form_data, $turnstile_field ) {
685
686 $submit_errors = array();
687 $turnstile_settings = Hustle_Settings_Admin::get_turnstile_settings();
688 $secret = ! empty( $turnstile_settings['turnstile_client_secret'] ) ? $turnstile_settings['turnstile_client_secret'] : '';
689 $site_key = ! empty( $turnstile_settings['turnstile_api_key'] ) ? $turnstile_settings['turnstile_api_key'] : '';
690 $token = ! empty( $form_data['cf-turnstile-response'] ) ? $form_data['cf-turnstile-response'] : false;
691
692 if ( ! empty( $secret ) && ! empty( $site_key ) ) {
693
694 try {
695 $validation_message = ! empty( $turnstile_field['validation_message'] )
696 ? esc_html( $turnstile_field['validation_message'] ) : '';
697
698 if ( ! $token ) {
699 throw new Exception(
700 ! empty( $validation_message ) ? $validation_message
701 : esc_html__( 'Please complete the Cloudflare Turnstile challenge.', 'hustle' )
702 );
703 }
704
705 $remote_ip = filter_input( INPUT_SERVER, 'HTTP_X_FORWARDED_FOR', FILTER_SANITIZE_SPECIAL_CHARS );
706 if ( ! $remote_ip ) {
707 $remote_ip = filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_SANITIZE_SPECIAL_CHARS );
708 }
709
710 $response = wp_remote_post(
711 'https://challenges.cloudflare.com/turnstile/v0/siteverify',
712 array(
713 'body' => array(
714 'secret' => $secret,
715 'response' => $token,
716 'remoteip' => $remote_ip,
717 ),
718 )
719 );
720
721 if ( is_wp_error( $response ) ) {
722 throw new Exception( $response->get_error_message() );
723 }
724
725 $response_body = ! empty( $response['body'] ) ? json_decode( $response['body'] ) : '';
726
727 if ( empty( $response_body ) || ! $response_body->success ) {
728 throw new Exception(
729 ! empty( $validation_message ) ? $validation_message
730 : esc_html__( 'Turnstile validation failed. Please try again.', 'hustle' )
731 );
732 }
733 } catch ( Exception $e ) {
734 $submit_errors['turnstile'] = $e->getMessage();
735 }
736 }
737
738 return $submit_errors;
739 }
740
741 /**
742 * Validate fields.
743 *
744 * @since 4.0
745 * @since 4.0.2 $fields_to_validate parameter added.
746 *
747 * @param array $form_data Form data.
748 * @param array $required_fields Rewuired Fields.
749 * @param array $fields_to_validate Fields to validate.
750 * @param array $fields Fields.
751 * @return array
752 */
753 private function validate_fields( $form_data, $required_fields, $fields_to_validate, $fields ) {
754
755 $submit_errors = array();
756
757 // Check required fields.
758 foreach ( $required_fields as $slug ) {
759
760 if ( ! isset( $form_data[ $slug ] ) || empty( trim( sanitize_text_field( $form_data[ $slug ] ) ) ) ) {
761 $submit_errors[ $slug ] = $fields[ $slug ]['required_error_message'];
762 }
763 }
764
765 // Validate url and email fields.
766 if ( empty( $submit_errors ) ) {
767
768 foreach ( $fields_to_validate as $slug ) {
769
770 // Don't validate empty fields. These are validated under "required" if needed.
771 if ( empty( trim( $form_data[ $slug ] ) ) ) {
772 continue;
773 }
774
775 if (
776 (
777 'email' === $fields[ $slug ]['type'] &&
778 ! is_email( $form_data[ $slug ] )
779 ) ||
780 (
781 'url' === $fields[ $slug ]['type'] &&
782 false === filter_var( $form_data[ $slug ], FILTER_VALIDATE_URL )
783 ) ||
784 (
785 'datepicker' === $fields[ $slug ]['type'] &&
786 false === $this->validate_date( $form_data[ $slug ], $fields[ $slug ]['date_format'] )
787 )
788 ) {
789
790 $submit_errors[ $slug ] = $fields[ $slug ]['validation_message'];
791
792 } elseif ( 'timepicker' === $fields[ $slug ]['type'] ) {
793
794 $time = str_replace( array( ' AM', ' PM' ), '', $form_data[ $slug ] );
795 $format = '12' === $fields[ $slug ]['time_format'] ? 'h:i' : 'H:i';
796 $created_time = DateTime::createFromFormat( $format, $time );
797 if ( ! $created_time || $created_time->format( $format ) !== $time ) {
798 $submit_errors[ $slug ] = $fields[ $slug ]['validation_message'];
799 }
800 }
801 }
802 }
803
804 return apply_filters( 'hustle_module_submit_errors_validated_fields', $submit_errors, $form_data, $required_fields, $fields_to_validate, $fields );
805 }
806
807 /**
808 * Get the generic error message.
809 *
810 * @since 4.0
811 *
812 * @param array $fields Fields.
813 * @return string
814 */
815 private function get_invalid_form_message( $fields ) {
816
817 if ( isset( $fields['submit'] ) && isset( $fields['submit']['error_message'] ) && ! empty( $fields['submit']['error_message'] ) ) {
818 $message = $fields['submit']['error_message'];
819 } else {
820 $message = '';
821 }
822
823 return esc_html( $message );
824 }
825
826 /**
827 * Replace placeholders
828 *
829 * @param string $raw_message Message.
830 * @param array $submitted_data Submitted data.
831 * @return string
832 */
833 private function parse_message_with_fields_placeholders( $raw_message, $submitted_data ) {
834
835 $message = str_replace(
836 array_keys( $submitted_data ),
837 array_values( $submitted_data ),
838 stripslashes( $raw_message )
839 );
840
841 return $message;
842 }
843
844 /**
845 * Log a conversion if tracking is enabled.
846 *
847 * @since 4.0
848 *
849 * @param int $module Module ID.
850 * @param int $page_id Page ID.
851 * @param string|null $module_sub_type Sub type.
852 * @param string|null $cta CTA.
853 */
854 private function maybe_log_conversion( $module, $page_id, $module_sub_type = null, $cta = null ) {
855
856 $type = is_null( $module_sub_type ) ? $module->module_type : $module_sub_type;
857
858 if ( ! $module->is_tracking_enabled( $type ) ) {
859 return false;
860 }
861
862 if ( 'social_sharing' === $module->module_type ) {
863 $action = 'conversion';
864 } elseif ( $cta ) {
865 $action = $cta . '_conversion';
866 } else {
867 $action = 'optin_conversion';
868 }
869 $this->temp_log_conversion( $module, $action, $page_id, $module_sub_type );
870
871 return true;
872 }
873
874
875 /**
876 * Temporary log for conversions.
877 *
878 * @param Hustle_Module_Model $module Module.
879 * @param string $action Action.
880 * @param int $post_id Post ID.
881 * @param string|null $module_sub_type Module subtype.
882 */
883 private function temp_log_conversion( $module, $action, $post_id, $module_sub_type ) {
884 $settings = Hustle_Settings_Admin::get_privacy_settings();
885 $ip_tracking = ! isset( $settings['ip_tracking'] ) || 'on' === $settings['ip_tracking'];
886
887 if ( $ip_tracking ) {
888 $user_ip = Opt_In_Geo::get_user_ip();
889 } else {
890 $user_ip = '';
891 }
892
893 $logs = get_option( 'hustle_conversion_logs', array() );
894 $logs[] = array(
895 'time' => time(),
896 'module_id' => $module->id,
897 'module_type' => $module->module_type,
898 'post_id' => $post_id,
899 'action' => $action,
900 'ip' => $user_ip,
901 'module_sub_type' => $module_sub_type,
902 );
903
904 update_option( 'hustle_conversion_logs', $logs );
905 }
906
907 /**
908 * Get an array with the connected integrations to show in entries.
909 *
910 * @since 4.0
911 *
912 * @param int $module_id Module ID.
913 * @return array
914 */
915 private function get_module_active_integrations_to_store( $module_id ) {
916
917 $active_integrations = array();
918 $connected_addons = Hustle_Provider_Utils::get_addons_instance_connected_with_module( $module_id );
919
920 foreach ( $connected_addons as $addon ) {
921 // Local list is not really an integration to be shown here.
922 if ( 'local_list' === $addon->get_slug() ) {
923 continue;
924 }
925 $active_integrations[ $addon->get_slug() ] = $addon->get_title();
926 }
927
928 return $active_integrations;
929 }
930
931 /**
932 * Executor on form submit for attached addons.
933 *
934 * @see Hustle_Provider_Form_Hooks_Abstract::on_form_submit()
935 * @since 4.0
936 *
937 * @param int $module_id Module ID.
938 * @param array $submitted_data Data submitted by the user.
939 * @param bool $allow_subscribed Allow already subscribed.
940 * @return bool true on success|string error message from addon otherwise
941 */
942 private function attach_addons_on_form_submit( $module_id, $submitted_data, $allow_subscribed ) {
943
944 // Find is_form_connected.
945 $connected_addons = Hustle_Provider_Utils::get_addons_instance_connected_with_module( $module_id );
946
947 $submitted_data = Opt_In_Utils::validate_and_sanitize_fields( $submitted_data );
948
949 foreach ( $connected_addons as $connected_addon ) {
950 try {
951 $slug = $connected_addon->get_slug();
952 $formatted_submitted_data = apply_filters( 'hustle_format_submitted_data', $submitted_data, $slug );
953 $form_hooks = $connected_addon->get_addon_form_hooks( $module_id );
954 if ( $form_hooks instanceof Hustle_Provider_Form_Hooks_Abstract ) {
955 $addon_return = $form_hooks->on_form_submit( $formatted_submitted_data, $allow_subscribed );
956 if ( true !== $addon_return ) {
957 return $form_hooks->get_submit_form_error_message();
958 }
959 }
960 } catch ( Exception $e ) {
961 Opt_In_Utils::maybe_log( $connected_addon->get_slug(), 'failed to attach_addons_on_form_submit', $e->getMessage() );
962 }
963 }
964
965 return true;
966 }
967
968 /**
969 * Executor to add more entry fields for attached addons.
970 *
971 * @see Hustle_Provider_Form_Hooks_Abstract::add_entry_fields()
972 *
973 * @since 4.0
974 *
975 * @param int $module_id Module ID.
976 * @param Hustle_Module_Model $module Module.
977 * @param array $submitted_data Data submitted by the user.
978 * @param array $current_entry_fields Entry fields.
979 * @return array fields to be added to entry
980 */
981 private function attach_addons_add_entry_fields( $module_id, Hustle_Module_Model $module, $submitted_data, $current_entry_fields ) {
982 $additional_fields_data = array();
983 $connected_addons = Hustle_Provider_Utils::get_addons_instance_connected_with_module( $module_id );
984 foreach ( $connected_addons as $connected_addon ) {
985 try {
986 $slug = $connected_addon->get_slug();
987 $formatted_submitted_data = apply_filters( 'hustle_format_submitted_data', $submitted_data, $slug );
988 $form_hooks = $connected_addon->get_addon_form_hooks( $module_id );
989
990 if ( $form_hooks instanceof Hustle_Provider_Form_Hooks_Abstract ) {
991
992 $addon_fields = $form_hooks->add_entry_fields( $formatted_submitted_data );
993 // log errors.
994 if (
995 ! empty( $addon_fields[0] ) && ! empty( $addon_fields[0]['value'] ) &&
996 isset( $addon_fields[0]['value']['is_sent'] ) &&
997 false === $addon_fields[0]['value']['is_sent']
998 ) {
999 $error = ! empty( $addon_fields[0]['value']['description'] ) ?
1000 $addon_fields[0]['value']['description']
1001 : __( 'Something went wrong.', 'hustle' );
1002
1003 $error = $connected_addon->get_title() . ' ' . $error;
1004
1005 Opt_In_Utils::maybe_log( $error );
1006 }
1007
1008 $account_settings = $connected_addon->get_settings_values();
1009 if ( ! empty( $connected_addon->selected_global_multi_id ) ) {
1010 $addon_fields[0]['value']['account_name'] = isset( $account_settings[ $connected_addon->selected_global_multi_id ]['name'] )
1011 ? $account_settings[ $connected_addon->selected_global_multi_id ]['name'] . ' (' . $connected_addon->selected_global_multi_id . ')'
1012 : $connected_addon->selected_global_multi_id;
1013 }
1014
1015 // Reformat additional fields.
1016 $addon_fields = self::format_addon_additional_fields( $connected_addon, $addon_fields );
1017 $additional_fields_data = array_merge( $additional_fields_data, $addon_fields );
1018 }
1019 } catch ( Exception $e ) {
1020 Opt_In_Utils::maybe_log( $connected_addon->get_slug(), 'failed to add_entry_fields', $e->getMessage() );
1021 }
1022 }
1023
1024 return $additional_fields_data;
1025 }
1026
1027 /**
1028 * Format additional fields from provider.
1029 * Format used is `hustle_provider_{$slug}_{$field_name}`
1030 *
1031 * @since 4.0
1032 *
1033 * @param Hustle_Provider_Abstract $addon Addon.
1034 * @param array $additional_fields Additional fields.
1035 * @return array
1036 */
1037 private static function format_addon_additional_fields( Hustle_Provider_Abstract $addon, $additional_fields ) {
1038 // to `name` and `value` basis.
1039 $formatted_additional_fields = array();
1040 if ( ! is_array( $additional_fields ) ) {
1041 return array();
1042 }
1043
1044 foreach ( $additional_fields as $additional_field ) {
1045 if ( ! isset( $additional_field['name'] ) || ! isset( $additional_field['value'] ) ) {
1046 continue;
1047 }
1048 $formatted_additional_fields[] = array(
1049 'name' => 'hustle_provider_' . $addon->get_slug() . '_' . $additional_field['name'],
1050 'value' => $additional_field['value'],
1051 );
1052 }
1053
1054 return $formatted_additional_fields;
1055 }
1056
1057 /**
1058 * Handles the unsubscribe form submission.
1059 *
1060 * @since 3.0.5
1061 */
1062 public function unsubscribe_submit_form() {
1063
1064 parse_str( $_POST['data'], $submitted_data ); // phpcs:ignore
1065 $sanitized_data = Opt_In_Utils::validate_and_sanitize_fields( $submitted_data );
1066 $messages = Hustle_Settings_Admin::get_unsubscribe_messages();
1067 $email = isset( $sanitized_data['email'] ) ? filter_var( $sanitized_data['email'], FILTER_VALIDATE_EMAIL ) : '';
1068 // Check if we got the email address and if it's valid.
1069 if ( $email ) {
1070 $modules_id = self::get_module_ids( $email, $sanitized_data, $messages );
1071
1072 // Handle 'choose_list' form step.
1073 if ( isset( $sanitized_data['form_step'] ) && 'choose_list' === $sanitized_data['form_step'] ) {
1074
1075 if ( ! empty( $sanitized_data['skip_confirmation'] ) ) {
1076 $sanitized_data['lists_id'] = $modules_id;
1077 }
1078
1079 // If the lists are defined, submit the email with the nonce.
1080 if ( ! empty( $sanitized_data['lists_id'] ) && isset( $sanitized_data['current_url'] ) ) {
1081
1082 // Do the process to send the unsubscription email.
1083 $email_processed = Hustle_Mail::handle_unsubscription_user_email( $email, $sanitized_data['lists_id'], $sanitized_data['current_url'] );
1084
1085 if ( $email_processed ) {
1086
1087 $html = $messages['email_submitted'];
1088 $wrapper = '.hustle-form-body';
1089
1090 $response = array(
1091 'html' => apply_filters( 'hustle_unsubscribe_email_processed_html', $html, $sanitized_data ),
1092 'wrapper' => apply_filters( 'hustle_unsubscribe_email_processed_wrapper', $wrapper, $sanitized_data ),
1093 );
1094 wp_send_json_success( $response );
1095
1096 }
1097 }
1098
1099 $html = apply_filters( 'hustle_unsubscribe_email_not_processed_html', $messages['email_not_processed'], $sanitized_data );
1100 wp_send_json_error( array( 'html' => $html ) );
1101
1102 } elseif ( isset( $sanitized_data['form_step'] ) && 'enter_email' === $sanitized_data['form_step'] ) {
1103 $modules_id = self::get_module_ids( $email, $sanitized_data, $messages );
1104
1105 $module = Hustle_Module_Model::new_instance();
1106 $params = array(
1107 'ajax_step' => true,
1108 'modules_id' => $modules_id,
1109 'module' => $module,
1110 'email' => $email,
1111 'current_url' => $sanitized_data['current_url'],
1112 'messages' => $messages,
1113 );
1114 $renderer = new Hustle_Layout_Helper();
1115 $html = $renderer->render( 'general/unsubscribe-form', $params, true );
1116 $wrapper = '.hustle-form-body';
1117
1118 $response = array(
1119 'html' => apply_filters( 'hustle_render_unsubscribe_lists_html', $html, $modules_id, $email ),
1120 'wrapper' => apply_filters( 'hustle_render_unsubscribe_list_wrapper', $wrapper, $modules_id, $email ),
1121 );
1122 wp_send_json_success( $response );
1123 }
1124 } else {
1125 // Return an error if the email is missing or is invalid.
1126 $html = apply_filters( 'hustle_unsubscribe_invalid_email_address_message', $messages['invalid_email'], $sanitized_data );
1127 wp_send_json_error( array( 'html' => $html ) );
1128 }
1129
1130 wp_send_json_success( $sanitized_data );
1131 }
1132
1133 /**
1134 * Get module ids
1135 *
1136 * @param atring $email Email.
1137 * @param array $sanitized_data Data.
1138 * @param array $messages Message texts.
1139 * @return array
1140 */
1141 private static function get_module_ids( $email, $sanitized_data, $messages ) {
1142 $entry = new Hustle_Entry_Model();
1143 $modules_id = $entry->get_modules_id_by_email_in_local_list( $email );
1144 // The lists are not defined yet. Show the list for the user to select them.
1145 // If not showing all, show only the ones defined in the shortcode.
1146 if ( ! empty( $sanitized_data['form_module_id'] ) && '-1' !== $sanitized_data['form_module_id'] ) {
1147 $form_modules_id = array_map( 'trim', explode( ',', $sanitized_data['form_module_id'] ) );
1148 $modules_id = array_intersect( $form_modules_id, $modules_id );
1149 }
1150
1151 // If the email is not in any of the selected lists.
1152 if ( empty( $modules_id ) ) {
1153
1154 $html = $messages['email_not_found'];
1155 $wrapper = '.hustle-form-body';
1156
1157 $response = array(
1158 'html' => apply_filters( 'hustle_unsubscribe_email_not_found_html', $html, $modules_id, $email ),
1159 'wrapper' => apply_filters( 'hustle_unsubscribe_email_not_found_wrapper', $wrapper, $modules_id, $email ),
1160 );
1161 wp_send_json_success( $response );
1162 }
1163
1164 return $modules_id;
1165 }
1166
1167 /**
1168 * Retrieve the number of shares from the network's native APIs.
1169 *
1170 * @since 3.0.3
1171 * @since 4.0 Get the networks' names from frontend.
1172 */
1173 public function get_networks_native_shares() {
1174
1175 // TODO: check the networks are the ones that have APIs, and sanitize.
1176 $networks = filter_input( INPUT_POST, 'networks', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_REQUIRE_ARRAY );
1177
1178 $post_id = filter_input( INPUT_POST, 'postId', FILTER_VALIDATE_INT );
1179
1180 if ( ! $networks || false === $post_id || is_null( $post_id ) ) {
1181 wp_send_json_error();
1182 }
1183
1184 $module_instance = Hustle_SShare_Model::new_instance();
1185 $networks_shares = $module_instance->retrieve_networks_shares( $networks, $post_id );
1186
1187 wp_send_json_success(
1188 array(
1189 'networks' => $networks_shares,
1190 'shorten' => array(
1191 'thousand' => esc_html__( 'K', 'hustle' ),
1192 'million' => esc_html__( 'M', 'hustle' ),
1193 ),
1194 )
1195 );
1196 }
1197
1198 /**
1199 * Log module conversion
1200 *
1201 * @return null
1202 */
1203 public function log_module_conversion() {
1204 $data = file_get_contents( 'php://input' );
1205 $data = json_decode( $data );
1206 $data = $data ? get_object_vars( $data ) : array();
1207
1208 if ( ! is_array( $data ) || empty( $data ) ) {
1209 return;
1210 }
1211
1212 // Verify nonce for security.
1213 if ( empty( $data['_nonce'] ) || ! wp_verify_nonce( $data['_nonce'], 'hustle_log_conversion' ) ) {
1214 wp_send_json_error( __( 'Invalid security token.', 'hustle' ) );
1215 }
1216
1217 $module_id = $data['module_id'];
1218
1219 if ( empty( $module_id ) ) {
1220 wp_send_json_error( __( 'Invalid Request!', 'hustle' ) . $module_id );
1221 }
1222
1223 $module = Hustle_Module_Collection::instance()->return_model_from_id( $module_id );
1224 if ( is_wp_error( $module ) ) {
1225 wp_send_json_error( __( 'Invalid module!', 'hustle' ) );
1226 }
1227
1228 if ( $module->id ) {
1229
1230 if ( ! $module->active ) {
1231 wp_send_json_error( __( 'Module is not active.', 'hustle' ) );
1232 }
1233
1234 $page_id = $data['page_id'];
1235 $module_sub_type = ( isset( $data['module_sub_type'] ) && ! empty( $data['module_sub_type'] ) ) ? $data['module_sub_type'] : null;
1236
1237 $cta = ! empty( $data['cta'] ) ? $data['cta'] : null;
1238 $res = $this->maybe_log_conversion( $module, $page_id, $module_sub_type, $cta );
1239
1240 } else {
1241 $res = false;
1242 }
1243
1244 if ( ! $res ) {
1245 wp_send_json_error( __( 'Error saving stats', 'hustle' ) );
1246 } else {
1247 wp_send_json_success( __( 'Stats Successfully saved', 'hustle' ) );
1248 }
1249 }
1250
1251 /**
1252 * Module view
1253 *
1254 * @return null
1255 */
1256 public function module_viewed() {
1257 $data = json_decode( file_get_contents( 'php://input' ) );
1258 $data = $data ? get_object_vars( $data ) : array();
1259
1260 if ( ! is_array( $data ) || empty( $data ) ) {
1261 return;
1262 }
1263
1264 $module_id = $data['module_id'];
1265
1266 if ( empty( $module_id ) ) {
1267 wp_send_json_error( __( 'Invalid Request: Module id invalid', 'hustle' ) );
1268 }
1269
1270 $module = Hustle_Module_Model::new_instance( $module_id );
1271 if ( is_wp_error( $module ) ) {
1272 wp_send_json_error( __( 'Invalid module!', 'hustle' ) );
1273 }
1274
1275 if ( $module->id ) {
1276 $page_id = $data['page_id'];
1277 $module_sub_type = isset( $data['module_sub_type'] ) ? $data['module_sub_type'] : null;
1278
1279 $this->temp_log_conversion( $module, 'view', $page_id, $module_sub_type );
1280 wp_send_json_success( __( 'Stats Successfully saved', 'hustle' ) );
1281 }
1282
1283 wp_send_json_error( __( 'Error saving stats', 'hustle' ) );
1284 }
1285
1286 /**
1287 * Update the click counter after an icon is clicked.
1288 *
1289 * @since 4.0
1290 */
1291 public function update_sshare_click_counter() {
1292
1293 $module_id = filter_input( INPUT_POST, 'moduleId', FILTER_VALIDATE_INT );
1294 $module = Hustle_Module_Collection::instance()->return_model_from_id( $module_id );
1295
1296 if ( ! is_wp_error( $module ) ) {
1297
1298 $network = filter_input( INPUT_POST, 'network', FILTER_SANITIZE_SPECIAL_CHARS );
1299
1300 $content = $module->get_content()->to_array();
1301
1302 if ( isset( $content['social_icons'][ $network ] ) ) {
1303
1304 $content['social_icons'][ $network ]['counter'] = intval( $content['social_icons'][ $network ]['counter'] ) + 1;
1305 $module->update_meta( Hustle_Module_Model::KEY_CONTENT, $content );
1306
1307 wp_send_json_success();
1308 }
1309 }
1310
1311 wp_send_json_error();
1312 }
1313
1314 /**
1315 * Validate date field.
1316 *
1317 * @param string $date date.
1318 * @param string $format date format.
1319 * @since 4.0.2
1320 */
1321 private function validate_date( $date, $format ) {
1322 $format = $this->validate_format( $format );
1323 $d = DateTime::createFromFormat( $format, $date );
1324
1325 return ( $d && $d->format( $format ) === $date );
1326 }
1327
1328 /**
1329 * Changes the date format from JS one to PHP version.
1330 *
1331 * @param string $format date format.
1332 * @since 4.0.2
1333 */
1334 private function validate_format( $format ) {
1335 // Formats based on https://api.jqueryui.com/datepicker/#utility-formatDate.
1336 $format_translate = array(
1337 'dd' => 'd',
1338 'mm' => 'm',
1339 'yy' => 'Y',
1340 'MM' => 'F',
1341 'oo' => 'z', // PHP doesn't have format like this, using the version without leading zeros.
1342 'DD' => 'l',
1343 'd' => 'j',
1344 'o' => 'z',
1345 'm' => 'n',
1346 '@' => 'U',
1347 );
1348
1349 $format = strtr( $format, $format_translate );
1350
1351 return $format;
1352 }
1353 }
1354