PluginProbe
Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) / 2.0.4
Darkify – Dark Mode & Night Mode for Website & Admin (Dark Theme Included) v2.0.4
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) 2.0.4, at src/Admin/appsero/Insights.php

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