PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.2
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.2
4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 All 200 releases
betterdocs / includes / Insights / Insights.php

Insights.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.9.2, at includes/Insights/Insights.php

1,228 lines 54.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace WPDeveloper\BetterDocs\Insights;
3
4 use AllowDynamicProperties;
5
6 /**
7 * Exit if accessed directly
8 */
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12
13 #[AllowDynamicProperties ]
14
15 class Insights {
16 /**
17 * WP Insights Version
18 */
19 const WPINS_VERSION = '3.0.3';
20 /**
21 * API URL
22 */
23 const API_URL = 'https://send.wpinsight.com/process-plugin-data';
24 /**
25 * Installed Plugin File
26 *
27 * @var string
28 */
29 private $plugin_file = null;
30 /**
31 * Installed Plugin Name
32 *
33 * @var string
34 */
35 private $plugin_name = null;
36 /**
37 * How often the event should subsequently
38 *
39 * @var string
40 */
41 public $recurrence = 'daily';
42 private $event_hook = null;
43 private $has_notice = false;
44 private $disabled_wp_cron = false;
45 private $enable_self_cron = false;
46 private $require_optin = false;
47 private $include_goodbye_form = false;
48 private $marketing = false;
49 private $options = array();
50 private $notice_options = array();
51 private $item_id = false;
52 /**
53 * Instace of BetterDocs_Plugin_Usage_Tracker
54 *
55 * @var BetterDocs_Plugin_Usage_Tracker
56 */
57 private static $_instance = null;
58 /**
59 * Get Instance of BetterDocs_Plugin_Usage_Tracker
60 *
61 * @return BetterDocs_Plugin_Usage_Tracker
62 */
63 public static function get_instance( $plugin_file, $args = array() ) {
64 if ( is_null( static::$_instance ) ) {
65 static::$_instance = new static( $plugin_file, $args );
66 }
67 return static::$_instance;
68 }
69
70 /**
71 * Automatically Invoked when initialized.
72 *
73 * @param array $args
74 */
75 public function __construct( $plugin_file, $args = array() ) {
76 $this->plugin_file = $plugin_file;
77 $this->plugin_name = basename( $this->plugin_file, '.php' );
78 $this->disabled_wp_cron = defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON == true;
79 $this->enable_self_cron = true == $this->disabled_wp_cron ? true : false;
80
81 $this->event_hook = 'put_do_weekly_action';
82
83 $this->require_optin = isset( $args[ 'opt_in' ] ) ? $args[ 'opt_in' ] : true;
84 $this->include_goodbye_form = isset( $args[ 'goodbye_form' ] ) ? $args[ 'goodbye_form' ] : true;
85 $this->marketing = isset( $args[ 'email_marketing' ] ) ? $args[ 'email_marketing' ] : true;
86 $this->options = isset( $args[ 'options' ] ) ? $args[ 'options' ] : array();
87 $this->item_id = isset( $args[ 'item_id' ] ) ? $args[ 'item_id' ] : false;
88 /**
89 * Activation Hook
90 */
91 register_activation_hook( $this->plugin_file, array( $this, 'activate_this_plugin' ) );
92 /**
93 * Deactivation Hook
94 */
95 register_deactivation_hook( $this->plugin_file, array( $this, 'deactivate_this_plugin' ) );
96 }
97
98 /**
99 * When user agreed to opt-in tracking schedule is enabled.
100 *
101 * @since 2.5.0
102 */
103 public function schedule_tracking() {
104 if ( $this->disabled_wp_cron ) {
105 return;
106 }
107 if ( ! wp_next_scheduled( $this->event_hook ) ) {
108 wp_schedule_event( time(), $this->recurrence, $this->event_hook );
109 }
110 }
111
112 /**
113 * Add the schedule event if the plugin is tracked.
114 *
115 * @return void
116 */
117 public function activate_this_plugin() {
118 $allow_tracking = $this->is_tracking_allowed();
119 if ( ! $allow_tracking ) {
120 return;
121 }
122 $this->schedule_tracking();
123 }
124
125 /**
126 * Remove the schedule event when plugin is deactivated and send the deactivated reason to inishghts if user submitted.
127 *
128 * @since 2.5.0
129 */
130 public function deactivate_this_plugin() {
131 /**
132 * Check tracking is allowed or not.
133 */
134 $allow_tracking = $this->is_tracking_allowed();
135 if ( ! $allow_tracking ) {
136 return;
137 }
138 $body = $this->get_data();
139 $body[ 'status' ] = 'Deactivated';
140 $body[ 'deactivated_date' ] = time();
141
142 // Check deactivation reason and add for insights data.
143 if ( false !== get_option( 'wpins_deactivation_reason_' . $this->plugin_name ) ) {
144 $body[ 'deactivation_reason' ] = get_option( 'wpins_deactivation_reason_' . $this->plugin_name );
145 }
146 if ( false !== get_option( 'wpins_deactivation_details_' . $this->plugin_name ) ) {
147 $body[ 'deactivation_details' ] = get_option( 'wpins_deactivation_details_' . $this->plugin_name );
148 }
149
150 $this->send_data( $body );
151 delete_option( 'wpins_deactivation_reason_' . $this->plugin_name );
152 delete_option( 'wpins_deactivation_details_' . $this->plugin_name );
153 /**
154 * Clear the event schedule.
155 */
156 if ( ! $this->disabled_wp_cron ) {
157 wp_clear_scheduled_hook( $this->event_hook );
158 }
159 }
160
161 /**
162 * Initial Method to Hook Everything.
163 *
164 * @return void
165 */
166 public function init() {
167 add_action( 'wpdeveloper_notice_clicked_for_' . $this->plugin_name, array( $this, 'clicked' ) );
168 add_action( $this->event_hook, array( $this, 'do_tracking' ) );
169 /**
170 * Self-heal the schedule. A file-only plugin update does NOT fire
171 * register_activation_hook(), so already-opted-in sites whose cron was
172 * never scheduled (or was lost) would otherwise never report again. Since
173 * init() runs on every request, re-ensure the event whenever tracking is
174 * allowed. schedule_tracking() guards with wp_next_scheduled(), so this is
175 * idempotent and respects DISABLE_WP_CRON.
176 */
177 if ( $this->is_tracking_allowed() ) {
178 $this->schedule_tracking();
179 }
180 // For Test
181 add_action( 'wpdeveloper_optin_notice_for_' . $this->plugin_name, array( $this, 'notice' ) );
182 /**
183 * Deactivation Reason Form and Submit Data to Insights.
184 */
185 if ( ! is_plugin_active( 'betterdocs-pro/betterdocs-pro.php' ) ) {
186 add_filter( 'plugin_action_links_' . plugin_basename( $this->plugin_file ), array( $this, 'deactivate_action_links' ) );
187 add_action( 'admin_print_footer_scripts', array( $this, 'notice_script' ) );
188 add_action( 'admin_footer-plugins.php', array( $this, 'deactivate_reasons_form' ) );
189 add_action( 'wp_ajax_deactivation_form_' . esc_attr( $this->plugin_name ), array( $this, 'deactivate_reasons_form_submit' ) );
190 }
191 }
192
193 /**
194 * For Redirecting Current Page without Arguments!
195 *
196 * @return string
197 */
198 private function redirect_to() {
199 $request_payload = isset( $_SERVER[ 'REQUEST_URI' ] ) ? esc_url_raw( wp_unslash( $_SERVER[ 'REQUEST_URI' ] ) ) : '';
200 $request_uri = wp_parse_url( $request_payload, PHP_URL_PATH );
201 $query_string = wp_parse_url( $request_payload, PHP_URL_QUERY );
202 parse_str( $query_string, $current_url );
203
204 $unset_array = array( 'dismiss', 'plugin', '_wpnonce', 'later', 'plugin_action', 'marketing_optin' );
205
206 foreach ( $unset_array as $value ) {
207 if ( isset( $current_url[ $value ] ) ) {
208 unset( $current_url[ $value ] );
209 }
210 }
211
212 $current_url = http_build_query( $current_url );
213 $redirect_url = $request_uri . '?' . $current_url;
214 return $redirect_url;
215 }
216
217 /**
218 * This method forcing the do_tracking method to execute instant.
219 *
220 * @return void
221 */
222 public function force_tracking() {
223 $this->do_tracking( true );
224 }
225
226 /**
227 * This method is responsible for all the magic from the front of the plugin.
228 *
229 * @since 2.5.0
230 * @param $force Force tracking if it's not the correct time to track/
231 */
232 public function do_tracking( $force = false ) {
233 /**
234 * Check URL is set or not.
235 */
236 if ( empty( self::API_URL ) ) {
237 return;
238 }
239 /**
240 * Check is tracking allowed or not.
241 */
242 if ( ! $this->is_tracking_allowed() ) {
243 return;
244 }
245 /**
246 * Check is this the correct time to track or not.
247 * or Force to track.
248 */
249 if ( ! $this->is_time_to_track() && ! $force ) {
250 return;
251 }
252 /**
253 * Get All Data.
254 */
255 $body = $this->get_data();
256 /**
257 * Send all data.
258 */
259 return $this->send_data( $body );
260 }
261
262 /**
263 * Is tracking allowed?
264 *
265 * @since 1.0.0
266 */
267 private function is_tracking_allowed() {
268 // First, check if the user has changed their mind and opted out of tracking
269 if ( $this->has_user_opted_out() ) {
270 $this->set_is_tracking_allowed( false, $this->plugin_name );
271 return false;
272 }
273 // The wpins_allow_tracking option is an array of plugins that are being tracked
274 $allow_tracking = get_option( 'wpins_allow_tracking' );
275 // If this plugin is in the array, then tracking is allowed
276 if ( isset( $allow_tracking[ $this->plugin_name ] ) ) {
277 return true;
278 }
279 return false;
280 }
281
282 /**
283 * Set a flag in DB If tracking is allowed.
284 *
285 * @since 2.5.0
286 * @param $is_allowed Boolean true if is allowed.
287 */
288 public function set_is_tracking_allowed( $is_allowed, $plugin = null ) {
289 if ( empty( $plugin ) ) {
290 $plugin = $this->plugin_name;
291 }
292 /**
293 * Get All Tracked Plugin List using this Tracker.
294 */
295 $allow_tracking = get_option( 'wpins_allow_tracking' );
296 /**
297 * Check user is opted out for tracking or not.
298 */
299 if ( $this->has_user_opted_out() ) {
300 if ( isset( $allow_tracking[ $plugin ] ) ) {
301 unset( $allow_tracking[ $plugin ] );
302 }
303 } elseif ( $is_allowed || ! $this->require_optin ) {
304 /**
305 * If user has agreed to allow tracking
306 */
307 if ( empty( $allow_tracking ) || ! is_array( $allow_tracking ) ) {
308 $allow_tracking = array( $plugin => $plugin );
309 } else {
310 $allow_tracking[ $plugin ] = $plugin;
311 }
312 } elseif ( isset( $allow_tracking[ $plugin ] ) ) {
313 unset( $allow_tracking[ $plugin ] );
314 }
315 update_option( 'wpins_allow_tracking', $allow_tracking, 'no' );
316 }
317
318 /**
319 * Check the user has opted out or not.
320 *
321 * @since 2.5.0
322 * @return boolean
323 */
324 protected function has_user_opted_out() {
325 if ( ! empty( $this->options ) ) {
326 foreach ( $this->options as $option_name ) {
327 $options = get_option( $option_name );
328 if ( ! empty( $options[ 'wpins_opt_out' ] ) ) {
329 return true;
330 }
331 }
332 }
333 return false;
334 }
335
336 /**
337 * Check if it's time to track
338 *
339 * @since 2.5.0
340 */
341 public function is_time_to_track() {
342 $track_times = get_option( 'wpins_last_track_time', array() );
343 return ! isset( $track_times[ $this->plugin_name ] ) ? true :
344 ( ( isset( $track_times[ $this->plugin_name ] ) && $track_times[ $this->plugin_name ] ) < strtotime( '-1 day' ) ? true : false );
345 }
346
347 /**
348 * Set tracking time.
349 *
350 * @since 2.5.0
351 */
352 public function set_track_time() {
353 $track_times = get_option( 'wpins_last_track_time', array() );
354 $track_times[ $this->plugin_name ] = time();
355 update_option( 'wpins_last_track_time', $track_times, 'no' );
356 }
357
358 /**
359 * This method is responsible for collecting all data.
360 *
361 * @since 2.5.0
362 */
363 public function get_data() {
364 $body = array(
365 'plugin_slug' => sanitize_text_field( $this->plugin_name ),
366 'url' => get_bloginfo( 'url' ),
367 'site_name' => get_bloginfo( 'name' ),
368 'site_version' => get_bloginfo( 'version' ),
369 'site_language' => get_bloginfo( 'language' ),
370 'charset' => get_bloginfo( 'charset' ),
371 'wpins_version' => self::WPINS_VERSION,
372 'php_version' => phpversion(),
373 'multisite' => is_multisite(),
374 'file_location' => __FILE__
375 );
376
377 // Collect the email if the correct option has been set
378 if ( $this->marketing ) {
379 if ( ! function_exists( 'wp_get_current_user' ) ) {
380 include ABSPATH . 'wp-includes/pluggable.php';
381 }
382 $current_user = wp_get_current_user();
383 $email = $current_user->user_email;
384 if ( is_email( $email ) ) {
385 $body[ 'email' ] = $email;
386 }
387 }
388 $body[ 'marketing_method' ] = $this->marketing;
389 $body[ 'server' ] = isset( $_SERVER[ 'SERVER_SOFTWARE' ] ) ? $_SERVER[ 'SERVER_SOFTWARE' ] : ''; //phpcs:ignore
390
391 /**
392 * Collect all active and inactive plugins
393 */
394 if ( ! function_exists( 'get_plugins' ) ) {
395 include ABSPATH . '/wp-admin/includes/plugin.php';
396 }
397 $plugins = array_keys( get_plugins() );
398 $active_plugins = is_network_admin() ? array_keys( get_site_option( 'active_sitewide_plugins', array() ) ) : get_option( 'active_plugins', array() );
399 foreach ( $plugins as $key => $plugin ) {
400 if ( in_array( $plugin, $active_plugins ) ) {
401 unset( $plugins[ $key ] );
402 }
403 }
404 $body[ 'active_plugins' ] = $active_plugins;
405 $body[ 'inactive_plugins' ] = $plugins;
406
407 /**
408 * Text Direction.
409 */
410 $body[ 'text_direction' ] = ( function_exists( 'is_rtl' ) ? ( is_rtl() ? 'RTL' : 'LTR' ) : 'NOT SET' );
411 /**
412 * Get Our Plugin Data.
413 *
414 * @since 2.5.0
415 */
416 $plugin = $this->plugin_data();
417 if ( empty( $plugin ) ) {
418 $body[ 'message' ] .= __( 'We can\'t detect any plugin information. This is most probably because you have not included the code in the plugin main file.', 'betterdocs' );
419 $body[ 'status' ] = 'NOT FOUND';
420 } else {
421 if ( isset( $plugin[ 'Name' ] ) ) {
422 $body[ 'plugin' ] = sanitize_text_field( $plugin[ 'Name' ] );
423 }
424 if ( isset( $plugin[ 'Version' ] ) ) {
425 $body[ 'version' ] = sanitize_text_field( $plugin[ 'Version' ] );
426 }
427 $body[ 'status' ] = 'Active';
428 }
429
430 /**
431 * Get active theme name and version
432 *
433 * @since 2.5.0
434 */
435 $theme = wp_get_theme();
436 if ( $theme->Name ) {
437 $body[ 'theme' ] = sanitize_text_field( $theme->Name );
438 }
439 if ( $theme->Version ) {
440 $body[ 'theme_version' ] = sanitize_text_field( $theme->Version );
441 }
442
443 /**
444 * Extension point for product-usage analytics.
445 *
446 * Collectors (Free / Pro / Chatbot) hook this to inject their `bd_*`
447 * metric groups into `$body['optional_data']` (a nested array). See
448 * `WPDeveloper\BetterDocs\Insights\Collector` and `docs/insights-tracking.md`.
449 *
450 * @param array $body The tracking payload.
451 * @param Insights $this The Insights instance.
452 */
453 $body = apply_filters( 'betterdocs_insights_data', $body, $this );
454
455 /**
456 * wpinsight persists only the fixed base fields plus a single
457 * `optional_data` field, which it expects as a JSON-encoded string.
458 * Collectors assemble it as a nested array; here we JSON-encode each
459 * first-layer group (bd_features, bd_counts, …) into its own string
460 * value, then JSON-encode the whole `optional_data` object.
461 */
462 if ( ! empty( $body[ 'optional_data' ] ) && is_array( $body[ 'optional_data' ] ) ) {
463 foreach ( $body[ 'optional_data' ] as $group => $value ) {
464 if ( is_array( $value ) ) {
465 $body[ 'optional_data' ][ $group ] = wp_json_encode( $value );
466 }
467 }
468 $body[ 'optional_data' ] = wp_json_encode( $body[ 'optional_data' ] );
469 }
470
471 return $body;
472 }
473
474 /**
475 * Collect plugin data,
476 * Retrieve current plugin information
477 *
478 * @since 2.5.0
479 */
480 public function plugin_data() {
481 if ( ! function_exists( 'get_plugin_data' ) ) {
482 include ABSPATH . '/wp-admin/includes/plugin.php';
483 }
484 $plugin = get_plugin_data( $this->plugin_file );
485 return $plugin;
486 }
487
488 /**
489 * Send the data to insights.
490 *
491 * @since 2.5.0
492 */
493 public function send_data( $body ) {
494 /**
495 * Get SITE ID
496 */
497 $site_id_key = "wpins_{$this->plugin_name}_site_id";
498 $site_id = get_option( $site_id_key, false );
499 $failed_data = array();
500 $site_url = get_bloginfo( 'url' );
501 $original_site_url = get_option( "wpins_{$this->plugin_name}_original_url", false );
502 // Re-register when the site has no recorded origin URL yet, or when the
503 // current URL no longer matches it (domain migration). Without the URL-change
504 // arm (added in tracker 3.0.3), a moved site keeps reporting under its old
505 // site ID with stale identity data. Mirrors EA Lite's 3.0.3 send_data().
506 if ( ( false === $original_site_url || $original_site_url != $site_url ) && version_compare( $body[ 'wpins_version' ], '3.0.1', '>=' ) ) {
507 $site_id = false;
508 }
509 /**
510 * Send Initial Data to API
511 */
512 if ( false == $site_id && false !== $this->item_id && ( false === $original_site_url || $original_site_url != $site_url ) ) {
513 // Validate before interpolating: REMOTE_ADDR went into the URL raw, so
514 // anything the host put there (some proxy setups write a comma-joined
515 // list, or a value taken from a client header) became URL path/query.
516 // FILTER_VALIDATE_IP also drops private, loopback and reserved
517 // addresses — a LAN address tells the geolocator nothing and sending
518 // it is a needless disclosure.
519 $remote_addr = isset( $_SERVER[ 'REMOTE_ADDR' ] ) ? wp_unslash( $_SERVER[ 'REMOTE_ADDR' ] ) : ''; //phpcs:ignore
520 $public_ip = filter_var(
521 $remote_addr,
522 FILTER_VALIDATE_IP,
523 FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
524 );
525
526 if ( false !== $public_ip ) {
527 // NOTE: http, not https, is deliberate — ip-api.com serves TLS only
528 // on its paid plans and answers 403 over https on the free tier, so
529 // switching the scheme would silently disable country detection
530 // rather than secure it. The response is therefore untrusted input:
531 // it is read for a single string field, length-bounded, and
532 // sanitised below. The residual exposure is that the site's own
533 // outbound IP travels in cleartext to a third party.
534 $country_request = wp_remote_get(
535 'http://ip-api.com/json/' . rawurlencode( $public_ip ) . '?fields=country',
536 [ 'timeout' => 5 ]
537 ); //phpcs:ignore
538
539 if ( ! is_wp_error( $country_request ) && 200 == wp_remote_retrieve_response_code( $country_request ) ) {
540 $ip_data = json_decode( wp_remote_retrieve_body( $country_request ) );
541 $country = ( is_object( $ip_data ) && isset( $ip_data->country ) && is_string( $ip_data->country ) )
542 ? sanitize_text_field( $ip_data->country )
543 : '';
544 $body[ 'country' ] = ( '' !== $country ) ? substr( $country, 0, 64 ) : 'NOT SET';
545 }
546 }
547
548 $body[ 'plugin_slug' ] = $this->plugin_name;
549 $body[ 'url' ] = $site_url;
550 $body[ 'item_id' ] = $this->item_id;
551
552 $request = $this->remote_post( $body );
553 if ( ! is_wp_error( $request ) && 200 == $request[ 'response' ][ 'code' ] ) {
554 $retrieved_body = json_decode( wp_remote_retrieve_body( $request ), true );
555 if ( is_array( $retrieved_body ) && isset( $retrieved_body[ 'siteId' ] ) ) {
556 update_option( $site_id_key, $retrieved_body[ 'siteId' ], 'no' );
557 update_option( "wpins_{$this->plugin_name}_original_url", $site_url, 'no' );
558 update_option( "wpins_{$this->plugin_name}_{$retrieved_body[ 'siteId' ]}", $body, 'no' );
559 }
560 } else {
561 $failed_data = $body;
562 }
563 }
564
565 $site_id_data_key = "wpins_{$this->plugin_name}_{$site_id}";
566 $site_id_data_failed_key = "wpins_{$this->plugin_name}_{$site_id}_send_failed";
567
568 if ( false != $site_id ) {
569 $old_sent_data = get_option( $site_id_data_key, array() );
570 $diff_data = $this->diff( $body, $old_sent_data );
571 $failed_data = get_option( $site_id_data_failed_key, array() );
572 if ( ! empty( $failed_data ) && $diff_data != $failed_data ) {
573 $failed_data = array_merge( $failed_data, $diff_data );
574 }
575 }
576
577 if ( ! empty( $failed_data ) && false != $site_id ) {
578 $failed_data[ 'plugin_slug' ] = $this->plugin_name;
579 $failed_data[ 'url' ] = $site_url;
580 $failed_data[ 'site_id' ] = $site_id;
581 if ( false != $original_site_url ) {
582 $failed_data[ 'original_url' ] = $original_site_url;
583 }
584
585 $request = $this->remote_post( $failed_data );
586 if ( ! is_wp_error( $request ) ) {
587 delete_option( $site_id_data_failed_key );
588 $replaced_data = array_merge( $old_sent_data, $failed_data );
589 update_option( $site_id_data_key, $replaced_data, 'no' );
590 }
591 }
592
593 if ( ! empty( $diff_data ) && false != $site_id && empty( $failed_data ) ) {
594 $diff_data[ 'plugin_slug' ] = $this->plugin_name;
595 $diff_data[ 'url' ] = $site_url;
596 $diff_data[ 'site_id' ] = $site_id;
597 if ( false != $original_site_url ) {
598 $diff_data[ 'original_url' ] = $original_site_url;
599 }
600
601 $request = $this->remote_post( $diff_data );
602 if ( is_wp_error( $request ) ) {
603 update_option( $site_id_data_failed_key, $diff_data, 'no' );
604 } else {
605 $replaced_data = array_merge( $old_sent_data, $diff_data );
606 update_option( $site_id_data_key, $replaced_data, 'no' );
607 }
608 }
609
610 $this->set_track_time();
611
612 if ( isset( $request ) && is_wp_error( $request ) ) {
613 return $request;
614 }
615
616 if ( isset( $request ) ) {
617 return true;
618 }
619 return false;
620 }
621
622 /**
623 * WP_REMOTE_POST method responsible for send data to the API_URL
624 *
625 * @param array $data
626 * @param array $args
627 * @return \WP_Error|array|null
628 */
629 protected function remote_post( $data = array(), $args = array() ) {
630 if ( empty( $data ) ) {
631 return;
632 }
633
634 $args = wp_parse_args(
635 $args,
636 array(
637 'method' => 'POST',
638 'timeout' => 30,
639 'redirection' => 5,
640 'httpversion' => '1.1',
641 'blocking' => true,
642 'body' => $data,
643 'user-agent' => 'PUT/1.0.0; ' . get_bloginfo( 'url' )
644 )
645 );
646
647 $request = wp_remote_post( esc_url( self::API_URL ), $args );
648
649 if ( is_wp_error( $request ) || ( isset( $request[ 'response' ], $request[ 'response' ][ 'code' ] ) && 200 != $request[ 'response' ][ 'code' ] ) ) {
650 return new \WP_Error( 500, 'Something went wrong.' );
651 }
652
653 return $request;
654 }
655
656 /**
657 * Difference between old and new data
658 *
659 * @param array $new_data
660 * @param array $old_data
661 * @return array
662 */
663 protected function diff( $new_data, $old_data ) {
664 $data = array();
665 if ( ! empty( $new_data ) ) {
666 foreach ( $new_data as $key => $value ) {
667 if ( isset( $old_data[ $key ] ) ) {
668 if ( $old_data[ $key ] == $value ) {
669 continue;
670 }
671 }
672 $data[ $key ] = $value;
673 }
674 }
675 return $data;
676 }
677
678 /**
679 * Display the admin notice to users to allow them to opt in
680 *
681 * @since 2.5.0
682 */
683 public function notice() {
684 /**
685 * Return if notice is not set.
686 */
687 if ( ! isset( $this->notice_options[ 'notice' ] ) ) {
688 return;
689 }
690 /**
691 * Check is allowed or blocked for notice.
692 */
693 $block_notice = get_option( 'wpins_block_notice' );
694 if ( isset( $block_notice[ $this->plugin_name ] ) ) {
695 return;
696 }
697 if ( ! current_user_can( 'manage_options' ) ) {
698 return;
699 }
700
701 $this->has_notice = true;
702
703 $url_yes = add_query_arg(
704 array(
705 'plugin' => $this->plugin_name,
706 'plugin_action' => 'yes'
707 )
708 );
709
710 $url_no = add_query_arg(
711 array(
712 'plugin' => $this->plugin_name,
713 'plugin_action' => 'no'
714 )
715 );
716
717 $url_yes = wp_nonce_url( $url_yes, '_wpnonce_optin_' . $this->plugin_name );
718 $url_no = wp_nonce_url( $url_no, '_wpnonce_optin_' . $this->plugin_name );
719
720 // Decide on notice text
721 $notice_text = $this->notice_options[ 'notice' ] . ' <a href="#" class="wpinsights-' . esc_attr( $this->plugin_name ) . '-collect" >' . $this->notice_options[ 'consent_button_text' ] . '</a>';
722 $extra_notice_text = $this->notice_options[ 'extra_notice' ];
723
724 ?>
725
726 <div class="nx-optin">
727 <p><?php echo wp_kses_post( $notice_text ); ?></p>
728 <div class="wpinsights-data" style="display: none;">
729 <p><?php echo wp_kses_post( $extra_notice_text ); ?></p>
730 </div>
731 <p>
732 <a href="<?php echo esc_url( $url_yes ); ?>" class="button-primary">
733 <?php echo esc_html( $this->notice_options[ 'yes' ] ); ?>
734 </a>&nbsp;
735 <a href="<?php echo esc_url( $url_no ); ?>" class="button-secondary">
736 <?php echo esc_html( $this->notice_options[ 'no' ] ); ?>
737 </a>
738 </p>
739 </div>
740
741 <?php
742 }
743
744 public function notice_script() {
745 if ( $this->has_notice ) {
746 echo "<script type='text/javascript'>jQuery('.wpinsights-" . esc_attr( $this->plugin_name ) . "-collect').on('click', function(e) {e.preventDefault();jQuery('.wpinsights-data').slideToggle('fast');});</script>";
747 }
748 }
749
750 /**
751 * Set all notice options to customized notice.
752 *
753 * @since 2.5.0
754 * @param array $options
755 * @return void
756 */
757 public function set_notice_options( $options = array() ) {
758 $default_options = array(
759 'consent_button_text' => __( 'What we collect.', 'betterdocs' ),
760 'yes' => __( 'Sure, I\'d like to help', 'betterdocs' ),
761 'no' => __( 'No Thanks.', 'betterdocs' )
762 );
763 $options = wp_parse_args( $options, $default_options );
764 $this->notice_options = $options;
765 }
766
767 /**
768 * Responsible for track the click from Notice.
769 *
770 * @return void
771 */
772 public function clicked( $notice = null ) {
773 if ( isset( $_GET[ '_wpnonce' ] ) && isset( $_GET[ 'plugin' ] ) && isset( $_GET[ 'plugin_action' ] ) ) {
774 $tab = isset( $_GET[ 'tab' ] ) ? sanitize_text_field( wp_unslash( $_GET[ 'tab' ] ) ) : '';
775 if ( 'plugin-information' === $tab ) {
776 return;
777 }
778
779 $nonce = sanitize_text_field( wp_unslash( $_GET[ '_wpnonce' ] ) );
780 if ( ! wp_verify_nonce( $nonce, '_wpnonce_optin_' . $this->plugin_name ) ) {
781 return;
782 }
783
784 $plugin = sanitize_text_field( wp_unslash( $_GET[ 'plugin' ] ) );
785 $action = sanitize_text_field( wp_unslash( $_GET[ 'plugin_action' ] ) );
786 if ( 'yes' == $action ) {
787 $this->schedule_tracking();
788 $this->set_is_tracking_allowed( true, $plugin );
789 if ( $this->do_tracking( true ) ) {
790 $this->update_block_notice( $plugin );
791 }
792 } else {
793 $this->set_is_tracking_allowed( false, $plugin );
794 $this->update_block_notice( $plugin );
795 }
796
797 if ( ! is_null( $notice ) ) {
798 $notice->dismiss->dismiss_notice();
799 }
800
801 /**
802 * Redirect User To the Current URL, but without set query arguments.
803 */
804 wp_safe_redirect( $this->redirect_to() );
805 }
806 }
807
808 /**
809 * Set if we should block the opt-in notice for this plugin
810 *
811 * @since 2.5.0
812 */
813 public function update_block_notice( $plugin = null ) {
814 if ( empty( $plugin ) ) {
815 $plugin = $this->plugin_name;
816 }
817 $block_notice = get_option( 'wpins_block_notice' );
818 if ( empty( $block_notice ) || ! is_array( $block_notice ) ) {
819 $block_notice = array( $plugin => $plugin );
820 } else {
821 $block_notice[ $plugin ] = $plugin;
822 }
823 update_option( 'wpins_block_notice', $block_notice, 'no' );
824 }
825
826 /**
827 * AJAX callback when the deactivated form is submitted.
828 *
829 * @since 2.5.0
830 */
831 public function deactivate_reasons_form_submit() {
832 check_ajax_referer( 'wpins_deactivation_nonce', 'security' );
833 if ( isset( $_POST[ 'values' ] ) ) {
834 $values = sanitize_text_field( wp_unslash( $_POST[ 'values' ] ) );
835 update_option( 'wpins_deactivation_reason_' . $this->plugin_name, $values );
836 }
837 if ( isset( $_POST[ 'details' ] ) ) {
838 $details = sanitize_text_field( wp_unslash( $_POST[ 'details' ] ) );
839 update_option( 'wpins_deactivation_details_' . $this->plugin_name, $details );
840 }
841 wp_die();
842 }
843
844 /**
845 * Filter the deactivation link to allow us to present a form when the user deactivates the plugin
846 *
847 * @since 2.5.0
848 */
849 public function deactivate_action_links( $links ) {
850 /**
851 * Check is tracking allowed or not.
852 */
853 if ( ! $this->is_tracking_allowed() ) {
854 return $links;
855 }
856 if ( isset( $links[ 'deactivate' ] ) && $this->include_goodbye_form ) {
857 $deactivation_link = $links[ 'deactivate' ];
858 /**
859 * Change the default deactivate button link.
860 */
861 $deactivation_link = str_replace( '<a ', '<div class="wpinsights-goodbye-form-wrapper-' . esc_attr( $this->plugin_name ) . '"><div class="wpinsights-goodbye-form-bg"></div><span class="wpinsights-goodbye-form" id="wpinsights-goodbye-form"></span></div><a onclick="javascript:event.preventDefault();" id="wpinsights-goodbye-link-' . esc_attr( $this->plugin_name ) . '" ', $deactivation_link );
862 $links[ 'deactivate' ] = $deactivation_link;
863 }
864 return $links;
865 }
866
867 /**
868 * ALL Deactivate Reasons.
869 *
870 * @since 2.5.0
871 */
872 public function deactivation_reasons() {
873 $form = array();
874 $form[ 'heading' ] = __( 'We Value Your Feedback', 'betterdocs' );
875 $form[ 'body' ] = __( "Could you please tell us why you're deactivating our plugin? Your insights will help us to improve.", 'betterdocs' );
876
877 $form[ 'options' ] = array(
878 __( 'I no longer need the plugin', 'betterdocs' ),
879 array(
880 'label' => __( 'I found a better plugin', 'betterdocs' ),
881 'extra_field' => __( 'Please share which plugin', 'betterdocs' )
882 ),
883 __( "I couldn't get the plugin to work", 'betterdocs' ),
884 __( 'It\'s a temporary deactivation', 'betterdocs' ),
885 array(
886 'label' => __( 'Other', 'betterdocs' ),
887 'extra_field' => __( 'Please share the reason', 'betterdocs' ),
888 'type' => 'textarea'
889 )
890 );
891 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WP Insights (appsero) library hook; name is defined by the upstream tracker library.
892 return apply_filters( 'wpins_form_text_' . $this->plugin_name, $form );
893 }
894
895 /**
896 * Deactivate Reasons Form.
897 * This form will appears when user wants to deactivate the plugin to send you deactivated reasons.
898 *
899 * @since 3.0.0
900 */
901 public function deactivate_reasons_form() {
902 $form = $this->deactivation_reasons();
903 $class_plugin_name = esc_attr( $this->plugin_name );
904 $html = '<div class="betterdocs__modal-close-btn"><svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M5.29289 5.29289C5.68342 4.90237 6.31658 4.90237 6.70711 5.29289L12 10.5858L17.2929 5.29289C17.6834 4.90237 18.3166 4.90237 18.7071 5.29289C19.0976 5.68342 19.0976 6.31658 18.7071 6.70711L13.4142 12L18.7071 17.2929C19.0976 17.6834 19.0976 18.3166 18.7071 18.7071C18.3166 19.0976 17.6834 19.0976 17.2929 18.7071L12 13.4142L6.70711 18.7071C6.31658 19.0976 5.68342 19.0976 5.29289 18.7071C4.90237 18.3166 4.90237 17.6834 5.29289 17.2929L10.5858 12L5.29289 6.70711C4.90237 6.31658 4.90237 5.68342 5.29289 5.29289Z"/></svg></div><div class="wpinsights-goodbye-form-head"><span><svg width="65" height="64" viewBox="0 0 65 64" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="0.5" width="64" height="64" rx="16" fill="#e0fff6"/><path d="M41.8346 22.668L45.8346 18.668M19.168 45.3346L23.168 41.3346M26.5013 34.0013L29.8346 30.668M30.5013 38.0013L33.8346 34.668M24.9013 43.068C25.1986 43.3663 25.5518 43.603 25.9408 43.7645C26.3298 43.926 26.7468 44.0092 27.168 44.0092C27.5891 44.0092 28.0062 43.926 28.3951 43.7645C28.7841 43.603 29.1373 43.3663 29.4346 43.068L32.5013 40.0013L24.5013 32.0013L21.4346 35.068C21.1363 35.3653 20.8996 35.7185 20.7381 36.1075C20.5766 36.4964 20.4934 36.9135 20.4934 37.3346C20.4934 37.7558 20.5766 38.1728 20.7381 38.5618C20.8996 38.9508 21.1363 39.304 21.4346 39.6013L24.9013 43.068ZM32.5013 24.0013L40.5013 32.0013L43.568 28.9346C43.8663 28.6373 44.103 28.2841 44.2645 27.8951C44.426 27.5062 44.5092 27.0891 44.5092 26.668C44.5092 26.2468 44.426 25.8298 44.2645 25.4408C44.103 25.0518 43.8663 24.6986 43.568 24.4013L40.1013 20.9346C39.804 20.6363 39.4508 20.3996 39.0618 20.2381C38.6728 20.0766 38.2558 19.9934 37.8346 19.9934C37.4135 19.9934 36.9964 20.0766 36.6075 20.2381C36.2185 20.3996 35.8653 20.6363 35.568 20.9346L32.5013 24.0013Z" stroke="#00b884" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span><h1>' . $form[ 'heading' ] . '</h1></div>';
905 $html .= '<div class="wpinsights-goodbye-form-body"><p class="wpinsights-goodbye-form-caption">' . esc_html( $form[ 'body' ] ) . '</p>';
906 if ( is_array( $form[ 'options' ] ) ) {
907 $html .= '<div id="wpinsights-goodbye-options" class="wpinsights-goodbye-options"><ul>';
908 foreach ( $form[ 'options' ] as $option ) {
909 if ( is_array( $option ) ) {
910 $id = strtolower( str_replace( ' ', '_', esc_attr( $option[ 'label' ] ) ) );
911 $id = $id . '_' . $class_plugin_name;
912 $html .= '<li class="has-goodbye-extra">';
913 $html .= '<input type="radio" name="wpinsights-' . esc_attr( $class_plugin_name ) . '-goodbye-options" id="' . esc_attr( $id ) . '" value="' . esc_attr( $option[ 'label' ] ) . '">';
914 $html .= '<div><label for="' . $id . '">' . esc_attr( $option[ 'label' ] ) . '</label>';
915 if ( isset( $option[ 'extra_field' ] ) && ! isset( $option[ 'type' ] ) ) {
916 $html .= '<input type="text" style="display: none" name="' . esc_attr( $id ) . '" id="' . str_replace( ' ', '', esc_attr( $option[ 'extra_field' ] ) ) . '" placeholder="' . esc_attr( $option[ 'extra_field' ] ) . '">';
917 }
918 if ( isset( $option[ 'extra_field' ] ) && isset( $option[ 'type' ] ) ) {
919 $html .= '<' . $option[ 'type' ] . ' style="display: none" type="text" name="' . esc_attr( $id ) . '" id="' . str_replace( ' ', '', esc_attr( $option[ 'extra_field' ] ) ) . '" placeholder="' . esc_attr( $option[ 'extra_field' ] ) . '"></' . $option[ 'type' ] . '>';
920 }
921 $html .= '</div></li>';
922 } else {
923 $id = strtolower( str_replace( ' ', '_', esc_attr( $option ) ) );
924 $id = $id . '_' . $class_plugin_name;
925 $html .= '<li><input type="radio" name="wpinsights-' . $class_plugin_name . '-goodbye-options" id="' . esc_attr( $id ) . '" value="' . esc_attr( $option ) . '"> <label for="' . $id . '">' . esc_attr( $option ) . '</label></li>';
926 }
927 }
928 $html .= '</ul></div><!-- .wpinsights-' . $class_plugin_name . '-goodbye-options -->';
929 }
930 $html .= '</div><!-- .wpinsights-goodbye-form-body -->';
931 $html .= '<p class="deactivating-spinner"><span class="spinner"></span> ' . __( 'Submitting form', 'betterdocs' ) . '</p>';
932
933 $wrapper_class = '.wpinsights-goodbye-form-wrapper-' . $class_plugin_name;
934
935 $styles = '';
936 $styles .= '<style type="text/css">';
937 $styles .= '.wpinsights-form-active-' . $class_plugin_name . ' .wpinsights-goodbye-form-bg {';
938 $styles .= 'background: rgba( 0, 0, 0, .8 );position: fixed;top: 0;left: 0;width: 100%;height: 100%;z-index: 9;';
939 $styles .= '}';
940 $styles .= $wrapper_class . '{';
941 $styles .= 'position: relative; display: none;';
942 $styles .= '}';
943 $styles .= '.wpinsights-form-active-' . $class_plugin_name . ' ' . $wrapper_class . '{';
944 $styles .= 'display: flex !important; position: fixed;top: 0;left: 0;width: 100%;height: 100%; justify-content: center; align-items: center;';
945 $styles .= '}';
946 $styles .= $wrapper_class . ' .wpinsights-goodbye-form { display: none; }';
947 $styles .= '.wpinsights-form-active-' . $class_plugin_name . ' .wpinsights-goodbye-form {';
948 $styles .= 'position: relative !important; width: 550px; max-width: 80%; background: #fff; box-shadow: 2px 8px 23px 3px rgba(0,0,0,.2); border-radius: 3px; white-space: normal; overflow: hidden; display: block; z-index: 999999;';
949 $styles .= '}';
950 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-head {';
951 $styles .= 'background: #fff; color: #495157; padding: 18px; box-shadow: 0 0 8px rgba(0,0,0,.1); font-size: 15px;';
952 $styles .= '}';
953 $styles .= $wrapper_class . ' .wpinsights-goodbye-form .wpinsights-goodbye-form-head strong { font-size: 15px; }';
954 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-body { padding: 8px 18px; color: #333; }';
955 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-body label { padding-left: 5px; color: #6d7882; }';
956 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-body .wpinsights-goodbye-form-caption {';
957 $styles .= 'font-weight: 500; font-size: 15px; color: #495157; line-height: 1.4;';
958 $styles .= '}';
959 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options { padding-top: 5px; }';
960 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li { margin-bottom: 15px; }';
961 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li > div { display: inline; padding-left: 3px; }';
962 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li > div > input, ' . $wrapper_class . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li > div > textarea {';
963 $styles .= 'margin: 10px 18px; padding: 8px; width: 80%;';
964 $styles .= '}';
965 $styles .= $wrapper_class . ' .deactivating-spinner { display: none; padding-bottom: 20px !important; }';
966 $styles .= $wrapper_class . ' .deactivating-spinner .spinner { float: none; margin: 4px 4px 0 18px; vertical-align: bottom; visibility: visible; }';
967 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-footer { padding: 8px 18px; margin-bottom: 15px; }';
968 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-footer > .wpinsights-goodbye-form-buttons { display: flex; align-items: center; justify-content: space-between; }';
969 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-footer .wpinsights-submit-btn {';
970 $styles .= 'background-color: #f3bafd; -webkit-border-radius: 3px; border-radius: 3px; color: #0c0d0e; line-height: 1; padding: 10px 20px; font-size: 13px; font-weight: 500; text-transform: uppercase; transition: .3s;';
971 $styles .= '}';
972 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-footer .wpinsights-submit-btn:hover {';
973 $styles .= 'background-color: #f5d0fe;';
974 $styles .= '}';
975 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-footer .wpinsights-deactivate-btn {';
976 $styles .= 'font-size: 13px; color: #a4afb7; background: none; float: right; padding-right: 10px; width: auto; text-decoration: underline;';
977 $styles .= '}';
978 $styles .= $wrapper_class . ' .test {';
979 $styles .= '}';
980 $styles .= '
981
982 .wpinsights-form-active-betterdocs .wpinsights-goodbye-form-wrapper-betterdocs {
983 display: flex !important;
984 position: fixed;
985 top: 0;
986 left: 0;
987 right: 0;
988 bottom: 0;
989 width: 100%;
990 z-index: 9999;
991 height: 100%;
992 justify-content: center;
993 align-items: center;
994 }
995 .wpinsights-form-active-betterdocs .wpinsights-goodbye-form {
996 border-radius: 8px;
997 width: 563px;
998 overflow: visible;
999 position: relative;
1000 }
1001 .wpinsights-goodbye-form .betterdocs__modal-close-btn {
1002 position: absolute;
1003 top: 0;
1004 right: -55px;
1005 width: 40px;
1006 height: 40px;
1007 background: #fff;
1008 border-radius: 50%;
1009 display: flex;
1010 justify-content: center;
1011 align-items: center;
1012 font-size: 12px;
1013 font-weight: 500;
1014 color: #475467;
1015 cursor: pointer;
1016 }
1017 .wpinsights-goodbye-form .betterdocs__modal-close-btn svg {
1018 width: 20px;
1019 }
1020 .wpinsights-goodbye-form-wrapper-betterdocs .wpinsights-goodbye-form-head {
1021 background: #fff;
1022 color: #495157;
1023 padding: 24px;
1024 border-radius: 8px 8px 0 0;
1025 box-shadow: none;
1026 border-bottom: 1px solid #EAECF0;
1027 display: flex;
1028 flex-direction: column;
1029 gap: 16px;
1030 align-items: center;
1031 }
1032 .wpinsights-goodbye-form-wrapper-betterdocs .wpinsights-goodbye-form-head > span {
1033 display: inline-flex;
1034 }
1035 .wpinsights-goodbye-form-head h1 {
1036 font-size: 22px;
1037 font-weight: 450;
1038 color: #1D2939;
1039 padding: 0;
1040 }
1041 .wpinsights-goodbye-form-wrapper-betterdocs .wpinsights-goodbye-form-body {
1042 padding: 32px 40px;
1043 max-height: calc(70vh - 230px);
1044 overflow: auto;
1045 }
1046 .wpinsights-goodbye-form-wrapper-betterdocs .wpinsights-goodbye-form-body .wpinsights-goodbye-form-caption {
1047 font-size: 18px;
1048 color: #1D2939;
1049 margin: 0;
1050 padding-bottom: 16px;
1051 font-weight: 400;
1052 }
1053 .wpinsights-goodbye-form-wrapper-betterdocs .wpinsights-goodbye-form-body #wpinsights-goodbye-options {
1054 padding-top: 0px;
1055 }
1056 .wpinsights-goodbye-form-wrapper-betterdocs .widefat td ul {
1057 font-size: 14px;
1058 margin: 0;
1059 color: #475467;
1060 line-height: 1.4em;
1061 }
1062 .wpinsights-goodbye-form-wrapper-betterdocs .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li:not(:last-child) {
1063 margin-bottom: 16px;
1064 }
1065 .wpinsights-goodbye-form-wrapper-betterdocs .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li:last-child {
1066 margin-bottom: 0;
1067 }
1068 .wpinsights-goodbye-form-wrapper-betterdocs input[type=radio] {
1069 border-radius: 50%;
1070 margin-right: .25rem;
1071 line-height: .71428571;
1072 margin: 0;
1073 }
1074 .wpinsights-goodbye-form-wrapper-betterdocs .wpinsights-goodbye-form-body label {
1075 padding-left: 4px;
1076 color: #475467;
1077 font-size: 14px;
1078 }
1079 .wpinsights-goodbye-form-wrapper-betterdocs .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li > div > input, .wpinsights-goodbye-form-wrapper-betterdocs .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li > div > textarea {
1080 padding: 10px 16px;
1081 border-radius: 8px;
1082 width: 100%;
1083 border: 1px solid #D0D5DD;
1084 margin: 0;
1085 margin-top: 16px;
1086 color: #1D2939;
1087 font-size: 14px;
1088 line-height: 1.5em;
1089 resize: none;
1090 max-height: 44px;
1091 }
1092 .wpinsights-goodbye-form-wrapper-betterdocs input[type=radio] {
1093 border: 1px solid #D0D5DD;
1094 box-shadow: none;
1095 }
1096 .wpinsights-goodbye-form-wrapper-betterdocs input[type=radio]:checked::before {
1097 content: "";
1098 border-radius: 50%;
1099 width: .88rem;
1100 height: .88rem;
1101 margin: -.95px;
1102 background-color: #00b884;
1103 border: 1px solid #e0fff6;
1104 line-height: 1.14285714;
1105 outline: 0;
1106 }
1107 .wpinsights-goodbye-form-wrapper-betterdocs input[type=radio]:focus {
1108 border-color: #D0D5DD;
1109 box-shadow: none;
1110 outline: 0;
1111 }
1112 .wpinsights-goodbye-form-wrapper-betterdocs .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li > div > input::placeholder,
1113 .wpinsights-goodbye-form-wrapper-betterdocs .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li > div > textarea::placeholder{
1114 color: #C6CBD2 !important;
1115 }
1116 .wpinsights-goodbye-form-wrapper-betterdocs .wpinsights-goodbye-form-footer {
1117 padding: 16px 36px;
1118 margin-bottom: 0;
1119 border-top: 1px solid #EAECF0;
1120 }
1121 .wpinsights-goodbye-form-wrapper-betterdocs input:focus,
1122 .wpinsights-goodbye-form-wrapper-betterdocs textarea:focus {
1123 box-shadow: none !important;
1124 outline: 0 !important;
1125 }
1126 .wpinsights-goodbye-form-wrapper-betterdocs .wpinsights-goodbye-form-footer > .wpinsights-goodbye-form-buttons {
1127 display: flex;
1128 align-items: center;
1129 justify-content: flex-start;
1130 }
1131 .wpinsights-goodbye-form-wrapper-betterdocs .wpinsights-goodbye-form-footer .wpinsights-submit-btn {
1132 background-color: #00b884;
1133 -webkit-border-radius: 8px;
1134 border-radius: 8px;
1135 color: #fff;
1136 padding: 8px 16px;
1137 font-size: 14px;
1138 font-weight: 500;
1139 line-height: 1.5em;
1140 text-transform: capitalize;
1141 transition: .3s;
1142 }
1143 .wpinsights-goodbye-form-wrapper-betterdocs .wpinsights-goodbye-form-footer .wpinsights-submit-btn:hover {
1144 background: #00b884;
1145 }
1146
1147 .wpinsights-goodbye-form-wrapper-betterdocs .wpinsights-goodbye-form-footer .wpsp-put-deactivate-btn {
1148 font-size: 14px;
1149 font-weight: 500;
1150 color: #344054;
1151 margin-left: 10px;
1152 }
1153 .wp-person a:focus .gravatar, a:focus, a:focus .media-icon img, a:focus .plugin-icon {
1154 color: #043959;
1155 box-shadow: none;
1156 outline: 0;
1157 }
1158
1159 ';
1160 $styles .= '</style>';
1161 $styles .= '';
1162
1163 echo $styles; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is styles, which is why escaping is not needed
1164
1165 ?>
1166 <script type="text/javascript">
1167 jQuery(document).ready(function($){
1168 $("#wpinsights-goodbye-link-<?php echo esc_attr( $class_plugin_name ); ?>").on("click",function(){
1169 // We'll send the user to this deactivation link when they've completed or dismissed the form
1170 var url = document.getElementById("wpinsights-goodbye-link-<?php echo esc_attr( $class_plugin_name ); ?>");
1171 $('body').toggleClass('wpinsights-form-active-<?php echo esc_attr( $class_plugin_name ); ?>');
1172 $(".wpinsights-goodbye-form-wrapper-<?php echo esc_attr( $class_plugin_name ); ?> #wpinsights-goodbye-form").fadeIn();
1173 $(".wpinsights-goodbye-form-wrapper-<?php echo esc_attr( $class_plugin_name ); ?> #wpinsights-goodbye-form").html( '<?php echo $html; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This is an html, not needed to escape ?>' + '<div class="wpinsights-goodbye-form-footer"><div class="wpinsights-goodbye-form-buttons"><a id="wpinsights-submit-form-<?php echo esc_attr( $class_plugin_name ); ?>" class="wpinsights-submit-btn" href="#"><?php esc_html_e( 'Submit and Deactivate', 'betterdocs' ); ?></a>&nbsp;<a class="wpsp-put-deactivate-btn" href="'+url+'"><?php esc_html_e( 'Just Deactivate', 'betterdocs' ); ?></a></div></div>');
1174 $('#wpinsights-submit-form-<?php echo esc_attr( $class_plugin_name ); ?>').on('click', function(e){
1175 // As soon as we click, the body of the form should disappear
1176 $("#wpinsights-goodbye-form-<?php echo esc_attr( $class_plugin_name ); ?> .wpinsights-goodbye-form-body").fadeOut();
1177 $("#wpinsights-goodbye-form-<?php echo esc_attr( $class_plugin_name ); ?> .wpinsights-goodbye-form-footer").fadeOut();
1178 // Fade in spinner
1179 $("#wpinsights-goodbye-form-<?php echo esc_attr( $class_plugin_name ); ?> .deactivating-spinner").fadeIn();
1180 e.preventDefault();
1181 var checkedInput = $("input[name='wpinsights-<?php echo esc_attr( $class_plugin_name ); ?>-goodbye-options']:checked"),
1182 checkedInputVal, details;
1183 if( checkedInput.length > 0 ) {
1184 checkedInputVal = checkedInput.val();
1185 details = $('input[name="'+ checkedInput[0].id +'"], textarea[name="'+ checkedInput[0].id +'"]').val();
1186 }
1187
1188 if( typeof details === 'undefined' ) {
1189 details = '';
1190 }
1191 if( typeof checkedInputVal === 'undefined' ) {
1192 checkedInputVal = 'No Reason';
1193 }
1194
1195 var data = {
1196 'action': 'deactivation_form_<?php echo esc_attr( $class_plugin_name ); ?>',
1197 'values': checkedInputVal,
1198 'details': details,
1199 'security': "<?php echo wp_create_nonce( 'wpins_deactivation_nonce' ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>",
1200 'dataType': "json"
1201 }
1202
1203 $.post(
1204 ajaxurl,
1205 data,
1206 function(response){
1207 // Redirect to original deactivation URL
1208 window.location.href = url;
1209 }
1210 );
1211 });
1212 $('#wpinsights-goodbye-options > ul ').on('click', 'li label, li > input', function( e ){
1213 var parent = $(this).parents('li');
1214 parent.siblings().find('label').next('input, textarea').css('display', 'none');
1215 parent.find('label').next('input, textarea').css('display', 'block');
1216 });
1217 // If we click outside the form, the form will close
1218 $('.wpinsights-goodbye-form-bg, #wpinsights-goodbye-form > .betterdocs__modal-close-btn').on('click',function(){
1219 $("#wpinsights-goodbye-form").fadeOut();
1220 $('body').removeClass('wpinsights-form-active-<?php echo esc_attr( $class_plugin_name ); ?>');
1221 });
1222 });
1223 });
1224 </script>
1225 <?php
1226 }
1227 }
1228