PluginProbe
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress / 8.5.4
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress v8.5.4
9.1.2 9.1.1 9.1.0 9.0.3 9.0.2 9.0.1 9.0.0 8.5.79 8.5.78 8.5.77 8.5.76 8.5.75 8.5.74 8.5.73 8.5.72 8.5.71 8.5.70 8.5.69 8.5.68 8.5.35 8.5.36 8.5.37 8.5.38 8.5.39 8.5.4 All 221 releases
wpvr / appsero / src / Insights.php

Insights.php in WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress 8.5.4, at appsero/src/Insights.php

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