PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 1.7.1
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v1.7.1
4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.5.2 All 199 releases
betterdocs / includes / class-betterdocs-usage-tracker.php

class-betterdocs-usage-tracker.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 1.7.1, at includes/class-betterdocs-usage-tracker.php

1,034 lines 37.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * This is the class that sends all the data back to the home site
4 * It also handles opting in and deactivation
5 * @version 1.1.2
6 */
7
8 // Exit if accessed directly
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12
13 if( ! class_exists( 'BetterDocs_Plugin_Usage_Tracker') ) :
14
15 class BetterDocs_Plugin_Usage_Tracker {
16 private $wpins_version = '1.1.3';
17 private $home_url = '';
18 private $plugin_file = '';
19 private $plugin_name = '';
20 private $options = array();
21 private $require_optin = true;
22 private $include_goodbye_form = true;
23 private $marketing = false;
24 private $collect_email = false;
25
26 /**
27 * Class constructor
28 *
29 * @param $_home_url The URL to the site we're sending data to
30 * @param $_plugin_file The file path for this plugin
31 * @param $_options Plugin options to track
32 * @param $_require_optin Whether user opt-in is required (always required on WordPress.org)
33 * @param $_include_goodbye_form Whether to include a form when the user deactivates
34 * @param $_marketing Marketing method:
35 * 0: Don't collect email addresses
36 * 1: Request permission same time as tracking opt-in
37 * 2: Request permission after opt-in
38 */
39 public function __construct(
40 $_plugin_file,
41 $_home_url,
42 $_options,
43 $_require_optin=true,
44 $_include_goodbye_form=true,
45 $_marketing=false ) {
46
47 $this->plugin_file = $_plugin_file;
48 $this->home_url = trailingslashit( $_home_url );
49 $this->plugin_name = basename( $this->plugin_file, '.php' );
50 $this->options = $_options;
51 $this->require_optin = $_require_optin;
52 $this->include_goodbye_form = $_include_goodbye_form;
53 $this->marketing = $_marketing;
54
55 // Schedule some tracking when activated
56 register_activation_hook( $this->plugin_file, array( $this, 'schedule_tracking' ) );
57 // Deactivation hook
58 register_deactivation_hook( $this->plugin_file, array( $this, 'deactivate_this_plugin' ) );
59
60 // Get it going
61 $this->init();
62
63 }
64
65 public function init() {
66 // Check marketing
67 if( $this->marketing == 3 ) {
68 $this->set_can_collect_email( true, $this->plugin_name );
69 }
70 // Check whether opt-in is required
71 // If not, then tracking is allowed
72 if( ! $this->require_optin ) {
73 $this->set_can_collect_email( true, $this->plugin_name );
74 $this->set_is_tracking_allowed( true );
75 $this->update_block_notice();
76 $this->do_tracking( true );
77 }
78 // Hook our do_tracking function to the daily action
79 add_action( 'wpdeveloper_notice_clicked_for_' . $this->plugin_name, array( $this, 'clicked' ) );
80
81 add_action( 'put_do_weekly_action', array( $this, 'do_tracking' ) );
82 add_action( 'wp_ajax_optin_wizard_action_betterdocs', array( $this, 'wizard_action' ) );
83 // Use this action for local testing and for one time force tracking in a life time.
84 // add_action( 'admin_init', array( $this, 'force_tracking' ) );
85
86 // Display the admin notice on activation
87 add_action( 'wpdeveloper_optin_notice_for_' . $this->plugin_name, array( $this, 'optin_notice' ) );
88 add_action( 'admin_notices', array( $this, 'marketing_notice' ) );
89
90 // Deactivation
91 add_filter( 'plugin_action_links_' . plugin_basename( $this->plugin_file ), array( $this, 'filter_action_links' ) );
92 add_action( 'admin_footer-plugins.php', array( $this, 'goodbye_ajax' ) );
93 add_action( 'wp_ajax_goodbye_form_' . esc_attr( $this->plugin_name ), array( $this, 'goodbye_form_callback' ) );
94
95 }
96
97 public function wizard_action()
98 {
99 if( ! check_ajax_referer( 'betterdocsqswnonce', 'nonce') ) {
100 return;
101 }
102 $this->do_wizard_tracking(true, $_POST);
103 }
104 public function do_wizard_tracking( $force = false, $data = [] )
105 {
106 // If the home site hasn't been defined, we just drop out. Nothing much we can do.
107 if (!$this->home_url) {
108 return;
109 }
110 // Check to see if the user has opted in to tracking
111 $allow_tracking = $this->get_is_tracking_allowed();
112 if (!$allow_tracking) {
113 return;
114 }
115 // Check to see if it's time to track
116 $track_time = $this->get_is_time_to_track();
117 if (!$track_time && !$force) {
118 return;
119 }
120 // Get our data
121 $body = $this->get_data();
122 if( isset( $data['admin_email'] ) && ! empty( $data['admin_email'] ) ) {
123 $body['email'] = $data['admin_email'];
124 $this->set_admin_email( $body['email'] );
125 } else {
126 $this->set_admin_email();
127 }
128 // Send the data
129 $this->send_data($body);
130 }
131
132 /**
133 * When the plugin is activated
134 * Create scheduled event
135 * And check if tracking is enabled - perhaps the plugin has been reactivated
136 *
137 * @since 1.0.0
138 */
139 public function schedule_tracking() {
140 // For historical reasons, this is called 'weekly' but is in fact daily
141 if ( ! wp_next_scheduled( 'put_do_weekly_action' ) ) {
142 wp_schedule_event( time(), 'daily', 'put_do_weekly_action' );
143 }
144 }
145 /**
146 * This function is responsible for force tracking the plugin,
147 * if users are allowed to do!
148 *
149 * @return void
150 */
151 public function force_tracking(){
152 $this->do_tracking( true );
153 }
154
155 /**
156 * This is our function to get everything going
157 * Check that user has opted in
158 * Collect data
159 * Then send it back
160 *
161 * @since 1.0.0
162 * @param $force Force tracking if it's not time
163 */
164 public function do_tracking( $force=false ) {
165 // If the home site hasn't been defined, we just drop out. Nothing much we can do.
166 if ( ! $this->home_url ) {
167 return;
168 }
169
170 // Check to see if the user has opted in to tracking
171 $allow_tracking = $this->get_is_tracking_allowed();
172 if( ! $allow_tracking ) {
173 return;
174 }
175
176 // Check to see if it's time to track
177 $track_time = $this->get_is_time_to_track();
178 if( ! $track_time && ! $force ) {
179 return;
180 }
181
182 $this->set_admin_email();
183
184 // Get our data
185 $body = $this->get_data();
186 // Send the data
187 $this->send_data( $body );
188 }
189
190 /**
191 * Send the data to the home site
192 *
193 * @since 1.0.0
194 */
195 public function send_data( $body ) {
196
197 $request = wp_remote_post(
198 esc_url( $this->home_url . '?usage_tracker=hello' ),
199 array(
200 'method' => 'POST',
201 'timeout' => 20,
202 'redirection' => 5,
203 'httpversion' => '1.1',
204 'blocking' => true,
205 'body' => $body,
206 'user-agent' => 'PUT/1.0.0; ' . get_bloginfo( 'url' )
207 )
208 );
209
210 $this->set_track_time();
211
212 if( is_wp_error( $request ) ) {
213 return $request;
214 }
215
216 }
217
218 /**
219 * Here we collect most of the data
220 *
221 * @since 1.0.0
222 */
223 public function get_data() {
224
225 // Use this to pass error messages back if necessary
226 $body['message'] = '';
227
228 // Use this array to send data back
229 $body = array(
230 'plugin_slug' => sanitize_text_field( $this->plugin_name ),
231 'url' => get_bloginfo( 'url' ),
232 'site_name' => get_bloginfo( 'name' ),
233 'site_version' => get_bloginfo( 'version' ),
234 'site_language' => get_bloginfo( 'language' ),
235 'charset' => get_bloginfo( 'charset' ),
236 'wpins_version' => $this->wpins_version,
237 'php_version' => phpversion(),
238 'multisite' => is_multisite(),
239 'file_location' => __FILE__
240 );
241
242 // Collect the email if the correct option has been set
243 if( $this->get_can_collect_email() ) {
244 $body['email'] = $this->get_admin_email();
245 }
246 $body['marketing_method'] = $this->marketing;
247
248 $body['server'] = isset( $_SERVER['SERVER_SOFTWARE'] ) ? $_SERVER['SERVER_SOFTWARE'] : '';
249
250 // Retrieve current plugin information
251 if( ! function_exists( 'get_plugins' ) ) {
252 include ABSPATH . '/wp-admin/includes/plugin.php';
253 }
254
255 $plugins = array_keys( get_plugins() );
256 $active_plugins = get_option( 'active_plugins', array() );
257
258 foreach ( $plugins as $key => $plugin ) {
259 if ( in_array( $plugin, $active_plugins ) ) {
260 // Remove active plugins from list so we can show active and inactive separately
261 unset( $plugins[$key] );
262 }
263 }
264
265 $body['active_plugins'] = $active_plugins;
266 $body['inactive_plugins'] = $plugins;
267
268 // Check text direction
269 $body['text_direction'] = 'LTR';
270 if( function_exists( 'is_rtl' ) ) {
271 if( is_rtl() ) {
272 $body['text_direction'] = 'RTL';
273 }
274 } else {
275 $body['text_direction'] = 'not set';
276 }
277
278 /**
279 * Get our plugin data
280 * Currently we grab plugin name and version
281 * Or, return a message if the plugin data is not available
282 * @since 1.0.0
283 */
284 $plugin = $this->plugin_data();
285 if( empty( $plugin ) ) {
286 // We can't find the plugin data
287 // Send a message back to our home site
288 $body['message'] .= __( 'We can\'t detect any plugin information. This is most probably because you have not included the code in the plugin main file.', 'plugin-usage-tracker' );
289 $body['status'] = 'Data not found'; // Never translated
290 } else {
291 if( isset( $plugin['Name'] ) ) {
292 $body['plugin'] = sanitize_text_field( $plugin['Name'] );
293 }
294 if( isset( $plugin['Version'] ) ) {
295 $body['version'] = sanitize_text_field( $plugin['Version'] );
296 }
297 $body['status'] = 'Active'; // Never translated
298 }
299
300 /**
301 * Get our plugin options
302 * @since 1.0.0
303 */
304 $options = $this->options;
305 $plugin_options = array();
306 if( ! empty( $options ) && is_array( $options ) ) {
307 foreach( $options as $option ) {
308 $fields = get_option( $option );
309 // Check for permission to send this option
310 if( isset( $fields['wpins_registered_setting'] ) ) {
311 foreach( $fields as $key=>$value ) {
312 $plugin_options[$key] = $value;
313 }
314 }
315 }
316 }
317 $body['plugin_options'] = $this->options; // Returns array
318 $body['plugin_options_fields'] = $plugin_options; // Returns object
319
320 /**
321 * Get our theme data
322 * Currently we grab theme name and version
323 * @since 1.0.0
324 */
325 $theme = wp_get_theme();
326 if( $theme->Name ) {
327 $body['theme'] = sanitize_text_field( $theme->Name );
328 }
329 if( $theme->Version ) {
330 $body['theme_version'] = sanitize_text_field( $theme->Version );
331 }
332
333 // Return the data
334 return $body;
335
336 }
337
338 /**
339 * Return plugin data
340 * @since 1.0.0
341 */
342 public function plugin_data() {
343 // Being cautious here
344 if( ! function_exists( 'get_plugin_data' ) ) {
345 include ABSPATH . '/wp-admin/includes/plugin.php';
346 }
347 // Retrieve current plugin information
348 $plugin = get_plugin_data( $this->plugin_file );
349 return $plugin;
350 }
351
352 /**
353 * Deactivating plugin
354 * @since 1.0.0
355 */
356 public function deactivate_this_plugin() {
357 // Check to see if the user has opted in to tracking
358 $allow_tracking = $this->get_is_tracking_allowed();
359 if( ! $allow_tracking ) {
360 return;
361 }
362 $body = $this->get_data();
363 $body['status'] = 'Deactivated'; // Never translated
364 $body['deactivated_date'] = time();
365
366 // Add deactivation form data
367 if( false !== get_option( 'wpins_deactivation_reason_' . $this->plugin_name ) ) {
368 $body['deactivation_reason'] = get_option( 'wpins_deactivation_reason_' . $this->plugin_name );
369 }
370 if( false !== get_option( 'wpins_deactivation_details_' . $this->plugin_name ) ) {
371 $body['deactivation_details'] = get_option( 'wpins_deactivation_details_' . $this->plugin_name );
372 }
373
374 $this->send_data( $body );
375 // Clear scheduled update
376 wp_clear_scheduled_hook( 'put_do_weekly_action' );
377 }
378
379 /**
380 * Is tracking allowed?
381 * @since 1.0.0
382 */
383 public function get_is_tracking_allowed() {
384 // First, check if the user has changed their mind and opted out of tracking
385 if( $this->has_user_opted_out() ) {
386 $this->set_is_tracking_allowed( false, $this->plugin_name );
387 return false;
388 }
389 // The wpins_allow_tracking option is an array of plugins that are being tracked
390 $allow_tracking = get_option( 'wpins_allow_tracking' );
391 // If this plugin is in the array, then tracking is allowed
392 if( isset( $allow_tracking[$this->plugin_name] ) ) {
393 return true;
394 }
395 return false;
396 }
397
398 /**
399 * Set if tracking is allowed
400 * Option is an array of all plugins with tracking permitted
401 * More than one plugin may be using the tracker
402 * @since 1.0.0
403 * @param $is_allowed Boolean true if tracking is allowed, false if not
404 */
405 public function set_is_tracking_allowed( $is_allowed, $plugin=null ) {
406 if( empty( $plugin ) ) {
407 $plugin = $this->plugin_name;
408 }
409 // The wpins_allow_tracking option is an array of plugins that are being tracked
410 $allow_tracking = get_option( 'wpins_allow_tracking' );
411
412 // If the user has decided to opt out
413 if( $this->has_user_opted_out() ) {
414 if( isset( $allow_tracking[$plugin] ) ) {
415 unset( $allow_tracking[$plugin] );
416 }
417 } else if( $is_allowed || ! $this->require_optin ) {
418 // If the user has agreed to allow tracking or if opt-in is not required
419 if( empty( $allow_tracking ) || ! is_array( $allow_tracking ) ) {
420 // If nothing exists in the option yet, start a new array with the plugin name
421 $allow_tracking = array( $plugin => $plugin );
422 } else {
423 // Else add the plugin name to the array
424 $allow_tracking[$plugin] = $plugin;
425 }
426 } else {
427 if( isset( $allow_tracking[$plugin] ) ) {
428 unset( $allow_tracking[$plugin] );
429 }
430 }
431 update_option( 'wpins_allow_tracking', $allow_tracking );
432 }
433
434 /**
435 * Has the user opted out of allowing tracking?
436 * @since 1.1.0
437 * @return Boolean
438 */
439 public function has_user_opted_out() {
440 // Iterate through the options that are being tracked looking for wpins_opt_out setting
441 if( ! empty( $this->options ) ) {
442 foreach( $this->options as $option_name ) {
443 // Check each option
444 $options = get_option( $option_name );
445 // If we find the setting, return true
446 if( ! empty( $options['wpins_opt_out'] ) ) {
447 return true;
448 }
449 }
450 }
451 return false;
452 }
453
454 /**
455 * Check if it's time to track
456 * @since 1.1.1
457 */
458 public function get_is_time_to_track() {
459 // Let's see if we're due to track this plugin yet
460 $track_times = get_option( 'wpins_last_track_time', array() );
461 if( ! isset( $track_times[$this->plugin_name] ) ) {
462 // If we haven't set a time for this plugin yet, then we must track it
463 return true;
464 } else {
465 // If the time is set, let's see if it's more than a day ago
466 if( $track_times[$this->plugin_name] < strtotime( '-1 day' ) ) {
467 return true;
468 }
469 }
470 return false;
471 }
472
473 /**
474 * Record the time we send tracking data
475 * @since 1.1.1
476 */
477 public function set_track_time() {
478 // We've tracked, so record the time
479 $track_times = get_option( 'wpins_last_track_time', array() );
480 // Set different times according to plugin, in case we are tracking multiple plugins
481 $track_times[$this->plugin_name] = time();
482 update_option( 'wpins_last_track_time', $track_times );
483 }
484
485 /**
486 * Set if we should block the opt-in notice for this plugin
487 * Option is an array of all plugins that have received a response from the user
488 * @since 1.0.0
489 */
490 public function update_block_notice( $plugin=null ) {
491 if( empty( $plugin ) ) {
492 $plugin = $this->plugin_name;
493 }
494 $block_notice = get_option( 'wpins_block_notice' );
495 if( empty( $block_notice ) || ! is_array( $block_notice ) ) {
496 // If nothing exists in the option yet, start a new array with the plugin name
497 $block_notice = array( $plugin => $plugin );
498 } else {
499 // Else add the plugin name to the array
500 $block_notice[$plugin] = $plugin;
501 }
502 update_option( 'wpins_block_notice', $block_notice );
503 }
504
505 /**
506 * Can we collect the email address?
507 * @since 1.0.0
508 */
509 public function get_can_collect_email() {
510 // The wpins_collect_email option is an array of plugins that are being tracked
511 $collect_email = get_option( 'wpins_collect_email' );
512 // If this plugin is in the array, then we can collect the email address
513 if( isset( $collect_email[$this->plugin_name] ) ) {
514 return true;
515 }
516 return false;
517 }
518
519 /**
520 * Set if user has allowed us to collect their email address
521 * Option is an array of all plugins with email collection permitted
522 * More than one plugin may be using the tracker
523 * @since 1.0.0
524 * @param $can_collect Boolean true if collection is allowed, false if not
525 */
526 public function set_can_collect_email( $can_collect, $plugin=null ) {
527 if( empty( $plugin ) ) {
528 $plugin = $this->plugin_name;
529 }
530 // The wpins_collect_email option is an array of plugins that are being tracked
531 $collect_email = get_option( 'wpins_collect_email' );
532 // If the user has agreed to allow tracking or if opt-in is not required
533 if( $can_collect ) {
534 if( empty( $collect_email ) || ! is_array( $collect_email ) ) {
535 // If nothing exists in the option yet, start a new array with the plugin name
536 $collect_email = array( $plugin => $plugin );
537 } else {
538 // Else add the plugin name to the array
539 $collect_email[$plugin] = $plugin;
540 }
541 } else {
542 if( isset( $collect_email[$plugin] ) ) {
543 unset( $collect_email[$plugin] );
544 }
545 }
546 update_option( 'wpins_collect_email', $collect_email );
547 }
548
549 /**
550 * Get the correct email address to use
551 * @since 1.1.2
552 * @return Email address
553 */
554 public function get_admin_email() {
555 // The wpins_collect_email option is an array of plugins that are being tracked
556 $email = get_option( 'wpins_admin_emails' );
557 // If this plugin is in the array, then we can collect the email address
558 if( isset( $email[$this->plugin_name] ) ) {
559 return $email[$this->plugin_name];
560 }
561 return false;
562 }
563
564 /**
565 * Set the correct email address to use
566 * There might be more than one admin on the site
567 * So we only use the first admin's email address
568 * @param $email Email address to set
569 * @param $plugin Plugin name to set email address for
570 * @since 1.1.2
571 */
572 public function set_admin_email( $email=null, $plugin=null ) {
573 if( empty( $plugin ) ) {
574 $plugin = $this->plugin_name;
575 }
576 // If no email address passed, try to get the current user's email
577 if( empty( $email ) ) {
578 // Have to check that current user object is available
579 if( function_exists( 'wp_get_current_user' ) ) {
580 $current_user = wp_get_current_user();
581 $email = $current_user->user_email;
582 }
583 }
584 // The wpins_admin_emails option is an array of admin email addresses
585 $admin_emails = get_option( 'wpins_admin_emails' );
586 if( empty( $admin_emails ) || ! is_array( $admin_emails ) ) {
587 // If nothing exists in the option yet, start a new array with the plugin name
588 $admin_emails = array( $plugin => sanitize_email( $email ) );
589 } else if( empty( $admin_emails[$plugin] ) ) {
590 // Else add the email address to the array, if not already set
591 $admin_emails[$plugin] = sanitize_email( $email );
592 }
593 update_option( 'wpins_admin_emails', $admin_emails );
594 }
595
596 public function clicked(){
597 // Check for plugin args
598 if( isset( $_GET['plugin'] ) && isset( $_GET['plugin_action'] ) ) {
599 $plugin = sanitize_text_field( $_GET['plugin'] );
600 $action = sanitize_text_field( $_GET['plugin_action'] );
601 if( $action == 'yes' ) {
602 $this->set_is_tracking_allowed( true, $plugin );
603 $this->do_tracking( true ); // Run this straightaway
604 } else {
605 $this->set_is_tracking_allowed( false, $plugin );
606 }
607 $this->update_block_notice( $plugin );
608 }
609 }
610
611 /**
612 * Display the admin notice to users to allow them to opt in
613 *
614 * @since 1.0.0
615 */
616 public function optin_notice() {
617 // Check whether to block the notice, e.g. because we're in a local environment
618 // wpins_block_notice works the same as wpins_allow_tracking, an array of plugin names
619 $block_notice = get_option( 'wpins_block_notice' );
620 if( isset( $block_notice[$this->plugin_name] ) ) {
621 return;
622 }
623
624 if ( ! current_user_can( 'manage_options' ) ) {
625 return;
626 }
627
628 // @credit EDD
629 // Don't bother asking user to opt in if they're in local dev
630 $is_local = false;
631 if( stristr( network_site_url( '/' ), '.dev' ) !== false || stristr( network_site_url( '/' ), 'localhost' ) !== false || stristr( network_site_url( '/' ), ':8888' ) !== false ) {
632 $is_local = true;
633 }
634 $is_local = apply_filters( 'wpins_is_local_' . $this->plugin_name, $is_local );
635 if ( $is_local ) {
636 $this->update_block_notice();
637 } else {
638
639 // Display the notice requesting permission to track
640 // Retrieve current plugin information
641 $plugin = $this->plugin_data();
642 $plugin_name = $plugin['Name'];
643
644 // Args to add to query if user opts in to tracking
645 $yes_args = array(
646 'plugin' => $this->plugin_name,
647 'plugin_action' => 'yes'
648 );
649
650 // Decide how to request permission to collect email addresses
651 if( $this->marketing == 1 ) {
652 // Option 1 combines permissions to track and collect email
653 $yes_args['marketing_optin'] = 'yes';
654 } else if( $this->marketing == 2 ) {
655 // Option 2 enables a second notice that fires after the user opts in to tracking
656 $yes_args['marketing'] = 'yes';
657 }
658 $url_yes = add_query_arg( $yes_args );
659 $url_no = add_query_arg( array(
660 'plugin' => $this->plugin_name,
661 'plugin_action' => 'no'
662 ) );
663
664 // Decide on notice text
665 if( $this->marketing != 1 ) {
666 // Standard notice text
667 $notice_text = __( 'Thank you for installing our plugin. We would like to track its usage on your site. We don\'t record any sensitive data, only information regarding the WordPress environment and plugin settings, which we will use to help us make improvements to the plugin. Tracking is completely optional.', 'plugin-usage-tracker' );
668 } else {
669 // If we have option 1 for marketing, we include reference to sending product information here
670 $notice_text = __( 'Want to help make <strong>BetterDocs</strong> even more awesome? You can get a <strong>10% discount coupon</strong> for Premium extensions if you allow us to track the usage. <a class="betterdocs-insights-data-we-collect" href="#">What we collect.</a>', 'plugin-usage-tracker' );
671 }
672 // And we allow you to filter the text anyway
673 $notice_text = apply_filters( 'wpins_notice_text_' . esc_attr( $this->plugin_name ), $notice_text ); ?>
674
675 <div class="notice notice-info updated put-dismiss-notice">
676 <p><?php echo __( $notice_text ); ?></p>
677 <div class="betterdocs-insights-data" style="display: none;">
678 <p><?php echo __( 'We collect non-sensitive diagnostic data and plugin usage information. Your site URL, WordPress & PHP version, plugins & themes and email address to send you the discount coupon. This data lets us make sure this plugin always stays compatible with the most popular plugins and themes. No spam, I promise.' ); ?></p>
679 </div>
680 <p>
681 <a href="<?php echo esc_url( $url_yes ); ?>" class="button-primary"><?php _e( 'Sure, I\'d like to help', 'plugin-usage-tracker' ); ?></a>
682 <a href="<?php echo esc_url( $url_no ); ?>" class="button-secondary"><?php _e( 'No Thanks', 'plugin-usage-tracker' ); ?></a>
683 </p>
684 <?php echo "<script type='text/javascript'>jQuery('.betterdocs-insights-data-we-collect').on('click', function(e) {
685 e.preventDefault();
686 jQuery('.betterdocs-insights-data').slideToggle('fast');
687 });
688 </script>";?>
689 </div>
690 <?php
691 }
692 }
693 /**
694 * Display the marketing notice to users if enabled
695 * Only displays after the user has opted in to tracking
696 *
697 * @since 1.0.0
698 */
699 public function marketing_notice() {
700 // Check if user has opted in to marketing
701 if( isset( $_GET['marketing_optin'] ) ) {
702 // Set marketing optin
703 $this->set_can_collect_email( sanitize_text_field( $_GET['marketing_optin'] ), $this->plugin_name );
704 // Do tracking
705 $this->do_tracking( true );
706 } else if( isset( $_GET['marketing'] ) && $_GET['marketing']=='yes' ) {
707 // Display the notice requesting permission to collect email address
708 // Retrieve current plugin information
709 $plugin = $this->plugin_data();
710 $plugin_name = $plugin['Name'];
711
712 $url_yes = add_query_arg( array(
713 'plugin' => $this->plugin_name,
714 'marketing_optin' => 'yes'
715 ) );
716 $url_no = add_query_arg( array(
717 'plugin' => $this->plugin_name,
718 'marketing_optin' => 'no'
719 ) );
720
721 $marketing_text = __( 'Thank you for opting in to tracking. Would you like to receive occasional news about this plugin, including details of new features and special offers?', 'plugin-usage-tracker' );
722 $marketing_text = apply_filters( 'wpins_marketing_text_' . esc_attr( $this->plugin_name ), $marketing_text ); ?>
723
724 <div class="notice notice-info updated put-dismiss-notice">
725 <p><?php echo '<strong>' . esc_html( $plugin_name ) . '</strong>'; ?></p>
726 <p><?php echo esc_html( $marketing_text ); ?></p>
727 <p>
728 <a href="<?php echo esc_url( $url_yes ); ?>" data-putnotice="yes" class="button-secondary"><?php _e( 'Yes Please', 'plugin-usage-tracker' ); ?></a>
729 <a href="<?php echo esc_url( $url_no ); ?>" data-putnotice="no" class="button-secondary"><?php _e( 'No Thank You', 'plugin-usage-tracker' ); ?></a>
730 </p>
731 </div>
732 <?php }
733 }
734
735 /**
736 * Filter the deactivation link to allow us to present a form when the user deactivates the plugin
737 * @since 1.0.0
738 */
739 public function filter_action_links( $links ) {
740 // Check to see if the user has opted in to tracking
741 if( ! $this->get_is_tracking_allowed() ) {
742 return $links;
743 }
744 if( isset( $links['deactivate'] ) && $this->include_goodbye_form ) {
745 $deactivation_link = $links['deactivate'];
746
747
748 // Insert an onClick action to allow form before deactivating
749 $deactivation_link = str_replace( '<a ', '<div class="wpdev-put-goodbye-form-wrapper-'. esc_attr( $this->plugin_name ) .'"><div class="wpdev-put-goodbye-form-bg-'. esc_attr( $this->plugin_name ) .'"></div><span class="wpdev-put-goodbye-form" id="wpdev-put-goodbye-form-' . esc_attr( $this->plugin_name ) . '"></span></div><a onclick="javascript:event.preventDefault();" id="wpdev-put-goodbye-link-' . esc_attr( $this->plugin_name ) . '" ', $deactivation_link );
750 $links['deactivate'] = $deactivation_link;
751 }
752 return $links;
753 }
754
755 /*
756 * Form text strings
757 * These are non-filterable and used as fallback in case filtered strings aren't set correctly
758 * @since 1.0.0
759 */
760 public function form_default_text() {
761 $form = array();
762 $form['heading'] = __( 'Sorry to see you go', 'plugin-usage-tracker' );
763 $form['body'] = __( 'Before you deactivate the plugin, would you quickly give us your reason for doing so?', 'plugin-usage-tracker' );
764
765 $form['options'] = array(
766 __( 'I no longer need the plugin', 'plugin-usage-tracker' ),
767 [
768 'label' => __( 'I found a better plugin', 'plugin-usage-tracker' ),
769 'extra_field' => __( 'Please share which plugin', 'plugin-usage-tracker' )
770 ],
771 __( "I couldn't get the plugin to work", 'plugin-usage-tracker' ),
772 __( 'It\'s a temporary deactivation', 'plugin-usage-tracker' ),
773 [
774 'label' => __( 'Other', 'plugin-usage-tracker' ),
775 'extra_field' => __( 'Please share the reason', 'plugin-usage-tracker' ),
776 'type' => 'textarea'
777 ]
778 );
779
780 return $form;
781 }
782
783 /**
784 * Form text strings
785 * These can be filtered
786 * The filter hook must be unique to the plugin
787 * @since 1.0.0
788 */
789 public function form_filterable_text() {
790 $form = $this->form_default_text();
791 return apply_filters( 'wpins_form_text_' . esc_attr( $this->plugin_name ), $form );
792 }
793
794 /**
795 * Form text strings
796 * These can be filtered
797 * @since 1.0.0
798 */
799 public function goodbye_ajax() {
800 // Get our strings for the form
801 $form = $this->form_filterable_text();
802 if( ! isset( $form['heading'] ) || ! isset( $form['body'] ) || ! isset( $form['options'] ) || ! is_array( $form['options'] ) || ! isset( $form['details'] ) ) {
803 // If the form hasn't been filtered correctly, we revert to the default form
804 $form = $this->form_default_text();
805 }
806 // Build the HTML to go in the form
807 $html = '<div class="wpdev-put-goodbye-form-head"><strong>' . esc_html( $form['heading'] ) . '</strong></div>';
808 $html .= '<div class="wpdev-put-goodbye-form-body"><p class="wpdev-put-goodbye-form-caption">' . esc_html( $form['body'] ) . '</p>';
809 if( is_array( $form['options'] ) ) {
810 $html .= '<div id="wpdev-'. esc_attr( $this->plugin_name ) .'-goodbye-options" class="wpdev-'. esc_attr( $this->plugin_name ) .'-goodbye-options"><ul>';
811 foreach( $form['options'] as $option ) {
812 if( is_array( $option ) ) {
813 $id = strtolower( str_replace( " ", "_", esc_attr( $option['label'] ) ) );
814 $id = $id . '_' . esc_attr( $this->plugin_name );
815 $html .= '<li class="has-goodbye-extra">';
816 $html .= '<input type="radio" name="wpdev-'. esc_attr( $this->plugin_name ) .'-goodbye-options" id="' . $id . '" value="' . esc_attr( $option['label'] ) . '">';
817 $html .= '<div><label for="' . $id . '">' . esc_attr( $option['label'] ) . '</label>';
818 if( isset( $option[ 'extra_field' ] ) && ! isset( $option['type'] )) {
819 $html .= '<input type="text" style="display: none" name="'. $id .'" id="' . str_replace( " ", "", esc_attr( $option['extra_field'] ) ) . '" placeholder="' . esc_attr( $option['extra_field'] ) . '">';
820 }
821 if( isset( $option[ 'extra_field' ] ) && isset( $option['type'] )) {
822 $html .= '<'. $option['type'] .' style="display: none" type="text" name="'. $id .'" id="' . str_replace( " ", "", esc_attr( $option['extra_field'] ) ) . '" placeholder="' . esc_attr( $option['extra_field'] ) . '"></' . $option['type'] . '>';
823 }
824 $html .= '</div></li>';
825 } else {
826 $id = strtolower( str_replace( " ", "_", esc_attr( $option ) ) );
827 $id = $id . '_' . esc_attr( $this->plugin_name );
828 $html .= '<li><input type="radio" name="wpdev-'. esc_attr( $this->plugin_name ) .'-goodbye-options" id="' . $id . '" value="' . esc_attr( $option ) . '"> <label for="' . $id . '">' . esc_attr( $option ) . '</label></li>';
829 }
830 }
831 $html .= '</ul></div><!-- .wpdev-'. esc_attr( $this->plugin_name ) .'-goodbye-options -->';
832 }
833 $html .= '</div><!-- .wpdev-put-goodbye-form-body -->';
834 $html .= '<p class="deactivating-spinner"><span class="spinner"></span> ' . __( 'Submitting form', 'plugin-usage-tracker' ) . '</p>';
835 ?>
836 <style type="text/css">
837 .wpdev-put-form-active-<?php echo esc_attr( $this->plugin_name ); ?> .wpdev-put-goodbye-form-bg-<?php echo esc_attr( $this->plugin_name ); ?> {
838 background: rgba( 0, 0, 0, .8 );
839 position: fixed;
840 top: 0;
841 left: 0;
842 width: 100%;
843 height: 100%;
844 z-index: 9;
845 }
846 .wpdev-put-goodbye-form-wrapper-<?php echo esc_attr( $this->plugin_name ); ?> {
847 position: relative;
848 display: none;
849 }
850 .wpdev-put-form-active-<?php echo esc_attr( $this->plugin_name ); ?> .wpdev-put-goodbye-form-wrapper-<?php echo esc_attr( $this->plugin_name ); ?> {
851 display: flex !important;
852 align-items: center;
853 justify-content: center;
854 width: 100%;
855 height: 100%;
856 position: fixed;
857 left: 0px;
858 top: 0px;
859 }
860 .wpdev-put-goodbye-form {
861 display: none;
862 }
863 .wpdev-put-form-active-<?php echo esc_attr( $this->plugin_name ); ?> .wpdev-put-goodbye-form {
864 position: relative !important;
865 width: 550px;
866 max-width: 80%;
867 background: #fff;
868 box-shadow: 2px 8px 23px 3px rgba(0,0,0,.2);
869 border-radius: 3px;
870 white-space: normal;
871 overflow: hidden;
872 display: block;
873 z-index: 999999;
874 }
875 .wpdev-put-goodbye-form-head {
876 background: #fff;
877 color: #495157;
878 padding: 18px;
879 box-shadow: 0 0 8px rgba(0,0,0,.1);
880 font-size: 15px;
881 }
882 .wpdev-put-goodbye-form .wpdev-put-goodbye-form-head strong {
883 font-size: 15px;
884 }
885 .wpdev-put-goodbye-form-body {
886 padding: 8px 18px;
887 color: #333;
888 }
889 .wpdev-put-goodbye-form-body label {
890 color: #6d7882;
891 padding-left: 5px;
892 }
893 .wpdev-put-goodbye-form-body .wpdev-put-goodbye-form-caption {
894 font-weight: 500;
895 font-size: 15px;
896 color: #495157;
897 line-height: 1.4;
898 }
899 .wpdev-put-goodbye-form-body #wpdev-<?php echo esc_attr( $this->plugin_name ); ?>-goodbye-options {
900 padding-top: 5px;
901 }
902 .wpdev-put-goodbye-form-body #wpdev-<?php echo esc_attr( $this->plugin_name ); ?>-goodbye-options ul > li {
903 margin-bottom: 15px;
904 }
905 .deactivating-spinner {
906 display: none;
907 padding-bottom: 20px !important;
908 }
909 .deactivating-spinner .spinner {
910 float: none;
911 margin: 4px 4px 0 18px;
912 vertical-align: bottom;
913 visibility: visible;
914 }
915 .wpdev-put-goodbye-form-footer {
916 padding: 8px 18px;
917 margin-bottom: 15px;
918 }
919 .wpdev-put-goodbye-form-footer > .wpdev-put-goodbye-form-buttons {
920 display: flex;
921 align-items: center;
922 justify-content: space-between;
923 }
924 .wpdev-put-goodbye-form-footer .betterdocs-put-submit-btn {
925 background-color: #d30c5c;
926 -webkit-border-radius: 3px;
927 border-radius: 3px;
928 color: #fff;
929 line-height: 1;
930 padding: 15px 20px;
931 font-size: 13px;
932 }
933 .wpdev-put-goodbye-form-footer .betterdocs-put-deactivate-btn {
934 font-size: 13px;
935 color: #a4afb7;
936 background: none;
937 float: right;
938 padding-right: 10px;
939 width: auto;
940 text-decoration: underline;
941 }
942 #wpdev-<?php echo esc_attr( $this->plugin_name ); ?>-goodbye-options ul li > div {
943 display: inline;
944 padding-left: 3px;
945 }
946 #wpdev-<?php echo esc_attr( $this->plugin_name ); ?>-goodbye-options ul li > div > input, #wpdev-<?php echo esc_attr( $this->plugin_name ); ?>-goodbye-options ul li > div > textarea {
947 margin: 10px 18px;
948 padding: 8px;
949 width: 80%;
950 }
951 </style>
952 <script>
953 jQuery(document).ready(function($){
954 $("#wpdev-put-goodbye-link-<?php echo esc_attr( $this->plugin_name ); ?>").on("click",function(){
955 // We'll send the user to this deactivation link when they've completed or dismissed the form
956 var url = document.getElementById("wpdev-put-goodbye-link-<?php echo esc_attr( $this->plugin_name ); ?>");
957 $('body').toggleClass('wpdev-put-form-active-<?php echo esc_attr( $this->plugin_name ); ?>');
958 $("#wpdev-put-goodbye-form-<?php echo esc_attr( $this->plugin_name ); ?>").fadeIn();
959 $("#wpdev-put-goodbye-form-<?php echo esc_attr( $this->plugin_name ); ?>").html( '<?php echo $html; ?>' + '<div class="wpdev-put-goodbye-form-footer"><div class="wpdev-put-goodbye-form-buttons"><a id="put-submit-form-<?php echo esc_attr( $this->plugin_name ); ?>" class="betterdocs-put-submit-btn" href="#"><?php _e( 'Submit and Deactivate', 'plugin-usage-tracker' ); ?></a>&nbsp;<a class="betterdocs-put-deactivate-btn" href="'+url+'"><?php _e( 'Just Deactivate', 'plugin-usage-tracker' ); ?></a></div></div>');
960 $('#put-submit-form-<?php echo esc_attr( $this->plugin_name ); ?>').on('click', function(e){
961 // As soon as we click, the body of the form should disappear
962 $("#wpdev-put-goodbye-form-<?php echo esc_attr( $this->plugin_name ); ?> .wpdev-put-goodbye-form-body").fadeOut();
963 $("#wpdev-put-goodbye-form-<?php echo esc_attr( $this->plugin_name ); ?> .wpdev-put-goodbye-form-footer").fadeOut();
964 // Fade in spinner
965 $("#wpdev-put-goodbye-form-<?php echo esc_attr( $this->plugin_name ); ?> .deactivating-spinner").fadeIn();
966 e.preventDefault();
967 var checkedInput = $("input[name='wpdev-<?php echo esc_attr( $this->plugin_name ); ?>-goodbye-options']:checked"),
968 checkedInputVal, details;
969 if( checkedInput.length > 0 ) {
970 checkedInputVal = checkedInput.val();
971 details = $('input[name="'+ checkedInput[0].id +'"], textarea[name="'+ checkedInput[0].id +'"]').val();
972 }
973
974 if( typeof details === 'undefined' ) {
975 details = '';
976 }
977 if( typeof checkedInputVal === 'undefined' ) {
978 checkedInputVal = 'No Reason';
979 }
980
981 var data = {
982 'action': 'goodbye_form_<?php echo esc_attr( $this->plugin_name ); ?>',
983 'values': checkedInputVal,
984 'details': details,
985 'security': "<?php echo wp_create_nonce ( 'wpins_goodbye_form' ); ?>",
986 'dataType': "json"
987 }
988
989 $.post(
990 ajaxurl,
991 data,
992 function(response){
993 // Redirect to original deactivation URL
994 window.location.href = url;
995 }
996 );
997 });
998 $('#wpdev-<?php echo esc_attr( $this->plugin_name ); ?>-goodbye-options > ul ').on('click', 'li label, li > input', function( e ){
999 var parent = $(this).parents('li');
1000 parent.siblings().find('label').next('input, textarea').css('display', 'none');
1001 parent.find('label').next('input, textarea').css('display', 'block');
1002 });
1003 // If we click outside the form, the form will close
1004 $('.wpdev-put-goodbye-form-bg-<?php echo esc_attr( $this->plugin_name ); ?>').on('click',function(){
1005 $("#wpdev-put-goodbye-form-<?php echo esc_attr( $this->plugin_name ); ?>").fadeOut();
1006 $('body').removeClass('wpdev-put-form-active-<?php echo esc_attr( $this->plugin_name ); ?>');
1007 });
1008 });
1009
1010
1011 });
1012 </script>
1013 <?php }
1014
1015 /**
1016 * AJAX callback when the form is submitted
1017 * @since 1.0.0
1018 */
1019 public function goodbye_form_callback() {
1020 check_ajax_referer( 'wpins_goodbye_form', 'security' );
1021 if( isset( $_POST['values'] ) ) {
1022 $values = $_POST['values'];
1023 update_option( 'wpins_deactivation_reason_' . $this->plugin_name, $values );
1024 }
1025 if( isset( $_POST['details'] ) ) {
1026 $details = sanitize_text_field( $_POST['details'] );
1027 update_option( 'wpins_deactivation_details_' . $this->plugin_name, $details );
1028 }
1029 $this->do_tracking(); // Run this straightaway
1030 echo 'success';
1031 wp_die();
1032 }
1033 }
1034 endif;