PluginProbe
Easy Hotel – Powerful Hotel Booking / 2.0.2
Easy Hotel – Powerful Hotel Booking v2.0.2
2.0.8 2.0.7 2.0.6 2.0.5 2.0.4 2.0.3 2.0.2 2.0.1 2.0.0 1.9.9 1.9.8 1.9.7 1.9.6 1.9.5 1.9.4 1.9.3 1.9.2 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 All 110 releases
easy-hotel / admin / includes / opt-in / Insights.php

Insights.php in Easy Hotel – Powerful Hotel Booking 2.0.2, at admin/includes/opt-in/Insights.php

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