PluginProbe ʕ •ᴥ•ʔ
LatePoint – Calendar Booking Plugin for Appointments and Events / 5.2.11
LatePoint – Calendar Booking Plugin for Appointments and Events v5.2.11
5.6.6 5.6.5 5.6.4 5.6.3 5.6.2 5.6.1 5.6.0 5.5.2 5.5.1 5.5.0 5.4.2 trunk 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.1.8 5.1.9 5.1.91 5.1.92 5.1.93 5.1.94 5.2.0 5.2.1 5.2.10 5.2.11 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 5.2.9 5.3.0 5.3.1 5.3.2 5.4.0 5.4.1
latepoint / lib / kit / bsf-analytics / class-bsf-analytics.php
latepoint / lib / kit / bsf-analytics Last commit date
assets 4 months ago classes 4 months ago modules 4 months ago changelog.txt 4 months ago class-bsf-analytics-loader.php 4 months ago class-bsf-analytics-stats.php 4 months ago class-bsf-analytics.php 4 months ago version.json 4 months ago
class-bsf-analytics.php
652 lines
1 <?php
2 /**
3 * BSF analytics class file.
4 *
5 * @version 1.0.0
6 *
7 * @package bsf-analytics
8 */
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit; // Exit if accessed directly.
12 }
13
14 if ( ! class_exists( 'BSF_Analytics' ) ) {
15
16 /**
17 * BSF analytics
18 */
19 class BSF_Analytics {
20
21 /**
22 * Member Variable
23 *
24 * @var array Entities data.
25 */
26 private $entities;
27
28 /**
29 * Member Variable
30 *
31 * @var string Usage tracking document URL
32 */
33 public $usage_doc_link = 'https://store.brainstormforce.com/usage-tracking/?utm_source=wp_dashboard&utm_medium=general_settings&utm_campaign=usage_tracking';
34
35 /**
36 * Setup actions, load files.
37 *
38 * @param array $args entity data for analytics.
39 * @param string $analytics_path directory path to analytics library.
40 * @param float $analytics_version analytics library version.
41 * @since 1.0.0
42 */
43 public function __construct( $args, $analytics_path, $analytics_version ) {
44
45 // Bail when no analytics entities are registered.
46 if ( empty( $args ) ) {
47 return;
48 }
49
50 $this->entities = $args;
51
52 // Run migration from old "analytics" option names to new "usage" names.
53 $this->maybe_migrate_options();
54
55 define( 'BSF_ANALYTICS_VERSION', $analytics_version );
56 define( 'BSF_ANALYTICS_URI', $this->get_analytics_url( $analytics_path ) );
57
58 add_action( 'admin_init', array( $this, 'handle_optin_optout' ) );
59 add_action( 'admin_init', array( $this, 'option_notice' ) );
60 add_action( 'init', array( $this, 'maybe_track_analytics' ), 99 );
61
62 $this->set_actions();
63
64 add_action( 'admin_init', array( $this, 'register_usage_tracking_setting' ) );
65
66 $this->includes();
67
68 $this->load_deactivation_survey_actions();
69 }
70
71 /**
72 * Function to load the deactivation survey form actions.
73 *
74 * @since 1.1.6
75 * @return void
76 */
77 public function load_deactivation_survey_actions() {
78
79 // If not in a admin area then return it.
80 if ( ! is_admin() ) {
81 return;
82 }
83
84 add_filter( 'uds_survey_vars', array( $this, 'add_slugs_to_uds_vars' ) );
85 add_action( 'admin_footer', array( $this, 'load_deactivation_survey_form' ) );
86 }
87
88 /**
89 * Setup actions for admin notice style and analytics cron event.
90 *
91 * @since 1.0.4
92 */
93 public function set_actions() {
94
95 foreach ( $this->entities as $key => $data ) {
96 add_action( 'astra_notice_before_markup_' . $key . '-optin-notice', array( $this, 'enqueue_assets' ) );
97 add_action( 'update_option_' . $key . '_usage_optin', array( $this, 'update_analytics_option_callback' ), 10, 3 );
98 add_action( 'add_option_' . $key . '_usage_optin', array( $this, 'add_analytics_option_callback' ), 10, 2 );
99 }
100 }
101
102 /**
103 * BSF Analytics URL
104 *
105 * @param string $analytics_path directory path to analytics library.
106 * @return String URL of bsf-analytics directory.
107 * @since 1.0.0
108 */
109 public function get_analytics_url( $analytics_path ) {
110
111 $content_dir_path = wp_normalize_path( WP_CONTENT_DIR );
112
113 $analytics_path = wp_normalize_path( $analytics_path );
114
115 return str_replace( $content_dir_path, content_url(), $analytics_path );
116 }
117
118 /**
119 * Enqueue Scripts.
120 *
121 * @since 1.0.0
122 * @return void
123 */
124 public function enqueue_assets() {
125
126 /**
127 * Load unminified if SCRIPT_DEBUG is true.
128 *
129 * Directory and Extensions.
130 */
131 $dir_name = ( SCRIPT_DEBUG ) ? 'unminified' : 'minified';
132 $file_rtl = ( is_rtl() ) ? '-rtl' : '';
133 $css_ext = ( SCRIPT_DEBUG ) ? '.css' : '.min.css';
134
135 $css_uri = BSF_ANALYTICS_URI . '/assets/css/' . $dir_name . '/style' . $file_rtl . $css_ext;
136
137 wp_enqueue_style( 'bsf-analytics-admin-style', $css_uri, false, BSF_ANALYTICS_VERSION, 'all' );
138 }
139
140 /**
141 * Send analytics API call.
142 *
143 * @since 1.0.0
144 */
145 public function send() {
146
147 $api_url = BSF_Analytics_Helper::get_api_url();
148
149 wp_remote_post(
150 $api_url . 'api/analytics/',
151 array(
152 'body' => BSF_Analytics_Stats::instance()->get_stats(),
153 'timeout' => 5,
154 'blocking' => false,
155 )
156 );
157 }
158
159 /**
160 * Check if usage tracking is enabled.
161 *
162 * @return bool
163 * @since 1.0.0
164 */
165 public function is_tracking_enabled() {
166
167 foreach ( $this->entities as $key => $data ) {
168
169 $is_enabled = get_site_option( $key . '_usage_optin', false ) === 'yes' ? true : false;
170 $is_enabled = $this->is_white_label_enabled( $key ) ? false : $is_enabled;
171
172 if ( apply_filters( $key . '_tracking_enabled', $is_enabled ) ) {
173 return true;
174 }
175 }
176
177 return false;
178 }
179
180 /**
181 * Check if WHITE label is enabled for BSF products.
182 *
183 * @param string $source source of analytics.
184 * @return bool
185 * @since 1.0.0
186 */
187 public function is_white_label_enabled( $source ) {
188
189 $options = apply_filters( $source . '_white_label_options', array() );
190 $is_enabled = false;
191
192 if ( is_array( $options ) ) {
193 foreach ( $options as $option ) {
194 if ( true === $option ) {
195 $is_enabled = true;
196 break;
197 }
198 }
199 }
200
201 return $is_enabled;
202 }
203
204 /**
205 * Display admin notice for usage tracking.
206 *
207 * @since 1.0.0
208 */
209 public function option_notice() {
210
211 if ( ! current_user_can( 'manage_options' ) ) {
212 return;
213 }
214
215 if( $this->is_tracking_enabled() ) {
216 return; // Don't need to display notice if any of our plugin already have the permission.
217 }
218
219 // If the user has opted out of tracking, don't show the notice till 7 days.
220 $last_displayed_time = get_site_option( 'bsf_usage_last_displayed_time', false );
221 if ( $last_displayed_time && $last_displayed_time > time() - ( 7 * DAY_IN_SECONDS ) ) {
222 return; // Don't display the notice if it was displayed recently.
223 }
224
225 foreach ( $this->entities as $key => $data ) {
226
227 $time_to_display = isset( $data['time_to_display'] ) ? $data['time_to_display'] : '+24 hours';
228 $usage_doc_link = isset( $data['usage_doc_link'] ) ? $data['usage_doc_link'] : $this->usage_doc_link;
229
230 // Don't display the notice if tracking is disabled or White Label is enabled for any of our plugins.
231 if ( false !== get_site_option( $key . '_usage_optin', false ) || $this->is_white_label_enabled( $key ) ) {
232 continue;
233 }
234
235 // Show tracker consent notice after 24 hours from installed time.
236 if ( strtotime( $time_to_display, $this->get_analytics_install_time( $key ) ) > time() ) {
237 continue;
238 }
239
240 /* translators: %s product name */
241 $notice_string = sprintf(
242 __(
243 'Help us improve %1$s and our other products!<br><br>With your permission, we\'d like to collect <strong>non-sensitive information</strong> from your website — like your PHP version and which features you use — so we can fix bugs faster, make smarter decisions, and build features that actually matter to you. <em>No personal info. Ever.</em>'
244 ),
245 '<strong>' . esc_html( $data['product_name'] ) . '</strong>'
246 );
247
248 if ( is_multisite() ) {
249 $notice_string .= __( 'This will be applicable for all sites from the network.' );
250 }
251
252 $language_dir = is_rtl() ? 'rtl' : 'ltr';
253
254 Astra_Notices::add_notice(
255 array(
256 'id' => $key . '-optin-notice',
257 'type' => '',
258 'message' => sprintf(
259 '<div class="notice-content">
260 <div class="notice-heading">
261 %1$s
262 </div>
263 <div class="astra-notices-container">
264 <a href="%2$s" class="astra-notices button-primary">
265 %3$s
266 </a>
267 <a href="%4$s" data-repeat-notice-after="%5$s" class="astra-notices button-secondary">
268 %6$s
269 </a>
270 </div>
271 </div>',
272 /* translators: %s usage doc link */
273 sprintf( $notice_string . '<span dir="%1s"><a href="%2s" target="_blank" rel="noreferrer noopener">%3s</a><span><br><br>', $language_dir, esc_url( $usage_doc_link ), __( ' Know More.' ) ),
274 esc_url(
275 add_query_arg(
276 array(
277 $key . '_analytics_optin' => 'yes',
278 $key . '_analytics_nonce' => wp_create_nonce( $key . '_analytics_optin' ),
279 'bsf_analytics_source' => $key,
280 )
281 )
282 ),
283 __( 'Yes! Allow it' ),
284 esc_url(
285 add_query_arg(
286 array(
287 $key . '_analytics_optin' => 'no',
288 $key . '_analytics_nonce' => wp_create_nonce( $key . '_analytics_optin' ),
289 'bsf_analytics_source' => $key,
290 )
291 )
292 ),
293 MONTH_IN_SECONDS,
294 __( 'No Thanks' )
295 ),
296 'show_if' => true,
297 'repeat-notice-after' => false,
298 'priority' => 18,
299 'display-with-other-notices' => true,
300 )
301 );
302
303 return;
304 }
305 }
306
307 /**
308 * Process usage tracking opt out.
309 *
310 * @since 1.0.0
311 */
312 public function handle_optin_optout() {
313
314 if ( ! current_user_can( 'manage_options' ) ) {
315 return;
316 }
317
318 $source = isset( $_GET['bsf_analytics_source'] ) ? sanitize_text_field( wp_unslash( $_GET['bsf_analytics_source'] ) ) : '';
319
320 if ( ! isset( $_GET[ $source . '_analytics_nonce' ] ) ) {
321 return;
322 }
323
324 if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET[ $source . '_analytics_nonce' ] ) ), $source . '_analytics_optin' ) ) {
325 return;
326 }
327
328 $optin_status = isset( $_GET[ $source . '_analytics_optin' ] ) ? sanitize_text_field( wp_unslash( $_GET[ $source . '_analytics_optin' ] ) ) : '';
329
330 if ( 'yes' === $optin_status ) {
331 $this->optin( $source );
332 } elseif ( 'no' === $optin_status ) {
333 $this->optout( $source );
334 }
335
336 wp_safe_redirect(
337 esc_url_raw(
338 remove_query_arg(
339 array(
340 $source . '_analytics_optin',
341 $source . '_analytics_nonce',
342 'bsf_analytics_source',
343 )
344 )
345 )
346 );
347 }
348
349 /**
350 * Opt in to usage tracking.
351 *
352 * @param string $source source of analytics.
353 * @since 1.0.0
354 */
355 private function optin( $source ) {
356 update_site_option( $source . '_usage_optin', 'yes' );
357 }
358
359 /**
360 * Opt out of usage tracking.
361 *
362 * @param string $source source of analytics.
363 * @since 1.0.0
364 */
365 private function optout( $source ) {
366 update_site_option( $source . '_usage_optin', 'no' );
367 update_site_option( 'bsf_usage_last_displayed_time', time() );
368 }
369
370 /**
371 * Load analytics stat class.
372 *
373 * @since 1.0.0
374 */
375 private function includes() {
376 require_once __DIR__ . '/classes/class-bsf-analytics-helper.php';
377 require_once __DIR__ . '/class-bsf-analytics-stats.php';
378
379 // Loads all the modules.
380 require_once __DIR__ . '/modules/deactivation-survey/classes/class-deactivation-survey-feedback.php';
381 require_once __DIR__ . '/modules/utm-analytics.php';
382 }
383
384 /**
385 * Migrate old "analytics" options to new "usage" naming.
386 * Copies values to new options and deletes old options.
387 *
388 * @since 1.1.17
389 */
390 private function maybe_migrate_options() {
391 if ( get_site_option( 'bsf_usage_migrated' ) ) {
392 return;
393 }
394
395 // Migrate global options.
396 $old_last_displayed = get_site_option( 'bsf_analytics_last_displayed_time' );
397 if ( false !== $old_last_displayed ) {
398 update_site_option( 'bsf_usage_last_displayed_time', $old_last_displayed );
399 delete_site_option( 'bsf_analytics_last_displayed_time' );
400 }
401
402 // Migrate per-product options.
403 foreach ( $this->entities as $key => $data ) {
404 $old_optin = get_site_option( $key . '_analytics_optin' );
405 if ( false !== $old_optin ) {
406 update_site_option( $key . '_usage_optin', $old_optin );
407 delete_site_option( $key . '_analytics_optin' );
408 }
409
410 $old_install_time = get_site_option( $key . '_analytics_installed_time' );
411 if ( false !== $old_install_time ) {
412 update_site_option( $key . '_usage_installed_time', $old_install_time );
413 delete_site_option( $key . '_analytics_installed_time' );
414 }
415 }
416
417 // Migrate transient.
418 $old_track = get_site_transient( 'bsf_analytics_track' );
419 if ( false !== $old_track ) {
420 set_site_transient( 'bsf_usage_track', $old_track, 2 * DAY_IN_SECONDS );
421 delete_site_transient( 'bsf_analytics_track' );
422 }
423
424 update_site_option( 'bsf_usage_migrated', true );
425 }
426
427 /**
428 * Register usage tracking option in General settings page.
429 *
430 * @since 1.0.0
431 */
432 public function register_usage_tracking_setting() {
433
434 foreach ( $this->entities as $key => $data ) {
435
436 if ( ! apply_filters( $key . '_tracking_enabled', true ) || $this->is_white_label_enabled( $key ) ) {
437 return;
438 }
439
440 /**
441 * Introducing a new key 'hide_optin_checkbox, which allows individual plugin to hide optin checkbox
442 * If they are providing providing in-plugin option to manage this option.
443 * from General > Settings page.
444 *
445 * @since 1.1.14
446 */
447 if( ! empty( $data['hide_optin_checkbox'] ) && true === $data['hide_optin_checkbox'] ) {
448 continue;
449 }
450
451 $usage_doc_link = isset( $data['usage_doc_link'] ) ? $data['usage_doc_link'] : $this->usage_doc_link;
452 $author = isset( $data['author'] ) ? $data['author'] : 'Brainstorm Force';
453
454 register_setting(
455 'general', // Options group.
456 $key . '_usage_optin', // Option name/database.
457 array( 'sanitize_callback' => array( $this, 'sanitize_option' ) ) // sanitize callback function.
458 );
459
460 add_settings_field(
461 $key . '-usage-optin', // Field ID.
462 __( 'Usage Tracking' ), // Field title.
463 array( $this, 'render_settings_field_html' ), // Field callback function.
464 'general',
465 'default', // Settings page slug.
466 array(
467 'type' => 'checkbox',
468 'title' => $author,
469 'name' => $key . '_usage_optin',
470 'label_for' => $key . '-usage-optin',
471 'id' => $key . '-usage-optin',
472 'usage_doc_link' => $usage_doc_link,
473 )
474 );
475 }
476 }
477
478 /**
479 * Sanitize Callback Function
480 *
481 * @param bool $input Option value.
482 * @since 1.0.0
483 */
484 public function sanitize_option( $input ) {
485
486 if ( ! $input || 'no' === $input ) {
487 return 'no';
488 }
489
490 return 'yes';
491 }
492
493 /**
494 * Print settings field HTML.
495 *
496 * @param array $args arguments to field.
497 * @since 1.0.0
498 */
499 public function render_settings_field_html( $args ) {
500 $is_checked = ( 'yes' === get_site_option( $args['name'], false ) );
501 ?>
502 <fieldset>
503 <label for="<?php echo esc_attr( $args['label_for'] ); ?>">
504 <input id="<?php echo esc_attr( $args['id'] ); ?>" type="checkbox" value="1" name="<?php echo esc_attr( $args['name'] ); ?>" <?php checked( $is_checked ); ?>>
505 <?php
506 /* translators: %s Product title */
507 echo esc_html( sprintf( __( 'Allow %s products to track non-sensitive usage tracking data.' ), $args['title'] ) );// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText
508
509 if ( is_multisite() ) {
510 esc_html_e( ' This will be applicable for all sites from the network.' );
511 }
512 ?>
513 </label>
514 <?php
515 echo wp_kses_post( sprintf( '<a href="%1s" target="_blank" rel="noreferrer noopener">%2s</a>', esc_url( $args['usage_doc_link'] ), __( 'Learn More.' ) ) );
516 ?>
517 </fieldset>
518 <?php
519 }
520
521 /**
522 * Get analytics installed time from option.
523 *
524 * @param string $source source of analytics.
525 * @return string $time analytics installed time.
526 * @since 1.0.0
527 */
528 private function get_analytics_install_time( $source ) {
529
530 $time = get_site_option( $source . '_usage_installed_time' );
531
532 if ( ! $time ) {
533 $time = time();
534 update_site_option( $source . '_usage_installed_time', $time );
535 }
536
537 return $time;
538 }
539
540 /**
541 * Schedule/unschedule cron event on updation of option.
542 *
543 * @param string $old_value old value of option.
544 * @param string $value value of option.
545 * @param string $option Option name.
546 * @since 1.0.0
547 */
548 public function update_analytics_option_callback( $old_value, $value, $option ) {
549 if ( is_multisite() ) {
550 $this->add_option_to_network( $option, $value );
551 }
552 }
553
554 /**
555 * Analytics option add callback.
556 *
557 * @param string $option Option name.
558 * @param string $value value of option.
559 * @since 1.0.0
560 */
561 public function add_analytics_option_callback( $option, $value ) {
562 if ( is_multisite() ) {
563 $this->add_option_to_network( $option, $value );
564 }
565 }
566
567 /**
568 * Send analytics track event if tracking is enabled.
569 *
570 * @since 1.0.0
571 */
572 public function maybe_track_analytics() {
573
574 if ( ! $this->is_tracking_enabled() ) {
575 return;
576 }
577
578 $analytics_track = get_site_transient( 'bsf_usage_track' );
579
580 // If the last data sent is 2 days old i.e. transient is expired.
581 if ( ! $analytics_track ) {
582 $this->send();
583 set_site_transient( 'bsf_usage_track', true, 2 * DAY_IN_SECONDS );
584 }
585 }
586
587 /**
588 * Save analytics option to network.
589 *
590 * @param string $option name of option.
591 * @param string $value value of option.
592 * @since 1.0.0
593 */
594 public function add_option_to_network( $option, $value ) {
595
596 // If action coming from general settings page.
597 if ( isset( $_POST['option_page'] ) && 'general' === $_POST['option_page'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
598
599 if ( get_site_option( $option ) ) {
600 update_site_option( $option, $value );
601 } else {
602 add_site_option( $option, $value );
603 }
604 }
605 }
606
607 /**
608 * Function to load the deactivation survey form on the admin footer.
609 *
610 * This function checks if the Deactivation_Survey_Feedback class exists and if so, it loads the deactivation survey form.
611 * The form is configured with specific settings for plugin. Example: For CartFlows, including the source, logo, plugin slug, title, support URL, description, and the screen on which to show the form.
612 *
613 * @since 1.1.6
614 * @return void
615 */
616 public function load_deactivation_survey_form() {
617
618 if ( class_exists( 'Deactivation_Survey_Feedback' ) ) {
619 foreach ( $this->entities as $key => $data ) {
620 // If the deactivation_survey info in available then only add the form.
621 if ( ! empty( $data['deactivation_survey'] ) && is_array( $data['deactivation_survey'] ) ) {
622 foreach ( $data['deactivation_survey'] as $key => $survey_args ) {
623 Deactivation_Survey_Feedback::show_feedback_form(
624 $survey_args
625 );
626 }
627 }
628 }
629 }
630 }
631
632 /**
633 * Function to add plugin slugs to Deactivation Survey vars for JS operations.
634 *
635 * @param array $vars UDS vars array.
636 * @return array Modified UDS vars array with plugin slugs.
637 * @since 1.1.6
638 */
639 public function add_slugs_to_uds_vars( $vars ) {
640 foreach ( $this->entities as $key => $data ) {
641 if ( ! empty( $data['deactivation_survey'] ) && is_array( $data['deactivation_survey'] ) ) {
642 foreach ( $data['deactivation_survey'] as $key => $survey_args ) {
643 $vars['_plugin_slug'] = isset( $vars['_plugin_slug'] ) ? array_merge( $vars['_plugin_slug'], array( $survey_args['plugin_slug'] ) ) : array( $survey_args['plugin_slug'] );
644 }
645 }
646 }
647
648 return $vars;
649 }
650 }
651 }
652