PluginProbe
Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) / 1.5.5
Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) v1.5.5
2.1.3 2.1.2 2.1.1 2.1.0 2.0.4 2.0.3 2.0.2 2.0.1 2.0.0 1.5.5 1.5.4 1.5.3 1.5.2 1.5.1 1.5.0 trunk 1.0.1 1.1.0 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 All 57 releases
darkify / src / Admin / appsero / Insights.php

Insights.php in Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) 1.5.5, at src/Admin/appsero/Insights.php

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