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

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

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