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