PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.5.0
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.5.0
2.12.6 2.12.5 2.12.4 2.12.3 2.12.2 2.12.1 2.12.0 2.11.1 2.11.0 2.10.1 2.10.0 2.9.1 2.9.0 2.8.2 2.8.1 2.7.0 2.7.1 2.8.0 trunk 0.0.10 0.0.11 0.0.12 0.0.13 0.0.2 0.0.3 All 96 releases
sureforms / admin / analytics.php

analytics.php in SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz 2.5.0, at admin/analytics.php

607 lines 17.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Analytics class helps to connect BSFAnalytics.
4 *
5 * @package sureforms.
6 */
7
8 namespace SRFM\Admin;
9
10 use SRFM\Inc\Database\Tables\Entries;
11 use SRFM\Inc\Helper;
12 use SRFM\Inc\Traits\Get_Instance;
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit; // Exit if accessed directly.
16 }
17 /**
18 * Analytics class.
19 *
20 * @since 1.4.0
21 */
22 class Analytics {
23 use Get_Instance;
24
25 /**
26 * Class constructor.
27 *
28 * @return void
29 * @since 1.4.0
30 */
31 public function __construct() {
32 /*
33 * BSF Analytics.
34 */
35 if ( ! class_exists( 'BSF_Analytics_Loader' ) ) {
36 require_once SRFM_DIR . 'inc/lib/bsf-analytics/class-bsf-analytics-loader.php';
37 }
38
39 if ( ! class_exists( 'Astra_Notices' ) ) {
40 require_once SRFM_DIR . 'inc/lib/astra-notices/class-astra-notices.php';
41 }
42
43 add_filter(
44 'uds_survey_allowed_screens',
45 static function () {
46 return [ 'plugins' ];
47 }
48 );
49
50 $srfm_bsf_analytics = \BSF_Analytics_Loader::get_instance();
51
52 $srfm_bsf_analytics->set_entity(
53 [
54 'sureforms' => [
55 'product_name' => 'SureForms',
56 'path' => SRFM_DIR . 'inc/lib/bsf-analytics',
57 'author' => 'SureForms',
58 'time_to_display' => '+24 hours',
59 'deactivation_survey' => apply_filters(
60 'srfm_deactivation_survey_data',
61 [
62 [
63 'id' => 'deactivation-survey-sureforms',
64 'popup_logo' => SRFM_URL . 'admin/assets/sureforms-logo.png',
65 'plugin_slug' => 'sureforms',
66 'popup_title' => 'Quick Feedback',
67 'support_url' => 'https://sureforms.com/contact/',
68 'popup_description' => 'If you have a moment, please share why you are deactivating SureForms:',
69 'show_on_screens' => [ 'plugins' ],
70 'plugin_version' => SRFM_VER,
71 ],
72 ]
73 ),
74 'hide_optin_checkbox' => true,
75 ],
76 ]
77 );
78
79 add_filter( 'bsf_core_stats', [ $this, 'add_srfm_analytics_data' ] );
80 }
81
82 /**
83 * Callback function to add SureForms specific analytics data.
84 *
85 * @param array $stats_data existing stats_data.
86 * @since 1.4.0
87 * @return array
88 */
89 public function add_srfm_analytics_data( $stats_data ) {
90 $stats_data['plugin_data']['sureforms'] = [
91 'free_version' => SRFM_VER,
92 'site_language' => get_locale(),
93 'most_used_anti_spam' => $this->most_used_anti_spam(),
94 'user_status' => $this->user_status(),
95 'pointer_popup_clicked' => $this->pointer_popup_clicked(),
96 ];
97 $stats_data['plugin_data']['sureforms']['numeric_values'] = [
98 'total_forms' => wp_count_posts( SRFM_FORMS_POST_TYPE )->publish ?? 0,
99 'instant_forms_enabled' => $this->instant_forms_enabled(),
100 'forms_using_custom_css' => $this->forms_using_custom_css(),
101 'ai_generated_forms' => $this->ai_generated_forms(),
102 'ai_generated_payment_forms' => $this->ai_generated_forms( 'payments' ),
103 'payment_forms' => $this->get_payment_forms_count(),
104 'total_entries' => Entries::get_total_entries_by_status(),
105 'restricted_forms' => $this->get_restricted_forms(),
106 ];
107
108 $stats_data['plugin_data']['sureforms'] = array_merge_recursive( $stats_data['plugin_data']['sureforms'], $this->global_settings_data() );
109 // Add onboarding analytics data.
110 $stats_data['plugin_data']['sureforms'] = array_merge_recursive( $stats_data['plugin_data']['sureforms'], $this->onboarding_analytics_data() );
111
112 // Add KPI tracking data.
113 $kpi_data = $this->get_kpi_tracking_data();
114 if ( ! empty( $kpi_data ) ) {
115 $stats_data['plugin_data']['sureforms']['kpi_records'] = $kpi_data;
116 }
117
118 return $stats_data;
119 }
120
121 /**
122 * Return total number of forms using instant forms.
123 *
124 * @since 1.4.0
125 * @return int
126 */
127 public function instant_forms_enabled() {
128 $meta_query = [
129 [
130 'key' => '_srfm_instant_form_settings',
131 'value' => '"enable_instant_form";b:1;',
132 'compare' => 'LIKE',
133 ],
134 ];
135
136 return $this->custom_wp_query_total_posts( $meta_query );
137 }
138
139 /**
140 * Return total number of ai generated forms.
141 *
142 * @param string $form_type Form type to check.
143 *
144 * @since 1.4.0
145 * @return int
146 */
147 public function ai_generated_forms( $form_type = '' ) {
148 $form_type = empty( $form_type ) || ! is_string( $form_type ) ? '' : $form_type;
149 $meta_query = [
150 [
151 'key' => '_srfm_is_ai_generated',
152 'value' => '',
153 'compare' => '!=', // Checks if the value is NOT empty.
154 ],
155 ];
156
157 if ( 'payments' === $form_type ) {
158 $search = 'wp:srfm/payment';
159 return $this->custom_wp_query_total_posts_with_search( $meta_query, $search );
160 }
161
162 return $this->custom_wp_query_total_posts( $meta_query );
163 }
164
165 /**
166 * Return most used anti-spam type on this site.
167 *
168 * @since 1.4.4
169 * @return int
170 */
171 public function most_used_anti_spam() {
172 global $wpdb;
173
174 // Attempt to get from cache first.
175 $cache_key = 'most_used_anti_spam';
176 $cached_result = wp_cache_get( $cache_key, 'sureforms' );
177
178 if ( false !== $cached_result ) {
179 return $cached_result;
180 }
181
182 $meta_key = '_srfm_captcha_security_type';
183
184 // Query to get the most used captcha type.
185 // PHPCS: Ignore direct database query warning, as there is no built-in alternative.
186 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
187 $result = $wpdb->get_row(
188 $wpdb->prepare(
189 "
190 SELECT meta_value, COUNT(meta_value) as count
191 FROM {$wpdb->postmeta}
192 WHERE meta_key = %s
193 AND meta_value != ''
194 GROUP BY meta_value
195 ORDER BY count DESC
196 LIMIT 1
197 ",
198 $meta_key
199 ),
200 ARRAY_A
201 );
202
203 $output = '';
204 if ( $result && ! empty( $result['meta_value'] ) ) {
205 switch ( $result['meta_value'] ) {
206 case 'g-recaptcha':
207 $output = 'Google reCAPTCHA';
208 break;
209
210 case 'cf-turnstile':
211 $output = 'CloudFlare Turnstile';
212 break;
213
214 case 'hcaptcha':
215 $output = 'hCaptcha';
216 break;
217
218 default:
219 $output = '';
220 break;
221 }
222 }
223
224 // Store result in cache for 1 hour.
225 wp_cache_set( $cache_key, $output, 'sureforms', HOUR_IN_SECONDS );
226
227 return $output;
228 }
229
230 /**
231 * Returns total number of forms using custom css.
232 *
233 * @since 1.4.0
234 * @return int
235 */
236 public function forms_using_custom_css() {
237 $meta_query = [
238 [
239 'key' => '_srfm_form_custom_css',
240 'value' => '',
241 'compare' => '!=', // Checks if the value is NOT empty.
242 ],
243 ];
244
245 return $this->custom_wp_query_total_posts( $meta_query );
246 }
247
248 /**
249 * Return total number of restricted forms.
250 *
251 * @since 1.10.1
252 * @return int
253 */
254 public function get_restricted_forms() {
255 $meta_query = [
256 [
257 'key' => '_srfm_form_restriction',
258 'value' => '"status":true',
259 'compare' => 'LIKE',
260 ],
261 ];
262
263 return $this->custom_wp_query_total_posts( $meta_query );
264 }
265
266 /**
267 * Generates global setting data for analytics
268 *
269 * @since 1.4.0
270 * @return array
271 */
272 public function global_settings_data() {
273 $global_data = [];
274
275 $security_settings = get_option( 'srfm_security_settings_options', [] );
276 $global_data['boolean_values']['honeypot_enabled'] = isset( $security_settings['srfm_honeypot'] ) && true === $security_settings['srfm_honeypot'];
277
278 $email_summary_data = get_option( 'srfm_email_summary_settings_options', [] );
279 $global_data['boolean_values']['email_summary_enabled'] = isset( $email_summary_data['srfm_email_summary'] ) && true === $email_summary_data['srfm_email_summary'];
280
281 $global_data['boolean_values']['suretriggers_active'] = is_plugin_active( 'suretriggers/suretriggers.php' );
282
283 $bsf_internal_referrer = get_option( 'bsf_product_referers', [] );
284 if ( ! empty( $bsf_internal_referrer['sureforms'] ) ) {
285 $global_data['internal_referer'] = $bsf_internal_referrer['sureforms'];
286 } else {
287 $global_data['internal_referer'] = '';
288 }
289
290 $general_settings = get_option( 'srfm_general_settings_options', [] );
291 $global_data['boolean_values']['ip_logging_enabled'] = ! empty( $general_settings['srfm_ip_log'] );
292
293 $validation_messages = get_option( 'srfm_default_dynamic_block_option', [] );
294 $global_data['boolean_values']['custom_validation_message'] = ! empty( $validation_messages ) && is_array( $validation_messages );
295
296 // Payment analytics - check if any payment method is enabled.
297 $global_data['boolean_values']['stripe_enabled'] = $this->is_stripe_enabled();
298
299 return $global_data;
300 }
301
302 /**
303 * Generates onboarding analytics data
304 *
305 * @since 1.9.1
306 * @return array
307 */
308 public function onboarding_analytics_data() {
309 $onboarding_data = [];
310 $analytics_option = Helper::get_srfm_option( 'onboarding_analytics', [] );
311
312 if ( empty( $analytics_option ) ) {
313 return $onboarding_data;
314 }
315
316 // Process skipped steps - store as an array.
317 if ( ! empty( $analytics_option['skippedSteps'] ) && is_array( $analytics_option['skippedSteps'] ) ) {
318 // Map step keys to more descriptive names.
319 $step_mapping = [
320 'welcome' => 'Welcome',
321 'connect' => 'Connect',
322 'emailDelivery' => 'SureMail',
323 'premiumFeatures' => 'Features',
324 'done' => 'Done',
325 ];
326
327 // Transform the step keys to their descriptive names.
328 $mapped_steps = array_map(
329 static function( $step ) use ( $step_mapping ) {
330 return $step_mapping[ $step ] ?? $step;
331 },
332 $analytics_option['skippedSteps']
333 );
334
335 // Store as an array.
336 $onboarding_data['onboarding_skipped_steps'] = $mapped_steps;
337 }
338
339 // SureMail Installation Status.
340 if ( isset( $analytics_option['suremailInstalled'] ) ) {
341 $onboarding_data['boolean_values']['onboarding_suremail_installed'] = (bool) $analytics_option['suremailInstalled'];
342 }
343
344 // Account Connection Status.
345 if ( isset( $analytics_option['accountConnected'] ) ) {
346 $onboarding_data['boolean_values']['onboarding_account_connected'] = (bool) $analytics_option['accountConnected'];
347 }
348
349 // Onboarding Completion Status.
350 if ( isset( $analytics_option['completed'] ) ) {
351 $onboarding_data['boolean_values']['onboarding_completed'] = (bool) $analytics_option['completed'];
352 }
353
354 // Onboarding Early Exit Status.
355 if ( isset( $analytics_option['exitedEarly'] ) ) {
356 $onboarding_data['boolean_values']['onboarding_exited_early'] = (bool) $analytics_option['exitedEarly'];
357 }
358
359 // Onboarding Selected Premium Features.
360 if ( ! empty( $analytics_option['premiumFeatures'] ) && ! empty( $analytics_option['premiumFeatures']['selectedFeatures'] ) ) {
361 // Map feature IDs to more descriptive names - exclude free features.
362 $feature_mapping = [
363 // Starter features.
364 'multi_step_form' => 'Multi-step Forms',
365 'conditional_logic' => 'Conditional Fields',
366 'webhooks' => 'Webhooks',
367 'advanced_fields' => 'Advanced Fields',
368
369 // Pro features.
370 'conversational_forms' => 'Conversational Forms',
371 'digital_signatures' => 'Digital Signatures',
372
373 // Business features.
374 'calculations' => 'Calculators',
375 'user_registration' => 'User Registration and Login',
376 'custom_app' => 'Custom App',
377 'pdf_generation' => 'PDF Generation',
378 ];
379
380 // Filter out any free features that might have been included.
381 $premium_features = array_filter(
382 $analytics_option['premiumFeatures']['selectedFeatures'],
383 static function( $feature ) {
384 // Exclude free features (ai-form-generation and entries).
385 return 'ai-form-generation' !== $feature && 'entries' !== $feature;
386 }
387 );
388
389 // Transform the feature IDs to their descriptive names.
390 $mapped_features = array_map(
391 static function( $feature ) use ( $feature_mapping ) {
392 return $feature_mapping[ $feature ] ?? $feature;
393 },
394 $premium_features
395 );
396
397 // Store as an array.
398 $onboarding_data['onboarding_selected_premium_features'] = $mapped_features;
399 }
400
401 return $onboarding_data;
402 }
403
404 /**
405 * Returns user status.
406 *
407 * @since 1.8.0
408 * @return string
409 */
410 public function user_status() {
411 // First, check if user_active is already set in srfm_options.
412 if ( Helper::get_srfm_option( 'user_active', false ) ) {
413 return 'active';
414 }
415 // Get up to 10 published SureForms.
416 $forms = get_posts(
417 [
418 'post_type' => SRFM_FORMS_POST_TYPE,
419 'posts_per_page' => 10,
420 'post_status' => 'publish',
421 ]
422 );
423 if ( empty( $forms ) ) {
424 return 'inactive';
425 }
426 foreach ( $forms as $form ) {
427 if ( ! get_post_meta( $form->ID, '_astra_sites_imported_post', true ) ) {
428 // Mark user as active in srfm_options.
429 Helper::update_srfm_option( 'user_active', true );
430 return 'active';
431 }
432 }
433 return 'inactive';
434 }
435
436 /**
437 * Return pointer popup clicked status.
438 *
439 * @return string pointer click status.
440 * @since 1.8.0
441 */
442 public function pointer_popup_clicked() {
443 // Get both values from srfm_options.
444 $accepted = Helper::get_srfm_option( 'pointer_popup_accepted', false );
445 $dismissed = Helper::get_srfm_option( 'pointer_popup_dismissed', false );
446 // If neither action has occurred.
447 if ( ! $accepted && ! $dismissed ) {
448 return '';
449 }
450
451 // If both are set, return the most recent one.
452 if ( $accepted && $dismissed ) {
453 return $accepted > $dismissed ? 'accepted' : 'dismissed';
454 }
455
456 // If only one is set, return it.
457 return $accepted ? 'accepted' : 'dismissed';
458 }
459
460 /**
461 * Runs a custom WP_Query to fetch the total number of posts matching the given meta query and optional search string.
462 *
463 * This function is used to count SureForms posts based on specific meta query conditions.
464 * Optionally, a search string can be included to further filter results by keyword match.
465 *
466 * @since 2.0.0
467 *
468 * @param array $meta_query Meta query array for WP_Query.
469 * @param string $search Optional. Search string for WP_Query. Default empty.
470 * @return int The number of matching posts.
471 */
472 public function custom_wp_query_total_posts_with_search( $meta_query = [], $search = '' ) {
473 $args = [
474 'post_type' => SRFM_FORMS_POST_TYPE,
475 'post_status' => 'publish',
476 'posts_per_page' => -1,
477 ];
478
479 if ( ! empty( $meta_query ) && is_array( $meta_query ) ) {
480 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query required as we need to fetch count of nested data.
481 $args['meta_query'] = $meta_query;
482 }
483
484 // If search string is provided, add it to the query.
485 if ( ! empty( $search ) ) {
486 $args['s'] = sanitize_text_field( $search );
487 }
488
489 $query = new \WP_Query( $args );
490 $posts_count = $query->found_posts;
491
492 wp_reset_postdata();
493
494 return $posts_count;
495 }
496
497 /**
498 * Get the total number of forms that utilize payment blocks.
499 *
500 * This function searches for forms containing the payment block identifier
501 * ('wp:srfm/payment') to determine how many forms include payment capabilities.
502 *
503 * @since 2.0.0
504 * @return int The number of forms that contain payment blocks.
505 */
506 public function get_payment_forms_count() {
507 $search = 'wp:srfm/payment';
508 // Runs a custom WP_Query to find the count of forms with payment block.
509 return $this->custom_wp_query_total_posts_with_search( [], $search );
510 }
511
512 /**
513 * Check if any payment method is enabled.
514 *
515 * This function checks if any payment gateway is connected and enabled.
516 * Currently supports Stripe, but can be extended for other payment methods in the future.
517 *
518 * @since 2.0.0
519 * @return bool True if any payment method is enabled, false otherwise.
520 */
521 private function is_stripe_enabled() {
522 // Check if Stripe is connected.
523 return class_exists( 'SRFM\Inc\Payments\Stripe\Stripe_Helper' ) && \SRFM\Inc\Payments\Stripe\Stripe_Helper::is_stripe_connected();
524 }
525
526 /**
527 * Runs custom WP_Query to fetch data as per requirement
528 *
529 * @param array $meta_query meta query array for WP_Query.
530 * @since 1.4.0
531 * @return int
532 */
533 private function custom_wp_query_total_posts( $meta_query ) {
534
535 $args = [
536 'post_type' => SRFM_FORMS_POST_TYPE,
537 'post_status' => 'publish',
538 'posts_per_page' => -1,
539 'meta_query' => $meta_query, //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query required as we need to fetch count of nested data.
540 ];
541
542 $query = new \WP_Query( $args );
543 $posts_count = $query->found_posts;
544
545 wp_reset_postdata();
546
547 return $posts_count;
548 }
549
550 /**
551 * Get KPI tracking data for the last 2 days (excluding today).
552 *
553 * @since 2.4.0
554 * @return array KPI data organized by date
555 */
556 private function get_kpi_tracking_data() {
557 $kpi_data = [];
558 $today = current_time( 'Y-m-d' );
559
560 // Get data for yesterday and day before yesterday.
561 for ( $i = 1; $i <= 2; $i++ ) {
562 $date = gmdate( 'Y-m-d', strtotime( $today . ' -' . $i . ' days' ) );
563 $submissions = $this->get_daily_submissions_count( $date );
564
565 // Always include data, even if submissions is 0.
566 $kpi_data[ $date ] = [
567 'numeric_values' => [
568 'submissions' => $submissions,
569 ],
570 ];
571 }
572
573 return $kpi_data;
574 }
575
576 /**
577 * Get daily submissions count for a specific date.
578 *
579 * @param string $date Date in Y-m-d format.
580 * @since 2.4.0
581 * @return int Daily submissions count
582 */
583 private function get_daily_submissions_count( $date ) {
584 $start_date = $date . ' 00:00:00';
585 $end_date = $date . ' 23:59:59';
586
587 // Use existing Entries class methods instead of direct database queries.
588 $where_conditions = [
589 [
590 [
591 'key' => 'created_at',
592 'compare' => '>=',
593 'value' => $start_date,
594 ],
595 [
596 'key' => 'created_at',
597 'compare' => '<=',
598 'value' => $end_date,
599 ],
600 ],
601 ];
602
603 return Entries::get_instance()->get_total_count( $where_conditions );
604 }
605
606 }
607