PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 2.8.16
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v2.8.16
3.13.3 3.13.2 3.13.1 3.13.0 3.12.13 3.12.12 3.12.11 3.12.10 3.12.9 3.12.8 3.12.7 3.12.6 3.12.5 3.12.4 3.12.3 3.12.1 3.12.2 3.12.0 3.11.1 3.11.0 3.10.9 3.10.8 3.10.7 3.10.6 2.8.16 All 261 releases
wpfunnels / vendor / appsero / client / src / Insights.php

Insights.php in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell 2.8.16, at vendor/appsero/client/src/Insights.php

1,222 lines 43.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Appsero;
4
5 /**
6 * Appsero Insights
7 *
8 * This is a tracker class to track plugin usage based on if the customer has opted in.
9 * No personal information is being tracked by this class, only general settings, active plugins, environment details
10 * and admin email.
11 */
12 class Insights {
13
14 /**
15 * The notice text
16 *
17 * @var string
18 */
19 public $notice;
20
21 /**
22 * Wheather to the notice or not
23 *
24 * @var bool
25 */
26 protected $show_notice = true;
27
28 /**
29 * If extra data needs to be sent
30 *
31 * @var array
32 */
33 protected $extra_data = [];
34
35 /**
36 * AppSero\Client
37 *
38 * @var object
39 */
40 protected $client;
41
42 /**
43 * @var bool
44 */
45 private $plugin_data = false;
46
47 /**
48 * Initialize the class
49 *
50 * @param null $name
51 * @param null $file
52 */
53 public function __construct( $client, $name = null, $file = null ) {
54 if ( is_string( $client ) && ! empty( $name ) && ! empty( $file ) ) {
55 $client = new Client( $client, $name, $file );
56 }
57
58 if ( is_object( $client ) && is_a( $client, 'Appsero\Client' ) ) {
59 $this->client = $client;
60 }
61 }
62
63 /**
64 * Don't show the notice
65 *
66 * @return \self
67 */
68 public function hide_notice() {
69 $this->show_notice = false;
70
71 return $this;
72 }
73
74 /**
75 * Add plugin data if needed
76 *
77 * @return \self
78 */
79 public function add_plugin_data() {
80 $this->plugin_data = true;
81
82 return $this;
83 }
84
85 /**
86 * Add extra data if needed
87 *
88 * @param array $data
89 *
90 * @return \self
91 */
92 public function add_extra( $data = [] ) {
93 $this->extra_data = $data;
94
95 return $this;
96 }
97
98 /**
99 * Set custom notice text
100 *
101 * @param string $text
102 *
103 * @return \self
104 */
105 public function notice( $text = '' ) {
106 $this->notice = $text;
107
108 return $this;
109 }
110
111 /**
112 * Initialize insights
113 *
114 * @return void
115 */
116 public function init() {
117 if ( $this->client->type === 'plugin' ) {
118 $this->init_plugin();
119 } elseif ( $this->client->type === 'theme' ) {
120 $this->init_theme();
121 }
122 }
123
124 /**
125 * Initialize theme hooks
126 *
127 * @return void
128 */
129 public function init_theme() {
130 $this->init_common();
131
132 add_action( 'switch_theme', [ $this, 'deactivation_cleanup' ] );
133 add_action( 'switch_theme', [ $this, 'theme_deactivated' ], 12, 3 );
134 }
135
136 /**
137 * Initialize plugin hooks
138 *
139 * @return void
140 */
141 public function init_plugin() {
142 // plugin deactivate popup
143 if ( ! $this->is_local_server() ) {
144 add_filter( 'plugin_action_links_' . $this->client->basename, [ $this, 'plugin_action_links' ] );
145 add_action( 'admin_footer', [ $this, 'deactivate_scripts' ] );
146 }
147
148 $this->init_common();
149
150 register_activation_hook( $this->client->file, [ $this, 'activate_plugin' ] );
151 register_deactivation_hook( $this->client->file, [ $this, 'deactivation_cleanup' ] );
152 }
153
154 /**
155 * Initialize common hooks
156 *
157 * @return void
158 */
159 protected function init_common() {
160 if ( $this->show_notice ) {
161 // tracking notice
162 add_action( 'admin_notices', [ $this, 'admin_notice' ] );
163 }
164
165 add_action( 'admin_init', [ $this, 'handle_optin_optout' ] );
166
167 // uninstall reason
168 add_action( 'wp_ajax_' . $this->client->slug . '_submit-uninstall-reason', [ $this, 'uninstall_reason_submission' ] );
169
170 // cron events
171 add_filter( 'cron_schedules', [ $this, 'add_weekly_schedule' ] );
172 add_action( $this->client->slug . '_tracker_send_event', [ $this, 'send_tracking_data' ] );
173 // add_action( 'admin_init', array( $this, 'send_tracking_data' ) ); // test
174 }
175
176 /**
177 * Send tracking data to AppSero server
178 *
179 * @param bool $override
180 *
181 * @return void
182 */
183 public function send_tracking_data( $override = false ) {
184 if ( ! $this->tracking_allowed() && ! $override ) {
185 return;
186 }
187
188 // Send a maximum of once per week
189 $last_send = $this->get_last_send();
190
191 if ( $last_send && $last_send > strtotime( '-1 week' ) ) {
192 return;
193 }
194
195 $tracking_data = $this->get_tracking_data();
196
197 $response = $this->client->send_request( $tracking_data, 'track' );
198
199 update_option( $this->client->slug . '_tracking_last_send', time() );
200 }
201
202 /**
203 * Get the tracking data points
204 *
205 * @return array
206 */
207 protected function get_tracking_data() {
208 $all_plugins = $this->get_all_plugins();
209
210 $users = get_users(
211 [
212 'role' => 'administrator',
213 'orderby' => 'ID',
214 'order' => 'ASC',
215 'number' => 1,
216 'paged' => 1,
217 ]
218 );
219
220 $admin_user = ( is_array( $users ) && ! empty( $users ) ) ? $users[0] : false;
221 $first_name = '';
222 $last_name = '';
223
224 if ( $admin_user ) {
225 $first_name = $admin_user->first_name ? $admin_user->first_name : $admin_user->display_name;
226 $last_name = $admin_user->last_name;
227 }
228
229 $data = [
230 'url' => esc_url( home_url() ),
231 'site' => $this->get_site_name(),
232 'admin_email' => get_option( 'admin_email' ),
233 'first_name' => $first_name,
234 'last_name' => $last_name,
235 'hash' => $this->client->hash,
236 'server' => $this->get_server_info(),
237 'wp' => $this->get_wp_info(),
238 'users' => $this->get_user_counts(),
239 'active_plugins' => count( $all_plugins['active_plugins'] ),
240 'inactive_plugins' => count( $all_plugins['inactive_plugins'] ),
241 'ip_address' => $this->get_user_ip_address(),
242 'project_version' => $this->client->project_version,
243 'tracking_skipped' => false,
244 'is_local' => $this->is_local_server(),
245 ];
246
247 // Add Plugins
248 if ( $this->plugin_data ) {
249 $plugins_data = [];
250
251 foreach ( $all_plugins['active_plugins'] as $slug => $plugin ) {
252 $slug = strstr( $slug, '/', true );
253
254 if ( ! $slug ) {
255 continue;
256 }
257
258 $plugins_data[ $slug ] = [
259 'name' => isset( $plugin['name'] ) ? $plugin['name'] : '',
260 'version' => isset( $plugin['version'] ) ? $plugin['version'] : '',
261 ];
262 }
263
264 if ( array_key_exists( $this->client->slug, $plugins_data ) ) {
265 unset( $plugins_data[ $this->client->slug ] );
266 }
267
268 $data['plugins'] = $plugins_data;
269 }
270
271 // Add Metadata
272 $extra = $this->get_extra_data();
273
274 if ( $extra ) {
275 $data['extra'] = $extra;
276 }
277
278 // Check this has previously skipped tracking
279 $skipped = get_option( $this->client->slug . '_tracking_skipped' );
280
281 if ( $skipped === 'yes' ) {
282 delete_option( $this->client->slug . '_tracking_skipped' );
283
284 $data['tracking_skipped'] = true;
285 }
286
287 return apply_filters( $this->client->slug . '_tracker_data', $data );
288 }
289
290 /**
291 * If a child class wants to send extra data
292 *
293 * @return mixed
294 */
295 protected function get_extra_data() {
296 if ( is_callable( $this->extra_data ) ) {
297 return call_user_func( $this->extra_data );
298 }
299
300 if ( is_array( $this->extra_data ) ) {
301 return $this->extra_data;
302 }
303
304 return [];
305 }
306
307 /**
308 * Explain the user which data we collect
309 *
310 * @return array
311 */
312 protected function data_we_collect() {
313 $data = [
314 'Server environment details (php, mysql, server, WordPress versions)',
315 'Number of users in your site',
316 'Site language',
317 'Number of active and inactive plugins',
318 'Site name and URL',
319 'Your name and email address',
320 ];
321
322 if ( $this->plugin_data ) {
323 array_splice( $data, 4, 0, [ "active plugins' name" ] );
324 }
325
326 return $data;
327 }
328
329 /**
330 * Check if the user has opted into tracking
331 *
332 * @return bool
333 */
334 public function tracking_allowed() {
335 $allow_tracking = get_option( $this->client->slug . '_allow_tracking', 'no' );
336
337 return $allow_tracking === 'yes';
338 }
339
340 /**
341 * Get the last time a tracking was sent
342 *
343 * @return false|string
344 */
345 private function get_last_send() {
346 return get_option( $this->client->slug . '_tracking_last_send', false );
347 }
348
349 /**
350 * Check if the notice has been dismissed or enabled
351 *
352 * @return bool
353 */
354 public function notice_dismissed() {
355 $hide_notice = get_option( $this->client->slug . '_tracking_notice', null );
356
357 if ( 'hide' === $hide_notice ) {
358 return true;
359 }
360
361 return false;
362 }
363
364 /**
365 * Check if the current server is localhost
366 *
367 * @return bool
368 */
369 private function is_local_server() {
370 $host = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : 'localhost';
371 $ip = isset( $_SERVER['SERVER_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_ADDR'] ) ) : '127.0.0.1';
372 $is_local = false;
373
374 if ( in_array( $ip, [ '127.0.0.1', '::1' ], true )
375 || ! strpos( $host, '.' )
376 || in_array( strrchr( $host, '.' ), [ '.test', '.testing', '.local', '.localhost', '.localdomain' ], true )
377 ) {
378 $is_local = true;
379 }
380
381 return apply_filters( 'appsero_is_local', $is_local );
382 }
383
384 /**
385 * Schedule the event weekly
386 *
387 * @return void
388 */
389 private function schedule_event() {
390 $hook_name = wp_unslash( $this->client->slug . '_tracker_send_event' );
391
392 if ( ! wp_next_scheduled( $hook_name ) ) {
393 wp_schedule_event( time(), 'weekly', $hook_name );
394 }
395 }
396
397 /**
398 * Clear any scheduled hook
399 *
400 * @return void
401 */
402 private function clear_schedule_event() {
403 wp_clear_scheduled_hook( $this->client->slug . '_tracker_send_event' );
404 }
405
406 /**
407 * Display the admin notice to users that have not opted-in or out
408 *
409 * @return void
410 */
411 public function admin_notice() {
412 if ( $this->notice_dismissed() ) {
413 return;
414 }
415
416 if ( $this->tracking_allowed() ) {
417 return;
418 }
419
420 if ( ! current_user_can( 'manage_options' ) ) {
421 return;
422 }
423
424 // don't show tracking if a local server
425 if ( $this->is_local_server() ) {
426 return;
427 }
428
429 $optin_url = wp_nonce_url( add_query_arg( $this->client->slug . '_tracker_optin', 'true' ), '_wpnonce' );
430 $optout_url = wp_nonce_url( add_query_arg( $this->client->slug . '_tracker_optout', 'true' ), '_wpnonce' );
431
432 if ( empty( $this->notice ) ) {
433 $notice = sprintf( $this->client->__trans( 'Want to help make <strong>%1$s</strong> even more awesome? Allow %1$s to collect diagnostic data and usage information.' ), $this->client->name );
434 } else {
435 $notice = $this->notice;
436 }
437
438 $policy_url = 'https://appsero.com/privacy-policy/';
439
440 $notice .= ' (<a class="' . $this->client->slug . '-insights-data-we-collect" href="#">' . $this->client->__trans( 'what we collect' ) . '</a>)';
441 $notice .= '<p class="description" style="display:none;">' . implode( ', ', $this->data_we_collect() ) . '. ';
442 $notice .= 'We are using Appsero to collect your data. <a href="' . $policy_url . '" target="_blank">Learn more</a> about how Appsero collects and handle your data.</p>';
443
444 echo '<div class="updated"><p>';
445 echo $notice;
446 echo '</p><p class="submit">';
447 echo '&nbsp;<a href="' . esc_url( $optin_url ) . '" class="button-primary button-large">' . $this->client->__trans( 'Allow' ) . '</a>';
448 echo '&nbsp;<a href="' . esc_url( $optout_url ) . '" class="button-secondary button-large">' . $this->client->__trans( 'No thanks' ) . '</a>';
449 echo '</p></div>';
450
451 echo "<script type='text/javascript'>jQuery('." . $this->client->slug . "-insights-data-we-collect').on('click', function(e) {
452 e.preventDefault();
453 jQuery(this).parents('.updated').find('p.description').slideToggle('fast');
454 });
455 </script>
456 ";
457 }
458
459 /**
460 * Handle the optin/optout
461 *
462 * @return void
463 */
464 public function handle_optin_optout() {
465 if ( ! isset( $_GET['_wpnonce'] ) ) {
466 return;
467 }
468
469 if ( ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), '_wpnonce' ) ) {
470 return;
471 }
472
473 if ( isset( $_GET[ $this->client->slug . '_tracker_optin' ] ) && $_GET[ $this->client->slug . '_tracker_optin' ] === 'true' ) {
474 $this->optin();
475
476 wp_safe_redirect( remove_query_arg( $this->client->slug . '_tracker_optin' ) );
477 exit;
478 }
479
480 if ( isset( $_GET[ $this->client->slug . '_tracker_optout' ] ) && isset( $_GET[ $this->client->slug . '_tracker_optout' ] ) && $_GET[ $this->client->slug . '_tracker_optout' ] === 'true' ) {
481 $this->optout();
482
483 wp_safe_redirect( remove_query_arg( $this->client->slug . '_tracker_optout' ) );
484 exit;
485 }
486 }
487
488 /**
489 * Tracking optin
490 *
491 * @return void
492 */
493 public function optin() {
494 update_option( $this->client->slug . '_allow_tracking', 'yes' );
495 update_option( $this->client->slug . '_tracking_notice', 'hide' );
496
497 $this->clear_schedule_event();
498 $this->schedule_event();
499 $this->send_tracking_data();
500
501 /*
502 * Fires when the user has opted in tracking.
503 */
504 do_action( $this->client->slug . '_tracker_optin', $this->get_tracking_data() );
505 }
506
507 /**
508 * Optout from tracking
509 *
510 * @return void
511 */
512 public function optout() {
513 update_option( $this->client->slug . '_allow_tracking', 'no' );
514 update_option( $this->client->slug . '_tracking_notice', 'hide' );
515
516 $this->send_tracking_skipped_request();
517
518 $this->clear_schedule_event();
519
520 /*
521 * Fires when the user has opted out tracking.
522 */
523 do_action( $this->client->slug . '_tracker_optout' );
524 }
525
526 /**
527 * Get the number of post counts
528 *
529 * @param string $post_type
530 *
531 * @return int
532 */
533 public function get_post_count( $post_type ) {
534 global $wpdb;
535
536 return (int) $wpdb->get_var(
537 $wpdb->prepare(
538 "SELECT count(ID) FROM $wpdb->posts WHERE post_type = %s and post_status = %s",
539 [ $post_type, 'publish' ]
540 )
541 );
542 }
543
544 /**
545 * Get server related info.
546 *
547 * @return array
548 */
549 private static function get_server_info() {
550 global $wpdb;
551
552 $server_data = [];
553
554 if ( isset( $_SERVER['SERVER_SOFTWARE'] ) && ! empty( $_SERVER['SERVER_SOFTWARE'] ) ) {
555 // phpcs:ignore
556 $server_data['software'] = $_SERVER['SERVER_SOFTWARE'];
557 }
558
559 if ( function_exists( 'phpversion' ) ) {
560 $server_data['php_version'] = phpversion();
561 }
562
563 $server_data['mysql_version'] = $wpdb->db_version();
564
565 $server_data['php_max_upload_size'] = size_format( wp_max_upload_size() );
566 $server_data['php_default_timezone'] = date_default_timezone_get();
567 $server_data['php_soap'] = class_exists( 'SoapClient' ) ? 'Yes' : 'No';
568 $server_data['php_fsockopen'] = function_exists( 'fsockopen' ) ? 'Yes' : 'No';
569 $server_data['php_curl'] = function_exists( 'curl_init' ) ? 'Yes' : 'No';
570
571 return $server_data;
572 }
573
574 /**
575 * Get WordPress related data.
576 *
577 * @return array
578 */
579 private function get_wp_info() {
580 $wp_data = [];
581
582 $wp_data['memory_limit'] = WP_MEMORY_LIMIT;
583 $wp_data['debug_mode'] = ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ? 'Yes' : 'No';
584 $wp_data['locale'] = get_locale();
585 $wp_data['version'] = get_bloginfo( 'version' );
586 $wp_data['multisite'] = is_multisite() ? 'Yes' : 'No';
587 $wp_data['theme_slug'] = get_stylesheet();
588
589 $theme = wp_get_theme( $wp_data['theme_slug'] );
590
591 $wp_data['theme_name'] = $theme->get( 'Name' );
592 $wp_data['theme_version'] = $theme->get( 'Version' );
593 $wp_data['theme_uri'] = $theme->get( 'ThemeURI' );
594 $wp_data['theme_author'] = $theme->get( 'Author' );
595
596 return $wp_data;
597 }
598
599 /**
600 * Get the list of active and inactive plugins
601 *
602 * @return array
603 */
604 private function get_all_plugins() {
605 // Ensure get_plugins function is loaded
606 if ( ! function_exists( 'get_plugins' ) ) {
607 include ABSPATH . '/wp-admin/includes/plugin.php';
608 }
609
610 $plugins = get_plugins();
611 $active_plugins_keys = get_option( 'active_plugins', [] );
612 $active_plugins = [];
613
614 foreach ( $plugins as $k => $v ) {
615 // Take care of formatting the data how we want it.
616 $formatted = [];
617 $formatted['name'] = wp_strip_all_tags( $v['Name'] );
618
619 if ( isset( $v['Version'] ) ) {
620 $formatted['version'] = wp_strip_all_tags( $v['Version'] );
621 }
622
623 if ( isset( $v['Author'] ) ) {
624 $formatted['author'] = wp_strip_all_tags( $v['Author'] );
625 }
626
627 if ( isset( $v['Network'] ) ) {
628 $formatted['network'] = wp_strip_all_tags( $v['Network'] );
629 }
630
631 if ( isset( $v['PluginURI'] ) ) {
632 $formatted['plugin_uri'] = wp_strip_all_tags( $v['PluginURI'] );
633 }
634
635 if ( in_array( $k, $active_plugins_keys, true ) ) {
636 // Remove active plugins from list so we can show active and inactive separately
637 unset( $plugins[ $k ] );
638 $active_plugins[ $k ] = $formatted;
639 } else {
640 $plugins[ $k ] = $formatted;
641 }
642 }
643
644 return [
645 'active_plugins' => $active_plugins,
646 'inactive_plugins' => $plugins,
647 ];
648 }
649
650 /**
651 * Get user totals based on user role.
652 *
653 * @return array
654 */
655 public function get_user_counts() {
656 $user_count = [];
657 $user_count_data = count_users();
658 $user_count['total'] = $user_count_data['total_users'];
659
660 // Get user count based on user role
661 foreach ( $user_count_data['avail_roles'] as $role => $count ) {
662 if ( ! $count ) {
663 continue;
664 }
665
666 $user_count[ $role ] = $count;
667 }
668
669 return $user_count;
670 }
671
672 /**
673 * Add weekly cron schedule
674 *
675 * @param array $schedules
676 *
677 * @return array
678 */
679 public function add_weekly_schedule( $schedules ) {
680 $schedules['weekly'] = [
681 'interval' => DAY_IN_SECONDS * 7,
682 'display' => 'Once Weekly',
683 ];
684
685 return $schedules;
686 }
687
688 /**
689 * Plugin activation hook
690 *
691 * @return void
692 */
693 public function activate_plugin() {
694 $allowed = get_option( $this->client->slug . '_allow_tracking', 'no' );
695
696 // if it wasn't allowed before, do nothing
697 if ( 'yes' !== $allowed ) {
698 return;
699 }
700
701 // re-schedule and delete the last sent time so we could force send again
702 $hook_name = $this->client->slug . '_tracker_send_event';
703
704 if ( ! wp_next_scheduled( $hook_name ) ) {
705 wp_schedule_event( time(), 'weekly', $hook_name );
706 }
707
708 delete_option( $this->client->slug . '_tracking_last_send' );
709
710 $this->send_tracking_data( true );
711 }
712
713 /**
714 * Clear our options upon deactivation
715 *
716 * @return void
717 */
718 public function deactivation_cleanup() {
719 $this->clear_schedule_event();
720
721 if ( 'theme' === $this->client->type ) {
722 delete_option( $this->client->slug . '_tracking_last_send' );
723 delete_option( $this->client->slug . '_allow_tracking' );
724 }
725
726 delete_option( $this->client->slug . '_tracking_notice' );
727 }
728
729 /**
730 * Hook into action links and modify the deactivate link
731 *
732 * @param array $links
733 *
734 * @return array
735 */
736 public function plugin_action_links( $links ) {
737 if ( array_key_exists( 'deactivate', $links ) ) {
738 $links['deactivate'] = str_replace( '<a', '<a class="' . $this->client->slug . '-deactivate-link"', $links['deactivate'] );
739 }
740
741 return $links;
742 }
743
744 /**
745 * Plugin uninstall reasons
746 *
747 * @return array
748 */
749 private function get_uninstall_reasons() {
750 $reasons = [
751 [
752 'id' => 'could-not-understand',
753 'text' => $this->client->__trans( "Couldn't understand" ),
754 'placeholder' => $this->client->__trans( 'Would you like us to assist you?' ),
755 'icon' => '<svg xmlns="http://www.w3.org/2000/svg" width="23" height="23" viewBox="0 0 23 23"><g fill="none"><g fill="#3B86FF"><path d="M11.5 0C17.9 0 23 5.1 23 11.5 23 17.9 17.9 23 11.5 23 10.6 23 9.6 22.9 8.8 22.7L8.8 22.6C9.3 22.5 9.7 22.3 10 21.9 10.3 21.6 10.4 21.3 10.4 20.9 10.8 21 11.1 21 11.5 21 16.7 21 21 16.7 21 11.5 21 6.3 16.7 2 11.5 2 6.3 2 2 6.3 2 11.5 2 13 2.3 14.3 2.9 15.6 2.7 16 2.4 16.3 2.2 16.8L2.1 17.1 2.1 17.3C2 17.5 2 17.7 2 18 0.7 16.1 0 13.9 0 11.5 0 5.1 5.1 0 11.5 0ZM6 13.6C6 13.7 6.1 13.8 6.1 13.9 6.3 14.5 6.2 15.7 6.1 16.4 6.1 16.6 6 16.9 6 17.1 6 17.1 6.1 17.1 6.1 17.1 7.1 16.9 8.2 16 9.3 15.5 9.8 15.2 10.4 15 10.9 15 11.2 15 11.4 15 11.6 15.2 11.9 15.4 12.1 16 11.6 16.4 11.5 16.5 11.3 16.6 11.1 16.7 10.5 17 9.9 17.4 9.3 17.7 9 17.9 9 18.1 9.1 18.5 9.2 18.9 9.3 19.4 9.3 19.8 9.4 20.3 9.3 20.8 9 21.2 8.8 21.5 8.5 21.6 8.1 21.7 7.9 21.8 7.6 21.9 7.3 21.9L6.5 22C6.3 22 6 21.9 5.8 21.9 5 21.8 4.4 21.5 3.9 20.9 3.3 20.4 3.1 19.6 3 18.8L3 18.5C3 18.2 3 17.9 3.1 17.7L3.1 17.6C3.2 17.1 3.5 16.7 3.7 16.3 4 15.9 4.2 15.4 4.3 15 4.4 14.6 4.4 14.5 4.6 14.2 4.6 13.9 4.7 13.7 4.9 13.6 5.2 13.2 5.7 13.2 6 13.6ZM11.7 11.2C13.1 11.2 14.3 11.7 15.2 12.9 15.3 13 15.4 13.1 15.4 13.2 15.4 13.4 15.3 13.8 15.2 13.8 15 13.9 14.9 13.8 14.8 13.7 14.6 13.5 14.4 13.2 14.1 13.1 13.5 12.6 12.8 12.3 12 12.2 10.7 12.1 9.5 12.3 8.4 12.8 8.3 12.8 8.2 12.8 8.1 12.8 7.9 12.8 7.8 12.4 7.8 12.2 7.7 12.1 7.8 11.9 8 11.8 8.4 11.7 8.8 11.5 9.2 11.4 10 11.2 10.9 11.1 11.7 11.2ZM16.3 5.9C17.3 5.9 18 6.6 18 7.6 18 8.5 17.3 9.3 16.3 9.3 15.4 9.3 14.7 8.5 14.7 7.6 14.7 6.6 15.4 5.9 16.3 5.9ZM8.3 5C9.2 5 9.9 5.8 9.9 6.7 9.9 7.7 9.2 8.4 8.2 8.4 7.3 8.4 6.6 7.7 6.6 6.7 6.6 5.8 7.3 5 8.3 5Z"/></g></g></svg>',
756 ],
757 [
758 'id' => 'found-better-plugin',
759 'text' => $this->client->__trans( 'Found a better plugin' ),
760 'placeholder' => $this->client->__trans( 'Which plugin?' ),
761 'icon' => '<svg xmlns="http://www.w3.org/2000/svg" width="23" height="23" viewBox="0 0 23 23"><g fill="none"><g fill="#3B86FF"><path d="M17.1 14L22.4 19.3C23.2 20.2 23.2 21.5 22.4 22.4 21.5 23.2 20.2 23.2 19.3 22.4L19.3 22.4 14 17.1C15.3 16.3 16.3 15.3 17.1 14L17.1 14ZM8.6 0C13.4 0 17.3 3.9 17.3 8.6 17.3 13.4 13.4 17.2 8.6 17.2 3.9 17.2 0 13.4 0 8.6 0 3.9 3.9 0 8.6 0ZM8.6 2.2C5.1 2.2 2.2 5.1 2.2 8.6 2.2 12.2 5.1 15.1 8.6 15.1 12.2 15.1 15.1 12.2 15.1 8.6 15.1 5.1 12.2 2.2 8.6 2.2ZM8.6 3.6L8.6 5C6.6 5 5 6.6 5 8.6L5 8.6 3.6 8.6C3.6 5.9 5.9 3.6 8.6 3.6L8.6 3.6Z"/></g></g></svg>',
762 ],
763 [
764 'id' => 'not-have-that-feature',
765 'text' => $this->client->__trans( 'Missing a specific feature' ),
766 'placeholder' => $this->client->__trans( 'Could you tell us more about that feature?' ),
767 'icon' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="17" viewBox="0 0 24 17"><g fill="none"><g fill="#3B86FF"><path d="M19.4 0C19.7 0.6 19.8 1.3 19.8 2 19.8 3.2 19.4 4.4 18.5 5.3 17.6 6.2 16.5 6.7 15.2 6.7 15.2 6.7 15.2 6.7 15.2 6.7 14 6.7 12.9 6.2 12 5.3 11.2 4.4 10.7 3.3 10.7 2 10.7 1.3 10.8 0.6 11.1 0L7.6 0 7 0 6.5 0 6.5 5.7C6.3 5.6 5.9 5.3 5.6 5.1 5 4.6 4.3 4.3 3.5 4.3 3.5 4.3 3.5 4.3 3.4 4.3 1.6 4.4 0 5.9 0 7.9 0 8.6 0.2 9.2 0.5 9.7 1.1 10.8 2.2 11.5 3.5 11.5 4.3 11.5 5 11.2 5.6 10.8 6 10.5 6.3 10.3 6.5 10.2L6.5 10.2 6.5 17 6.5 17 7 17 7.6 17 22.5 17C23.3 17 24 16.3 24 15.5L24 0 19.4 0Z"/></g></g></svg>',
768 ],
769 [
770 'id' => 'is-not-working',
771 'text' => $this->client->__trans( 'Not working' ),
772 'placeholder' => $this->client->__trans( 'Could you tell us a bit more whats not working?' ),
773 'icon' => '<svg xmlns="http://www.w3.org/2000/svg" width="23" height="23" viewBox="0 0 23 23"><g fill="none"><g fill="#3B86FF"><path d="M11.5 0C17.9 0 23 5.1 23 11.5 23 17.9 17.9 23 11.5 23 5.1 23 0 17.9 0 11.5 0 5.1 5.1 0 11.5 0ZM11.8 14.4C11.2 14.4 10.7 14.8 10.7 15.4 10.7 16 11.2 16.4 11.8 16.4 12.4 16.4 12.8 16 12.8 15.4 12.8 14.8 12.4 14.4 11.8 14.4ZM12 7C10.1 7 9.1 8.1 9 9.6L10.5 9.6C10.5 8.8 11.1 8.3 11.9 8.3 12.7 8.3 13.2 8.8 13.2 9.5 13.2 10.1 13 10.4 12.2 10.9 11.3 11.4 10.9 12 11 12.9L11 13.4 12.5 13.4 12.5 13C12.5 12.4 12.7 12.1 13.5 11.6 14.4 11.1 14.9 10.4 14.9 9.4 14.9 8 13.7 7 12 7Z"/></g></g></svg>',
774 ],
775 [
776 'id' => 'looking-for-other',
777 'text' => $this->client->__trans( 'Not what I was looking' ),
778 'placeholder' => $this->client->__trans( 'Could you tell us a bit more?' ),
779 'icon' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="17" viewBox="0 0 24 17"><g fill="none"><g fill="#3B86FF"><path d="M23.5 9C23.5 9 23.5 8.9 23.5 8.9 23.5 8.9 23.5 8.9 23.5 8.9 23.4 8.6 23.2 8.3 23 8 22.2 6.5 20.6 3.7 19.8 2.6 18.8 1.3 17.7 0 16.1 0 15.7 0 15.3 0.1 14.9 0.2 13.8 0.6 12.6 1.2 12.3 2.7L11.7 2.7C11.4 1.2 10.2 0.6 9.1 0.2 8.7 0.1 8.3 0 7.9 0 6.3 0 5.2 1.3 4.2 2.6 3.4 3.7 1.8 6.5 1 8 0.8 8.3 0.6 8.6 0.5 8.9 0.5 8.9 0.5 8.9 0.5 8.9 0.5 8.9 0.5 9 0.5 9 0.2 9.7 0 10.5 0 11.3 0 14.4 2.5 17 5.5 17 7.3 17 8.8 16.1 9.8 14.8L14.2 14.8C15.2 16.1 16.7 17 18.5 17 21.5 17 24 14.4 24 11.3 24 10.5 23.8 9.7 23.5 9ZM5.5 15C3.6 15 2 13.2 2 11 2 8.8 3.6 7 5.5 7 7.4 7 9 8.8 9 11 9 13.2 7.4 15 5.5 15ZM18.5 15C16.6 15 15 13.2 15 11 15 8.8 16.6 7 18.5 7 20.4 7 22 8.8 22 11 22 13.2 20.4 15 18.5 15Z"/></g></g></svg>',
780 ],
781 [
782 'id' => 'did-not-work-as-expected',
783 'text' => $this->client->__trans( "Didn't work as expected" ),
784 'placeholder' => $this->client->__trans( 'What did you expect?' ),
785 'icon' => '<svg xmlns="http://www.w3.org/2000/svg" width="23" height="23" viewBox="0 0 23 23"><g fill="none"><g fill="#3B86FF"><path d="M11.5 0C17.9 0 23 5.1 23 11.5 23 17.9 17.9 23 11.5 23 5.1 23 0 17.9 0 11.5 0 5.1 5.1 0 11.5 0ZM11.5 2C6.3 2 2 6.3 2 11.5 2 16.7 6.3 21 11.5 21 16.7 21 21 16.7 21 11.5 21 6.3 16.7 2 11.5 2ZM12.5 12.9L12.7 5 10.2 5 10.5 12.9 12.5 12.9ZM11.5 17.4C12.4 17.4 13 16.8 13 15.9 13 15 12.4 14.4 11.5 14.4 10.6 14.4 10 15 10 15.9 10 16.8 10.6 17.4 11.5 17.4Z"/></g></g></svg>',
786 ],
787 [
788 'id' => 'other',
789 'text' => $this->client->__trans( 'Others' ),
790 'placeholder' => $this->client->__trans( 'Could you tell us a bit more?' ),
791 'icon' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="23" viewBox="0 0 24 6"><g fill="none"><g fill="#3B86FF"><path d="M3 0C4.7 0 6 1.3 6 3 6 4.7 4.7 6 3 6 1.3 6 0 4.7 0 3 0 1.3 1.3 0 3 0ZM12 0C13.7 0 15 1.3 15 3 15 4.7 13.7 6 12 6 10.3 6 9 4.7 9 3 9 1.3 10.3 0 12 0ZM21 0C22.7 0 24 1.3 24 3 24 4.7 22.7 6 21 6 19.3 6 18 4.7 18 3 18 1.3 19.3 0 21 0Z"/></g></g></svg>',
792 ],
793 ];
794
795 return $reasons;
796 }
797
798 /**
799 * Plugin deactivation uninstall reason submission
800 *
801 * @return void
802 */
803 public function uninstall_reason_submission() {
804 if ( ! isset( $_POST['nonce'] ) ) {
805 return;
806 }
807
808 if ( ! isset( $_POST['reason_id'] ) ) {
809 wp_send_json_error();
810 }
811
812 if ( ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['nonce'] ) ), 'appsero-security-nonce' ) ) {
813 wp_send_json_error( 'Nonce verification failed' );
814 }
815
816 if ( ! current_user_can( 'manage_options' ) ) {
817 wp_send_json_error( 'You are not allowed for this task' );
818 }
819
820 $data['reason_id'] = sanitize_text_field( wp_unslash( $_POST['reason_id'] ) );
821 $data['reason_info'] = isset( $_REQUEST['reason_info'] ) ? trim( sanitize_text_field( wp_unslash( $_REQUEST['reason_info'] ) ) ) : '';
822
823 $this->client->send_request( $data, 'deactivate' );
824
825 /*
826 * Fire after the plugin _uninstall_reason_submitted
827 */
828 do_action( $this->client->slug . '_uninstall_reason_submitted', $data );
829
830 wp_send_json_success();
831 }
832
833 /**
834 * Handle the plugin deactivation feedback
835 *
836 * @return void
837 */
838 public function deactivate_scripts() {
839 global $pagenow;
840
841 if ( 'plugins.php' !== $pagenow ) {
842 return;
843 }
844
845 $this->deactivation_modal_styles();
846 $reasons = $this->get_uninstall_reasons();
847 $custom_reasons = apply_filters( 'appsero_custom_deactivation_reasons', [], $this->client );
848 ?>
849
850 <div class="wd-dr-modal" id="<?php echo $this->client->slug; ?>-wd-dr-modal">
851 <div class="wd-dr-modal-wrap">
852 <div class="wd-dr-modal-header">
853 <h3><?php $this->client->_etrans( 'Goodbyes are always hard. If you have a moment, please let us know how we can improve.' ); ?></h3>
854 </div>
855
856 <div class="wd-dr-modal-body">
857 <ul class="wd-de-reasons">
858 <?php foreach ( $reasons as $reason ) { ?>
859 <li data-placeholder="<?php echo esc_attr( $reason['placeholder'] ); ?>">
860 <label>
861 <input type="radio" name="selected-reason" value="<?php echo $reason['id']; ?>">
862 <div class="wd-de-reason-icon"><?php echo $reason['icon']; ?></div>
863 <div class="wd-de-reason-text"><?php echo $reason['text']; ?></div>
864 </label>
865 </li>
866 <?php } ?>
867 </ul>
868 <?php if ( $custom_reasons && is_array( $custom_reasons ) ) { ?>
869 <ul class="wd-de-reasons wd-de-others-reasons">
870 <?php foreach ( $custom_reasons as $reason ) { ?>
871 <li data-placeholder="<?php echo esc_attr( $reason['placeholder'] ); ?>" data-customreason="true">
872 <label>
873 <input type="radio" name="selected-reason" value="<?php echo $reason['id']; ?>">
874 <div class="wd-de-reason-icon"><?php echo $reason['icon']; ?></div>
875 <div class="wd-de-reason-text"><?php echo $reason['text']; ?></div>
876 </label>
877 </li>
878 <?php } ?>
879 </ul>
880 <?php } ?>
881 <div class="wd-dr-modal-reason-input"><textarea></textarea></div>
882 <p class="wd-dr-modal-reasons-bottom">
883 <?php
884 echo sprintf(
885 $this->client->__trans( 'We share your data with <a href="%1$s" target="_blank">Appsero</a> to troubleshoot problems &amp; make product improvements. <a href="%2$s" target="_blank">Learn more</a> about how Appsero handles your data.' ),
886 esc_url( 'https://appsero.com/' ),
887 esc_url( 'https://appsero.com/privacy-policy' )
888 );
889 ?>
890 </p>
891 </div>
892
893 <div class="wd-dr-modal-footer">
894 <a href="#" class="dont-bother-me wd-dr-button-secondary"><?php $this->client->_etrans( 'Skip & Deactivate' ); ?></a>
895 <button class="wd-dr-button-secondary wd-dr-cancel-modal"><?php $this->client->_etrans( 'Cancel' ); ?></button>
896 <button class="wd-dr-submit-modal"><?php $this->client->_etrans( 'Submit & Deactivate' ); ?></button>
897 </div>
898 </div>
899 </div>
900
901 <script type="text/javascript">
902 (function($) {
903 $(function() {
904 var modal = $( '#<?php echo $this->client->slug; ?>-wd-dr-modal' );
905 var deactivateLink = '';
906
907 // Open modal
908 $( '#the-list' ).on('click', 'a.<?php echo $this->client->slug; ?>-deactivate-link', function(e) {
909 e.preventDefault();
910
911 modal.addClass('modal-active');
912 deactivateLink = $(this).attr('href');
913 modal.find('a.dont-bother-me').attr('href', deactivateLink).css('float', 'left');
914 });
915
916 // Close modal; Cancel
917 modal.on('click', 'button.wd-dr-cancel-modal', function(e) {
918 e.preventDefault();
919 modal.removeClass('modal-active');
920 });
921
922 // Reason change
923 modal.on('click', 'input[type="radio"]', function () {
924 var parent = $(this).parents('li');
925 var isCustomReason = parent.data('customreason');
926 var inputValue = $(this).val();
927
928 if ( isCustomReason ) {
929 $('ul.wd-de-reasons.wd-de-others-reasons li').removeClass('wd-de-reason-selected');
930 } else {
931 $('ul.wd-de-reasons li').removeClass('wd-de-reason-selected');
932
933 if ( "other" != inputValue ) {
934 $('ul.wd-de-reasons.wd-de-others-reasons').css('display', 'none');
935 }
936 }
937
938 // Show if has custom reasons
939 if ( "other" == inputValue ) {
940 $('ul.wd-de-reasons.wd-de-others-reasons').css('display', 'flex');
941 }
942
943 parent.addClass('wd-de-reason-selected');
944 $('.wd-dr-modal-reason-input').show();
945
946 $('.wd-dr-modal-reason-input textarea').attr('placeholder', parent.data('placeholder')).focus();
947 });
948
949 // Submit response
950 modal.on('click', 'button.wd-dr-submit-modal', function(e) {
951 e.preventDefault();
952
953 var button = $(this);
954
955 if ( button.hasClass('disabled') ) {
956 return;
957 }
958
959 var $radio = $( 'input[type="radio"]:checked', modal );
960 var $input = $('.wd-dr-modal-reason-input textarea');
961
962 $.ajax({
963 url: ajaxurl,
964 type: 'POST',
965 data: {
966 nonce: '<?php echo wp_create_nonce( 'appsero-security-nonce' ); ?>',
967 action: '<?php echo $this->client->slug; ?>_submit-uninstall-reason',
968 reason_id: ( 0 === $radio.length ) ? 'none' : $radio.val(),
969 reason_info: ( 0 !== $input.length ) ? $input.val().trim() : ''
970 },
971 beforeSend: function() {
972 button.addClass('disabled');
973 button.text('Processing...');
974 },
975 complete: function() {
976 window.location.href = deactivateLink;
977 }
978 });
979 });
980 });
981 }(jQuery));
982 </script>
983
984 <?php
985 }
986
987 /**
988 * Run after theme deactivated
989 *
990 * @param string $new_name
991 * @param object $new_theme
992 * @param object $old_theme
993 *
994 * @return void
995 */
996 public function theme_deactivated( $new_name, $new_theme, $old_theme ) {
997 // Make sure this is appsero theme
998 if ( $old_theme->get_template() === $this->client->slug ) {
999 $this->client->send_request( $this->get_tracking_data(), 'deactivate' );
1000 }
1001 }
1002
1003 /**
1004 * Get user IP Address
1005 */
1006 private function get_user_ip_address() {
1007 $response = wp_remote_get( 'https://icanhazip.com/' );
1008
1009 if ( is_wp_error( $response ) ) {
1010 return '';
1011 }
1012
1013 $ip = trim( wp_remote_retrieve_body( $response ) );
1014
1015 if ( ! filter_var( $ip, FILTER_VALIDATE_IP ) ) {
1016 return '';
1017 }
1018
1019 return $ip;
1020 }
1021
1022 /**
1023 * Get site name
1024 */
1025 private function get_site_name() {
1026 $site_name = get_bloginfo( 'name' );
1027
1028 if ( empty( $site_name ) ) {
1029 $site_name = get_bloginfo( 'description' );
1030 $site_name = wp_trim_words( $site_name, 3, '' );
1031 }
1032
1033 if ( empty( $site_name ) ) {
1034 $site_name = esc_url( home_url() );
1035 }
1036
1037 return $site_name;
1038 }
1039
1040 /**
1041 * Send request to appsero if user skip to send tracking data
1042 */
1043 private function send_tracking_skipped_request() {
1044 $skipped = get_option( $this->client->slug . '_tracking_skipped' );
1045
1046 $data = [
1047 'hash' => $this->client->hash,
1048 'previously_skipped' => false,
1049 ];
1050
1051 if ( $skipped === 'yes' ) {
1052 $data['previously_skipped'] = true;
1053 } else {
1054 update_option( $this->client->slug . '_tracking_skipped', 'yes' );
1055 }
1056
1057 $this->client->send_request( $data, 'tracking-skipped' );
1058 }
1059
1060 /**
1061 * Deactivation modal styles
1062 */
1063 private function deactivation_modal_styles() {
1064 ?>
1065 <style type="text/css">
1066 .wd-dr-modal {
1067 position: fixed;
1068 z-index: 99999;
1069 top: 0;
1070 right: 0;
1071 bottom: 0;
1072 left: 0;
1073 background: rgba(0,0,0,0.5);
1074 display: none;
1075 box-sizing: border-box;
1076 overflow: scroll;
1077 }
1078 .wd-dr-modal * {
1079 box-sizing: border-box;
1080 }
1081 .wd-dr-modal.modal-active {
1082 display: block;
1083 }
1084 .wd-dr-modal-wrap {
1085 max-width: 870px;
1086 width: 100%;
1087 position: relative;
1088 margin: 10% auto;
1089 background: #fff;
1090 }
1091 .wd-dr-modal-header {
1092 border-bottom: 1px solid #E8E8E8;
1093 padding: 20px 20px 18px 20px;
1094 }
1095 .wd-dr-modal-header h3 {
1096 line-height: 1.8;
1097 margin: 0;
1098 color: #4A5568;
1099 }
1100 .wd-dr-modal-body {
1101 padding: 5px 20px 20px 20px;
1102 }
1103 .wd-dr-modal-body .reason-input {
1104 margin-top: 5px;
1105 margin-left: 20px;
1106 }
1107 .wd-dr-modal-footer {
1108 border-top: 1px solid #E8E8E8;
1109 padding: 20px;
1110 text-align: right;
1111 }
1112 .wd-dr-modal-reasons-bottom {
1113 margin: 0;
1114 }
1115 ul.wd-de-reasons {
1116 display: flex;
1117 margin: 0 -5px 0 -5px;
1118 padding: 15px 0 20px 0;
1119 }
1120 ul.wd-de-reasons.wd-de-others-reasons {
1121 padding-top: 0;
1122 display: none;
1123 }
1124 ul.wd-de-reasons li {
1125 padding: 0 5px;
1126 margin: 0;
1127 width: 14.26%;
1128 }
1129 ul.wd-de-reasons label {
1130 position: relative;
1131 border: 1px solid #E8E8E8;
1132 border-radius: 4px;
1133 display: block;
1134 text-align: center;
1135 height: 100%;
1136 padding: 15px 3px 8px 3px;
1137 }
1138 ul.wd-de-reasons label:after {
1139 width: 0;
1140 height: 0;
1141 border-left: 8px solid transparent;
1142 border-right: 8px solid transparent;
1143 border-top: 10px solid #3B86FF;
1144 position: absolute;
1145 left: 50%;
1146 top: 100%;
1147 margin-left: -8px;
1148 }
1149 ul.wd-de-reasons label input[type="radio"] {
1150 position: absolute;
1151 left: 0;
1152 right: 0;
1153 visibility: hidden;
1154 }
1155 .wd-de-reason-text {
1156 color: #4A5568;
1157 font-size: 13px;
1158 }
1159 .wd-de-reason-icon {
1160 margin-bottom: 7px;
1161 }
1162 ul.wd-de-reasons li.wd-de-reason-selected label {
1163 background-color: #3B86FF;
1164 border-color: #3B86FF;
1165 }
1166 li.wd-de-reason-selected .wd-de-reason-icon svg,
1167 li.wd-de-reason-selected .wd-de-reason-icon svg g {
1168 fill: #fff;
1169 }
1170 li.wd-de-reason-selected .wd-de-reason-text {
1171 color: #fff;
1172 }
1173 ul.wd-de-reasons li.wd-de-reason-selected label:after {
1174 content: "";
1175 }
1176 .wd-dr-modal-reason-input {
1177 margin-bottom: 15px;
1178 display: none;
1179 }
1180 .wd-dr-modal-reason-input textarea {
1181 background: #FAFAFA;
1182 border: 1px solid #287EB8;
1183 border-radius: 4px;
1184 width: 100%;
1185 height: 100px;
1186 color: #524242;
1187 font-size: 13px;
1188 line-height: 1.4;
1189 padding: 11px 15px;
1190 resize: none;
1191 }
1192 .wd-dr-modal-reason-input textarea:focus {
1193 outline: 0 none;
1194 box-shadow: 0 0 0;
1195 }
1196 .wd-dr-button-secondary, .wd-dr-button-secondary:hover {
1197 border: 1px solid #EBEBEB;
1198 border-radius: 3px;
1199 font-size: 13px;
1200 line-height: 1.5;
1201 color: #718096;
1202 padding: 5px 12px;
1203 cursor: pointer;
1204 background-color: transparent;
1205 text-decoration: none;
1206 }
1207 .wd-dr-submit-modal, .wd-dr-submit-modal:hover {
1208 border: 1px solid #3B86FF;
1209 background-color: #3B86FF;
1210 border-radius: 3px;
1211 font-size: 13px;
1212 line-height: 1.5;
1213 color: #fff;
1214 padding: 5px 12px;
1215 cursor: pointer;
1216 margin-left: 4px;
1217 }
1218 </style>
1219 <?php
1220 }
1221 }
1222