PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / trunk
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO vtrunk
2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.10.0 1.11.0 All 47 releases
thinkrank / includes / core / class-plugin-usage-tracker.php

class-plugin-usage-tracker.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO trunk, at includes/core/class-plugin-usage-tracker.php

1,266 lines 54.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Plugin_Usage_Tracker
5 *
6 * Opt-in usage tracking SDK (WP Insights). Responsible for collecting
7 * non-sensitive diagnostic data and sending it to the insights endpoint
8 * when the site administrator has explicitly opted in.
9 *
10 * Ported from the WPDeveloper WP Insights SDK and adapted for ThinkRank:
11 * standalone notice wiring (no external notice library) and ThinkRank
12 * feature usage as the optional data payload.
13 *
14 * @package ThinkRank\Core
15 * @since 1.12.0
16 * @version 3.0.3
17 */
18
19 declare(strict_types=1);
20
21 namespace ThinkRank\Core;
22
23 // Prevent direct access
24 if (!defined('ABSPATH')) {
25 exit;
26 }
27
28 /**
29 * Main SDK for Plugin_Usage_Tracker.
30 */
31 class Plugin_Usage_Tracker {
32
33 /**
34 * WP Insights Version
35 */
36 const WPINS_VERSION = '3.0.3';
37
38 /**
39 * API URL
40 */
41 const API_URL = 'https://send.wpinsight.com/process-plugin-data';
42
43 /**
44 * Installed Plugin File
45 *
46 * @var string
47 */
48 private $plugin_file = null;
49
50 /**
51 * Installed Plugin Name
52 *
53 * @var string
54 */
55 private $plugin_name = null;
56
57 /**
58 * How often the event should subsequently
59 *
60 * @var string
61 */
62 public $recurrence = 'daily';
63
64 private $event_hook = null;
65
66 /**
67 * Instance of Plugin_Usage_Tracker
68 *
69 * @var Plugin_Usage_Tracker
70 */
71 private static $_instance = null;
72
73 private $disabled_wp_cron;
74 private $enable_self_cron;
75 private $require_optin;
76 private $include_goodbye_form;
77 private $marketing;
78 private $options;
79 private $item_id;
80 private $notice_options;
81
82 /**
83 * Get Instance of Plugin_Usage_Tracker
84 *
85 * @return Plugin_Usage_Tracker
86 */
87 public static function get_instance($plugin_file, $args = []) {
88 if (is_null(static::$_instance)) {
89 static::$_instance = new static($plugin_file, $args);
90 }
91 return static::$_instance;
92 }
93
94 /**
95 * Automatically Invoked when initialized.
96 *
97 * @param string $plugin_file
98 * @param array $args
99 */
100 public function __construct($plugin_file, $args = []) {
101 $this->plugin_file = $plugin_file;
102 $this->plugin_name = basename($this->plugin_file, '.php');
103 $this->disabled_wp_cron = defined('DISABLE_WP_CRON') && DISABLE_WP_CRON == true;
104 $this->enable_self_cron = $this->disabled_wp_cron == true ? true : false;
105
106 $this->event_hook = 'thinkrank_put_do_daily_action';
107
108 $this->require_optin = isset($args['opt_in']) ? $args['opt_in'] : true;
109 $this->include_goodbye_form = isset($args['goodbye_form']) ? $args['goodbye_form'] : true;
110 $this->marketing = isset($args['email_marketing']) ? $args['email_marketing'] : true;
111 $this->options = isset($args['options']) ? $args['options'] : [];
112 $this->item_id = isset($args['item_id']) ? $args['item_id'] : false;
113
114 /**
115 * Activation Hook
116 */
117 register_activation_hook($this->plugin_file, array($this, 'activate_this_plugin'));
118 /**
119 * Deactivation Hook
120 */
121 register_deactivation_hook($this->plugin_file, array($this, 'deactivate_this_plugin'));
122 }
123
124 /**
125 * When user agreed to opt-in tracking schedule is enabled.
126 *
127 * @since 1.12.0
128 */
129 public function schedule_tracking() {
130 if ($this->disabled_wp_cron) {
131 return;
132 }
133 if (!wp_next_scheduled($this->event_hook)) {
134 wp_schedule_event(time(), $this->recurrence, $this->event_hook);
135 }
136 }
137
138 /**
139 * Add the schedule event if the plugin is tracked.
140 *
141 * @return void
142 */
143 public function activate_this_plugin() {
144 $allow_tracking = $this->is_tracking_allowed();
145 if (!$allow_tracking) {
146 return;
147 }
148 $this->schedule_tracking();
149 }
150
151 /**
152 * Remove the schedule event when plugin is deactivated and send the
153 * deactivated reason to insights if user submitted.
154 *
155 * @since 1.12.0
156 */
157 public function deactivate_this_plugin() {
158 /**
159 * Check tracking is allowed or not.
160 */
161 $allow_tracking = $this->is_tracking_allowed();
162 if (!$allow_tracking) {
163 return;
164 }
165 $body = $this->get_data();
166 $body['status'] = 'Deactivated';
167 $body['deactivated_date'] = time();
168
169 // Check deactivation reason and add for insights data.
170 if (false !== get_option('wpins_deactivation_reason_' . $this->plugin_name)) {
171 $body['deactivation_reason'] = get_option('wpins_deactivation_reason_' . $this->plugin_name);
172 }
173 if (false !== get_option('wpins_deactivation_details_' . $this->plugin_name)) {
174 $body['deactivation_details'] = get_option('wpins_deactivation_details_' . $this->plugin_name);
175 }
176
177 $this->send_data($body);
178 delete_option('wpins_deactivation_reason_' . $this->plugin_name);
179 delete_option('wpins_deactivation_details_' . $this->plugin_name);
180 /**
181 * Clear the event schedule.
182 */
183 if (!$this->disabled_wp_cron) {
184 wp_clear_scheduled_hook($this->event_hook);
185 }
186 }
187
188 /**
189 * Initial Method to Hook Everything.
190 *
191 * @return void
192 */
193 public function init() {
194 add_action('admin_init', array($this, 'clicked'));
195 add_action($this->event_hook, array($this, 'do_tracking'));
196 add_action('admin_notices', array($this, 'notice'));
197 /**
198 * Deactivation Reason Form and Submit Data to Insights.
199 */
200 add_filter('plugin_action_links_' . plugin_basename($this->plugin_file), array($this, 'deactivate_action_links'));
201 add_action('admin_footer-plugins.php', array($this, 'deactivate_reasons_form'));
202 add_action('wp_ajax_deactivation_form_' . esc_attr($this->plugin_name), array($this, 'deactivate_reasons_form_submit'));
203 }
204
205 /**
206 * For Redirecting Current Page without Arguments!
207 *
208 * @return string
209 */
210 private function redirect_to() {
211 $request_uri = !empty($_SERVER['REQUEST_URI']) ? wp_parse_url(sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])), PHP_URL_PATH) : '';
212 $query_string = !empty($_SERVER['REQUEST_URI']) ? wp_parse_url(sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])), PHP_URL_QUERY) : '';
213 parse_str((string) $query_string, $current_url);
214
215 $unset_array = array('dismiss', 'plugin', '_wpnonce', 'later', 'plugin_action', 'marketing_optin');
216
217 foreach ($unset_array as $value) {
218 if (isset($current_url[$value])) {
219 unset($current_url[$value]);
220 }
221 }
222
223 $current_url = http_build_query($current_url);
224 $redirect_url = $request_uri . '?' . $current_url;
225 return $redirect_url;
226 }
227
228 /**
229 * This method forcing the do_tracking method to execute instant.
230 *
231 * @return void
232 */
233 public function force_tracking() {
234 $this->do_tracking(true);
235 }
236
237 /**
238 * This method is responsible for all the magic from the front of the plugin.
239 *
240 * @since 1.12.0
241 * @param bool $force Force tracking if it's not the correct time to track.
242 */
243 public function do_tracking($force = false) {
244 /**
245 * Check URL is set or not.
246 */
247 if (empty(self::API_URL)) {
248 return;
249 }
250 /**
251 * Check is tracking allowed or not.
252 */
253 if (!$this->is_tracking_allowed()) {
254 return;
255 }
256 /**
257 * Check is this the correct time to track or not.
258 * or Force to track.
259 */
260 if (!$this->is_time_to_track() && !$force) {
261 return;
262 }
263 /**
264 * Get All Data.
265 */
266 $body = $this->get_data();
267 /**
268 * Send all data.
269 */
270 return $this->send_data($body);
271 }
272
273 /**
274 * Is tracking allowed?
275 *
276 * @since 1.12.0
277 */
278 private function is_tracking_allowed() {
279 // First, check if the user has changed their mind and opted out of tracking
280 if ($this->has_user_opted_out()) {
281 $this->set_is_tracking_allowed(false, $this->plugin_name);
282 return false;
283 }
284 // The wpins_allow_tracking option is an array of plugins that are being tracked
285 $allow_tracking = get_option('wpins_allow_tracking');
286 // If this plugin is in the array, then tracking is allowed
287 if (isset($allow_tracking[$this->plugin_name])) {
288 return true;
289 }
290 return false;
291 }
292
293 /**
294 * Set a flag in DB If tracking is allowed.
295 *
296 * @since 1.12.0
297 * @param bool $is_allowed true if is allowed.
298 * @param string|null $plugin
299 */
300 public function set_is_tracking_allowed($is_allowed, $plugin = null) {
301 if (empty($plugin)) {
302 $plugin = $this->plugin_name;
303 }
304 /**
305 * Get All Tracked Plugin List using this Tracker.
306 */
307 $allow_tracking = get_option('wpins_allow_tracking');
308 /**
309 * Check user is opted out for tracking or not.
310 */
311 if ($this->has_user_opted_out()) {
312 if (isset($allow_tracking[$plugin])) {
313 unset($allow_tracking[$plugin]);
314 }
315 } elseif ($is_allowed || !$this->require_optin) {
316 /**
317 * If user has agreed to allow tracking
318 */
319 if (empty($allow_tracking) || !is_array($allow_tracking)) {
320 $allow_tracking = array($plugin => $plugin);
321 } else {
322 $allow_tracking[$plugin] = $plugin;
323 }
324 } else {
325 if (isset($allow_tracking[$plugin])) {
326 unset($allow_tracking[$plugin]);
327 }
328 }
329 update_option('wpins_allow_tracking', $allow_tracking);
330 }
331
332 /**
333 * Check the user has opted out or not.
334 *
335 * @since 1.12.0
336 * @return bool
337 */
338 protected function has_user_opted_out() {
339 if (!empty($this->options)) {
340 foreach ($this->options as $option_name) {
341 $options = get_option($option_name);
342 if (!empty($options['wpins_opt_out'])) {
343 return true;
344 }
345 }
346 }
347 return false;
348 }
349
350 /**
351 * Check if it's time to track
352 *
353 * @since 1.12.0
354 */
355 public function is_time_to_track() {
356 $track_times = get_option('wpins_last_track_time', array());
357 return !isset($track_times[$this->plugin_name]) ? true :
358 (($track_times[$this->plugin_name]) < strtotime('-1 day') ? true : false);
359 }
360
361 /**
362 * Set tracking time.
363 *
364 * @since 1.12.0
365 */
366 public function set_track_time() {
367 $track_times = get_option('wpins_last_track_time', array());
368 $track_times[$this->plugin_name] = time();
369 update_option('wpins_last_track_time', $track_times);
370 }
371
372 /**
373 * This method is responsible for collecting all data.
374 *
375 * @since 1.12.0
376 */
377 public function get_data() {
378 $body = array(
379 'plugin_slug' => sanitize_text_field($this->plugin_name),
380 'url' => get_bloginfo('url'),
381 'site_name' => get_bloginfo('name'),
382 'site_version' => get_bloginfo('version'),
383 'site_language' => get_bloginfo('language'),
384 'charset' => get_bloginfo('charset'),
385 'wpins_version' => self::WPINS_VERSION,
386 'php_version' => phpversion(),
387 'multisite' => is_multisite(),
388 'file_location' => __FILE__,
389 );
390
391 // Collect the email if the correct option has been set
392 if ($this->marketing) {
393 if (!function_exists('wp_get_current_user')) {
394 include ABSPATH . 'wp-includes/pluggable.php';
395 }
396 $current_user = wp_get_current_user();
397 $email = $current_user->user_email;
398 if (is_email($email)) {
399 $body['email'] = $email;
400 } else {
401 $email = get_option('admin_email');
402 if (is_email($email)) {
403 $body['email'] = $email;
404 }
405 }
406 }
407 $body['marketing_method'] = $this->marketing;
408 $body['server'] = isset($_SERVER['SERVER_SOFTWARE']) ? sanitize_text_field(wp_unslash($_SERVER['SERVER_SOFTWARE'])) : '';
409
410 /**
411 * Collect all active and inactive plugins
412 */
413 if (!function_exists('get_plugins')) {
414 include ABSPATH . '/wp-admin/includes/plugin.php';
415 }
416 $plugins = array_keys(get_plugins());
417 $active_plugins = is_network_admin() ? array_keys(get_site_option('active_sitewide_plugins', array())) : get_option('active_plugins', array());
418 foreach ($plugins as $key => $plugin) {
419 if (in_array($plugin, $active_plugins)) {
420 unset($plugins[$key]);
421 }
422 }
423 $body['active_plugins'] = $active_plugins;
424 $body['inactive_plugins'] = $plugins;
425
426 /**
427 * Text Direction.
428 */
429 $body['text_direction'] = (function_exists('is_rtl') ? (is_rtl() ? 'RTL' : 'LTR') : 'NOT SET');
430 /**
431 * Get Our Plugin Data.
432 *
433 * @since 1.12.0
434 */
435 $plugin = $this->plugin_data();
436 if (empty($plugin)) {
437 $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.', 'thinkrank');
438 $body['status'] = 'NOT FOUND';
439 } else {
440 if (isset($plugin['Name'])) {
441 $body['plugin'] = sanitize_text_field($plugin['Name']);
442 }
443 if (isset($plugin['Version'])) {
444 $body['version'] = sanitize_text_field($plugin['Version']);
445 }
446 $body['status'] = 'Active';
447 }
448
449 /**
450 * Get active theme name and version
451 *
452 * @since 1.12.0
453 */
454 $theme = wp_get_theme();
455 if ($theme->Name) {
456 $body['theme'] = sanitize_text_field($theme->Name);
457 }
458 if ($theme->Version) {
459 $body['theme_version'] = sanitize_text_field($theme->Version);
460 }
461
462 $optional_data = $this->get_used_elements_count();
463 if (!empty($optional_data)) {
464 $body['optional_data'] = $optional_data;
465 }
466
467 return $body;
468 }
469
470 /**
471 * Collect plugin data,
472 * Retrieve current plugin information
473 *
474 * @since 1.12.0
475 */
476 public function plugin_data() {
477 if (!function_exists('get_plugin_data')) {
478 include ABSPATH . '/wp-admin/includes/plugin.php';
479 }
480 $plugin = get_plugin_data($this->plugin_file);
481 return $plugin;
482 }
483
484 /**
485 * Send the data to insights.
486 *
487 * @since 1.12.0
488 */
489 public function send_data($body) {
490 /**
491 * Get SITE ID
492 */
493 $site_id_key = "wpins_{$this->plugin_name}_site_id";
494 $site_id = get_option($site_id_key, false);
495 $failed_data = [];
496 $diff_data = [];
497 $site_url = get_bloginfo('url');
498 $original_site_url = get_option("wpins_{$this->plugin_name}_original_url", false);
499
500 if (($original_site_url === false || $original_site_url != $site_url) && version_compare($body['wpins_version'], '3.0.1', '>=')) {
501 $site_id = false;
502 }
503 /**
504 * Send Initial Data to API
505 */
506 if ($site_id == false && $this->item_id !== false) {
507 if (isset($_SERVER['REMOTE_ADDR']) && !empty($_SERVER['REMOTE_ADDR'] && $_SERVER['REMOTE_ADDR'] != '127.0.0.1')) {
508 $country_request = wp_remote_get('https://ip-api.com/json/' . sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) . '?fields=country');
509 if (!is_wp_error($country_request) && $country_request['response']['code'] == 200) {
510 $ip_data = json_decode($country_request["body"]);
511 $body['country'] = isset($ip_data->country) ? $ip_data->country : 'NOT SET';
512 }
513 }
514
515 $body['plugin_slug'] = $this->plugin_name;
516 $body['url'] = $site_url;
517 $body['item_id'] = $this->item_id;
518
519 $request = $this->remote_post($body);
520 if (!is_wp_error($request) && $request['response']['code'] == 200) {
521 $retrieved_body = json_decode(wp_remote_retrieve_body($request), true);
522 if (is_array($retrieved_body) && isset($retrieved_body['siteId'])) {
523 update_option($site_id_key, $retrieved_body['siteId']);
524 update_option("wpins_{$this->plugin_name}_original_url", $site_url);
525 update_option("wpins_{$this->plugin_name}_{$retrieved_body['siteId']}", $body);
526 }
527 } else {
528 $failed_data = $body;
529 }
530 }
531
532 $site_id_data_key = "wpins_{$this->plugin_name}_{$site_id}";
533 $site_id_data_failed_key = "wpins_{$this->plugin_name}_{$site_id}_send_failed";
534
535 if ($site_id != false) {
536 $old_sent_data = get_option($site_id_data_key, []);
537 $diff_data = $this->diff($body, $old_sent_data);
538 $failed_data = get_option($site_id_data_failed_key, []);
539 if (!empty($failed_data) && $diff_data != $failed_data) {
540 $failed_data = array_merge($failed_data, $diff_data);
541 }
542 }
543
544 if (!empty($failed_data) && $site_id != false) {
545 $failed_data['plugin_slug'] = $this->plugin_name;
546 $failed_data['url'] = $site_url;
547 $failed_data['site_id'] = $site_id;
548 if ($original_site_url != false) {
549 $failed_data['original_url'] = $original_site_url;
550 }
551
552 $request = $this->remote_post($failed_data);
553 if (!is_wp_error($request)) {
554 delete_option($site_id_data_failed_key);
555 $replaced_data = array_merge($old_sent_data, $failed_data);
556 update_option($site_id_data_key, $replaced_data);
557 }
558 }
559
560 if (!empty($diff_data) && $site_id != false && empty($failed_data)) {
561 $diff_data['plugin_slug'] = $this->plugin_name;
562 $diff_data['url'] = $site_url;
563 $diff_data['site_id'] = $site_id;
564 if ($original_site_url != false) {
565 $diff_data['original_url'] = $original_site_url;
566 }
567
568 $request = $this->remote_post($diff_data);
569 if (is_wp_error($request)) {
570 update_option($site_id_data_failed_key, $diff_data);
571 } else {
572 $replaced_data = array_merge($old_sent_data, $diff_data);
573 update_option($site_id_data_key, $replaced_data);
574 }
575 }
576
577 $this->set_track_time();
578
579 if (isset($request) && is_wp_error($request)) {
580 return $request;
581 }
582
583 if (isset($request)) {
584 return true;
585 }
586 return false;
587 }
588
589 /**
590 * WP_REMOTE_POST method responsible for send data to the API_URL
591 *
592 * @param array $data
593 * @param array $args
594 * @return mixed
595 */
596 protected function remote_post($data = array(), $args = array()) {
597 if (empty($data)) {
598 return;
599 }
600
601 $args = wp_parse_args($args, array(
602 'method' => 'POST',
603 'timeout' => 30,
604 'redirection' => 5,
605 'httpversion' => '1.1',
606 'blocking' => true,
607 'body' => $data,
608 'user-agent' => 'PUT/1.0.0; ' . get_bloginfo('url'),
609 ));
610 $request = wp_remote_post(esc_url(self::API_URL), $args);
611 if (is_wp_error($request) || (isset($request['response'], $request['response']['code']) && $request['response']['code'] != 200)) {
612 return new \WP_Error(500, 'Something went wrong.');
613 }
614 return $request;
615 }
616
617 /**
618 * Difference between old and new data
619 *
620 * @param array $new_data
621 * @param array $old_data
622 * @return array
623 */
624 protected function diff($new_data, $old_data) {
625 $data = [];
626 if (!empty($new_data)) {
627 foreach ($new_data as $key => $value) {
628 if (isset($old_data[$key])) {
629 if ($old_data[$key] == $value) {
630 continue;
631 }
632 }
633 $data[$key] = $value;
634 }
635 }
636 return $data;
637 }
638
639 /**
640 * Display the admin notice to users to allow them to opt in
641 *
642 * @since 1.12.0
643 */
644 public function notice() {
645 /**
646 * Return if notice is not set.
647 */
648 if (!isset($this->notice_options['notice'])) {
649 return;
650 }
651 /**
652 * Already opted in — there is nothing left to ask for.
653 */
654 if ($this->is_tracking_allowed()) {
655 return;
656 }
657 /**
658 * Check is allowed or blocked for notice.
659 */
660 $block_notice = get_option('wpins_block_notice');
661 if (isset($block_notice[$this->plugin_name])) {
662 return;
663 }
664 if (!current_user_can('manage_options')) {
665 return;
666 }
667
668 $url_yes = add_query_arg([
669 'plugin' => $this->plugin_name,
670 'plugin_action' => 'yes',
671 ]);
672 $url_no = add_query_arg(array(
673 'plugin' => $this->plugin_name,
674 'plugin_action' => 'no',
675 ));
676
677 $url_yes = wp_nonce_url($url_yes, '_wpnonce_optin_' . $this->plugin_name);
678 $url_no = wp_nonce_url($url_no, '_wpnonce_optin_' . $this->plugin_name);
679
680 // Shared branded notice styles (same card layout as the welcome notice).
681 wp_enqueue_style(
682 'thinkrank-admin-notices',
683 THINKRANK_PLUGIN_URL . 'static/css/admin-notices.css',
684 [],
685 THINKRANK_VERSION
686 );
687
688 // Decide on notice text
689 $notice_title = $this->notice_options['notice_title'] ?? '';
690 $notice_text = $this->notice_options['notice'] . ' <a href="#" class="wpinsights-' . esc_attr($this->plugin_name) . '-collect">' . $this->notice_options['consent_button_text'] . '</a>';
691 $extra_notice_text = $this->notice_options['extra_notice'] ?? '';
692
693 $output = '';
694 $output .= '<div class="notice notice-success put-dismiss-notice thinkrank-notice">';
695 $output .= '<div class="thinkrank-notice__inner">';
696 $output .= '<div class="thinkrank-notice__body">';
697 if ($notice_title !== '') {
698 $output .= '<p class="thinkrank-notice__title">' . esc_html($notice_title) . '</p>';
699 }
700 $output .= '<p class="thinkrank-notice__text">' . $notice_text . '</p>';
701 $output .= '<div class="wpinsights-data" style="display: none;">';
702 $output .= '<p class="thinkrank-notice__text">' . $extra_notice_text . '</p>';
703 $output .= '</div>';
704 $output .= '<p class="thinkrank-notice__actions">';
705 $output .= '<a href="' . esc_url($url_yes) . '" class="button button-primary">' . $this->notice_options['yes'] . '</a>';
706 $output .= '<a href="' . esc_url($url_no) . '" class="thinkrank-notice__dismiss">' . $this->notice_options['no'] . '</a>';
707 $output .= '</p>';
708 $output .= '</div>';
709 $output .= '</div>';
710 $output .= "<script type='text/javascript'>jQuery('.wpinsights-" . esc_attr($this->plugin_name) . "-collect').on('click', function(e) {e.preventDefault();jQuery('.wpinsights-data').slideToggle('fast');});</script>";
711 $output .= '</div>';
712
713 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
714 echo $output;
715 }
716
717 /**
718 * Set all notice options to customized notice.
719 *
720 * @since 1.12.0
721 * @param array $options
722 * @return void
723 */
724 public function set_notice_options($options = []) {
725 $default_options = [
726 'consent_button_text' => __('What we collect.', 'thinkrank'),
727 'yes' => __('Sure, I\'d like to help', 'thinkrank'),
728 'no' => __('No Thanks.', 'thinkrank'),
729 ];
730 $options = wp_parse_args($options, $default_options);
731 $this->notice_options = $options;
732 }
733
734 /**
735 * Responsible for track the click from Notice.
736 *
737 * @return void
738 */
739 public function clicked() {
740 if (isset($_GET['_wpnonce']) && isset($_GET['plugin']) && sanitize_text_field(wp_unslash($_GET['plugin'])) === $this->plugin_name && isset($_GET['plugin_action'])) {
741 if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), '_wpnonce_optin_' . $this->plugin_name)) {
742 return;
743 }
744
745 if (isset($_GET['tab']) && $_GET['tab'] === 'plugin-information') {
746 return;
747 }
748 $plugin = sanitize_text_field(wp_unslash($_GET['plugin']));
749 $action = sanitize_text_field(wp_unslash($_GET['plugin_action']));
750 if ($action == 'yes') {
751 $this->schedule_tracking();
752 $this->set_is_tracking_allowed(true, $plugin);
753 if ($this->do_tracking(true)) {
754 $this->update_block_notice($plugin);
755 }
756 /**
757 * Redirect User To the Current URL, but without set query arguments.
758 */
759 wp_safe_redirect($this->redirect_to());
760 } else {
761 $this->set_is_tracking_allowed(false, $plugin);
762 $this->update_block_notice($plugin);
763 }
764 }
765 }
766
767 /**
768 * Programmatically opt the site in to tracking.
769 *
770 * Mirrors the "yes" branch of clicked() without the request redirect, so
771 * other flows (e.g. the Setup Wizard "Get Started" action) can record
772 * consent: schedule the cron, flag tracking allowed, send the first
773 * payload and suppress the opt-in notice from then on.
774 *
775 * @since 1.12.0
776 * @return void
777 */
778 public function opt_in() {
779 $this->schedule_tracking();
780 $this->set_is_tracking_allowed(true, $this->plugin_name);
781 $this->do_tracking(true);
782 $this->update_block_notice($this->plugin_name);
783 }
784
785 /**
786 * Set if we should block the opt-in notice for this plugin
787 *
788 * @since 1.12.0
789 */
790 public function update_block_notice($plugin = null) {
791 if (empty($plugin)) {
792 $plugin = $this->plugin_name;
793 }
794 $block_notice = get_option('wpins_block_notice');
795 if (empty($block_notice) || !is_array($block_notice)) {
796 $block_notice = array($plugin => $plugin);
797 } else {
798 $block_notice[$plugin] = $plugin;
799 }
800 update_option('wpins_block_notice', $block_notice);
801 }
802
803 /**
804 * AJAX callback when the deactivated form is submitted.
805 *
806 * @since 1.12.0
807 */
808 public function deactivate_reasons_form_submit() {
809 check_ajax_referer('wpins_deactivation_nonce', 'security');
810
811 // The form is only reachable from the plugins screen; require the same
812 // capability that screen does so the nonce isn't the only control.
813 if (!current_user_can('activate_plugins')) {
814 wp_die('', '', ['response' => 403]);
815 }
816
817 if (isset($_POST['values'])) {
818 $values = sanitize_text_field(wp_unslash($_POST['values']));
819 update_option('wpins_deactivation_reason_' . $this->plugin_name, $values);
820 }
821 if (isset($_POST['details'])) {
822 $details = sanitize_text_field(wp_unslash($_POST['details']));
823 update_option('wpins_deactivation_details_' . $this->plugin_name, $details);
824 }
825 echo 'success';
826 wp_die();
827 }
828
829 /**
830 * Filter the deactivation link to allow us to present a form when the user
831 * deactivates the plugin
832 *
833 * @since 1.12.0
834 */
835 public function deactivate_action_links($links) {
836 /**
837 * Check is tracking allowed or not.
838 */
839 if (!$this->is_tracking_allowed()) {
840 return $links;
841 }
842 if (isset($links['deactivate']) && $this->include_goodbye_form) {
843 $deactivation_link = $links['deactivate'];
844 /**
845 * Change the default deactivate button link.
846 */
847 $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);
848 $links['deactivate'] = $deactivation_link;
849 }
850 return $links;
851 }
852
853 /**
854 * ALL Deactivate Reasons.
855 *
856 * @since 1.12.0
857 */
858 public function deactivation_reasons() {
859 $form = array();
860 $form['heading'] = __('We Value Your Feedback', 'thinkrank');
861 $form['body'] = __("Could you please tell us why you're deactivating our plugin? Your insights will help us to improve.", 'thinkrank');
862
863 $form['options'] = array(
864 __('I no longer need the plugin', 'thinkrank'),
865 [
866 'label' => __('I found a better plugin', 'thinkrank'),
867 'extra_field' => __('Please share which plugin', 'thinkrank'),
868 ],
869 __("I couldn't get the plugin to work", 'thinkrank'),
870 __('It\'s a temporary deactivation', 'thinkrank'),
871 [
872 'label' => __('Other', 'thinkrank'),
873 'extra_field' => __('Please share the reason', 'thinkrank'),
874 'type' => 'textarea',
875 ],
876 );
877 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
878 return apply_filters('wpins_form_text_' . $this->plugin_name, $form);
879 }
880
881 /**
882 * Deactivate Reasons Form.
883 * This form will appears when user wants to deactivate the plugin to send
884 * you deactivated reasons.
885 *
886 * @since 1.12.0
887 */
888 public function deactivate_reasons_form() {
889 $form = $this->deactivation_reasons();
890 $class_plugin_name = esc_attr($this->plugin_name);
891 $html = '<div class="tr__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="#EDEDFD"/><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="#4451FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span><h1>' . esc_html($form['heading']) . '</h1></div>';
892 $html .= '<div class="wpinsights-goodbye-form-body"><p class="wpinsights-goodbye-form-caption">' . esc_html($form['body']) . '</p>';
893 if (is_array($form['options'])) {
894 $html .= '<div id="wpinsights-goodbye-options" class="wpinsights-goodbye-options"><ul>';
895 foreach ($form['options'] as $option) {
896 if (is_array($option)) {
897 $id = strtolower(str_replace(" ", "_", esc_attr($option['label'])));
898 $id = $id . '_' . $class_plugin_name;
899 $html .= '<li class="has-goodbye-extra">';
900 $html .= '<input type="radio" name="wpinsights-' . esc_attr($class_plugin_name) . '-goodbye-options" id="' . esc_attr($id) . '" value="' . esc_attr($option['label']) . '">';
901 $html .= '<div><label for="' . $id . '">' . esc_attr($option['label']) . '</label>';
902 if (isset($option['extra_field']) && !isset($option['type'])) {
903 $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']) . '">';
904 }
905 if (isset($option['extra_field']) && isset($option['type'])) {
906 $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'] . '>';
907 }
908 $html .= '</div></li>';
909 } else {
910 $id = strtolower(str_replace(" ", "_", esc_attr($option)));
911 $id = $id . '_' . $class_plugin_name;
912 $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>';
913 }
914 }
915 $html .= '</ul></div><!-- .wpinsights-' . $class_plugin_name . '-goodbye-options -->';
916 }
917 $html .= '</div><!-- .wpinsights-goodbye-form-body -->';
918 $html .= '<p class="deactivating-spinner"><span class="spinner"></span> ' . esc_html__('Submitting form', 'thinkrank') . '</p>';
919
920 $wrapper_class = '.wpinsights-goodbye-form-wrapper-' . $class_plugin_name;
921
922 $styles = '';
923 $styles .= '<style type="text/css">';
924 $styles .= '.wpinsights-form-active-' . $class_plugin_name . ' .wpinsights-goodbye-form-bg {';
925 $styles .= 'background: rgba( 0, 0, 0, .8 );position: fixed;top: 0;left: 0;width: 100%;height: 100%;z-index: 9;';
926 $styles .= '}';
927 $styles .= $wrapper_class . '{';
928 $styles .= 'position: relative; display: none;';
929 $styles .= '}';
930 $styles .= '.wpinsights-form-active-' . $class_plugin_name . ' ' . $wrapper_class . '{';
931 $styles .= 'display: flex !important; position: fixed;top: 0;left: 0;width: 100%;height: 100%; justify-content: center; align-items: center;';
932 $styles .= '}';
933 $styles .= $wrapper_class . ' .wpinsights-goodbye-form { display: none; }';
934 $styles .= '.wpinsights-form-active-' . $class_plugin_name . ' .wpinsights-goodbye-form {';
935 $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;';
936 $styles .= '}';
937 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-head {';
938 $styles .= 'background: #fff; color: #495157; padding: 18px; box-shadow: 0 0 8px rgba(0,0,0,.1); font-size: 15px;';
939 $styles .= '}';
940 $styles .= $wrapper_class . ' .wpinsights-goodbye-form .wpinsights-goodbye-form-head strong { font-size: 15px; }';
941 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-body { padding: 8px 18px; color: #333; }';
942 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-body label { padding-left: 5px; color: #6d7882; }';
943 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-body .wpinsights-goodbye-form-caption {';
944 $styles .= 'font-weight: 500; font-size: 15px; color: #495157; line-height: 1.4;';
945 $styles .= '}';
946 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options { padding-top: 5px; }';
947 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li { margin-bottom: 15px; }';
948 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li > div { display: inline; padding-left: 3px; }';
949 $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 {';
950 $styles .= 'margin: 10px 18px; padding: 8px; width: 80%;';
951 $styles .= '}';
952 $styles .= $wrapper_class . ' .deactivating-spinner { display: none; padding-bottom: 20px !important; }';
953 $styles .= $wrapper_class . ' .deactivating-spinner .spinner { float: none; margin: 4px 4px 0 18px; vertical-align: bottom; visibility: visible; }';
954 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-footer { padding: 8px 18px; margin-bottom: 15px; }';
955 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-footer > .wpinsights-goodbye-form-buttons { display: flex; align-items: center; justify-content: space-between; }';
956 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-footer .wpinsights-submit-btn {';
957 $styles .= 'background-color: #4451ff; -webkit-border-radius: 4px; border-radius: 4px; color: #fff; line-height: 1; padding: 10px 20px; font-size: 13px; font-weight: 500; text-transform: uppercase; transition: .3s;';
958 $styles .= '}';
959 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-footer .wpinsights-submit-btn:hover {';
960 $styles .= 'background-color: #3a46e0;';
961 $styles .= '}';
962 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-footer .wpinsights-deactivate-btn {';
963 $styles .= 'font-size: 13px; color: #a4afb7; background: none; float: right; padding-right: 10px; width: auto; text-decoration: underline;';
964 $styles .= '}';
965 $styles .= '
966 .wpinsights-form-active-' . $class_plugin_name . ' .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' {
967 display: flex !important;
968 position: fixed;
969 top: 0;
970 left: 0;
971 right: 0;
972 bottom: 0;
973 width: 100%;
974 z-index: 9999;
975 height: 100%;
976 justify-content: center;
977 align-items: center;
978 }
979 .wpinsights-form-active-' . $class_plugin_name . ' .wpinsights-goodbye-form {
980 border-radius: 8px;
981 width: 563px;
982 overflow: visible;
983 position: relative;
984 font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
985 }
986 .wpinsights-goodbye-form .tr__modal-close-btn {
987 position: absolute;
988 top: 0;
989 right: -55px;
990 width: 40px;
991 height: 40px;
992 background: #fff;
993 border-radius: 50%;
994 display: flex;
995 justify-content: center;
996 align-items: center;
997 font-size: 12px;
998 font-weight: 500;
999 color: #475467;
1000 cursor: pointer;
1001 }
1002 .wpinsights-goodbye-form .tr__modal-close-btn svg {
1003 width: 20px;
1004 }
1005 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-head {
1006 background: #fff;
1007 color: #495157;
1008 padding: 24px;
1009 border-radius: 8px 8px 0 0;
1010 box-shadow: none;
1011 border-bottom: 1px solid #EAECF0;
1012 display: flex;
1013 flex-direction: column;
1014 gap: 16px;
1015 align-items: center;
1016 }
1017 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-head > span {
1018 display: inline-flex;
1019 }
1020 .wpinsights-goodbye-form-head h1 {
1021 font-size: 22px;
1022 font-weight: 450;
1023 color: #1D2939;
1024 padding: 0;
1025 }
1026 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-body {
1027 padding: 32px 40px;
1028 max-height: calc(70vh - 230px);
1029 overflow: auto;
1030 }
1031 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-body .wpinsights-goodbye-form-caption {
1032 font-size: 18px;
1033 color: #1D2939;
1034 margin: 0;
1035 padding-bottom: 16px;
1036 font-weight: 400;
1037 }
1038 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options {
1039 padding-top: 0px;
1040 }
1041 .widefat td ul {
1042 font-size: 14px;
1043 margin: 0;
1044 color: #475467;
1045 line-height: 1.4em;
1046 }
1047 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li:not(:last-child) {
1048 margin-bottom: 16px;
1049 }
1050 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li:last-child {
1051 margin-bottom: 0;
1052 }
1053 input[type=radio] {
1054 border-radius: 50%;
1055 margin-right: .25rem;
1056 line-height: .71428571;
1057 margin: 0;
1058 }
1059 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-body label {
1060 padding-left: 4px;
1061 color: #475467;
1062 font-size: 14px;
1063 }
1064 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li > div > input, .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li > div > textarea {
1065 padding: 10px 16px;
1066 border-radius: 8px;
1067 width: 100%;
1068 border: 1px solid #D0D5DD;
1069 margin: 0;
1070 margin-top: 16px;
1071 color: #1D2939;
1072 font-size: 14px;
1073 line-height: 1.5em;
1074 resize: none;
1075 max-height: 44px;
1076 }
1077 input[type=radio] {
1078 border: 1px solid #D0D5DD;
1079 box-shadow: none;
1080 }
1081 input[type=radio]:checked::before {
1082 content: "";
1083 border-radius: 50%;
1084 width: .88rem;
1085 height: .88rem;
1086 margin: -.95px;
1087 background-color: #4451ff;
1088 border: 1px solid #c7ccff;
1089 line-height: 1.14285714;
1090 outline: 0;
1091 }
1092 input[type=radio]:focus {
1093 border-color: #D0D5DD;
1094 box-shadow: none;
1095 outline: 0;
1096 }
1097 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li > div > input::placeholder,
1098 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li > div > textarea::placeholder{
1099 color: #C6CBD2 !important;
1100 }
1101 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-footer {
1102 padding: 16px 36px;
1103 margin-bottom: 0;
1104 border-top: 1px solid #EAECF0;
1105 }
1106 input:focus,
1107 textarea:focus {
1108 box-shadow: none !important;
1109 outline: 0 !important;
1110 }
1111 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-footer > .wpinsights-goodbye-form-buttons {
1112 display: flex;
1113 align-items: center;
1114 justify-content: flex-start;
1115 }
1116 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-footer .wpinsights-submit-btn {
1117 background: #4451ff;
1118 -webkit-border-radius: 8px;
1119 border-radius: 8px;
1120 color: #fff;
1121 padding: 8px 16px;
1122 font-size: 14px;
1123 font-weight: 500;
1124 line-height: 1.5em;
1125 text-transform: capitalize;
1126 transition: .3s;
1127 }
1128 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-footer .wpinsights-submit-btn:hover {
1129 background: #3a46e0;
1130 }
1131 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-footer .wpsp-put-deactivate-btn {
1132 font-size: 14px;
1133 font-weight: 500;
1134 color: #344054;
1135 margin-left: 10px;
1136 }
1137 .wp-person a:focus .gravatar, a:focus, a:focus .media-icon img, a:focus .plugin-icon {
1138 color: #043959;
1139 box-shadow: none;
1140 outline: 0;
1141 }
1142 ';
1143 $styles .= '</style>';
1144
1145 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1146 echo $styles;
1147 ?>
1148 <script type="text/javascript">
1149 jQuery(document).ready(function($){
1150 $("#wpinsights-goodbye-link-<?php echo esc_js($class_plugin_name); ?>").on("click",function(){
1151 // We'll send the user to this deactivation link when they've completed or dismissed the form
1152 var url = document.getElementById("wpinsights-goodbye-link-<?php echo esc_js($class_plugin_name); ?>");
1153 $('body').toggleClass('wpinsights-form-active-<?php echo esc_js($class_plugin_name); ?>');
1154 $(".wpinsights-goodbye-form-wrapper-<?php echo esc_js($class_plugin_name); ?> #wpinsights-goodbye-form").fadeIn();
1155 $(".wpinsights-goodbye-form-wrapper-<?php echo esc_js($class_plugin_name); ?> #wpinsights-goodbye-form").html( '<?php
1156 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1157 echo $html; ?>' + '<div class="wpinsights-goodbye-form-footer"><div class="wpinsights-goodbye-form-buttons"><a id="wpinsights-submit-form-<?php echo esc_js($class_plugin_name); ?>" class="wpinsights-submit-btn" href="#"><?php esc_html_e('Submit and Deactivate', 'thinkrank'); ?></a>&nbsp;<a class="wpsp-put-deactivate-btn" href="'+url+'"><?php esc_html_e('Just Deactivate', 'thinkrank'); ?></a></div></div>');
1158 $('#wpinsights-submit-form-<?php echo esc_js($class_plugin_name); ?>').on('click', function(e){
1159 // As soon as we click, the body of the form should disappear
1160 $("#wpinsights-goodbye-form-<?php echo esc_js($class_plugin_name); ?> .wpinsights-goodbye-form-body").fadeOut();
1161 $("#wpinsights-goodbye-form-<?php echo esc_js($class_plugin_name); ?> .wpinsights-goodbye-form-footer").fadeOut();
1162 // Fade in spinner
1163 $("#wpinsights-goodbye-form-<?php echo esc_js($class_plugin_name); ?> .deactivating-spinner").fadeIn();
1164 e.preventDefault();
1165 var checkedInput = $("input[name='wpinsights-<?php echo esc_attr($class_plugin_name); ?>-goodbye-options']:checked"),
1166 checkedInputVal, details;
1167 if( checkedInput.length > 0 ) {
1168 checkedInputVal = checkedInput.val();
1169 details = $('input[name="'+ checkedInput[0].id +'"], textarea[name="'+ checkedInput[0].id +'"]').val();
1170 }
1171
1172 if( typeof details === 'undefined' ) {
1173 details = '';
1174 }
1175 if( typeof checkedInputVal === 'undefined' ) {
1176 checkedInputVal = 'No Reason';
1177 }
1178
1179 var data = {
1180 'action': 'deactivation_form_<?php echo esc_attr($class_plugin_name); ?>',
1181 'values': checkedInputVal,
1182 'details': details,
1183 'security': "<?php
1184 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1185 echo esc_js(wp_create_nonce('wpins_deactivation_nonce')); ?>",
1186 'dataType': "json"
1187 }
1188
1189 $.post(
1190 ajaxurl,
1191 data,
1192 function(response){
1193 // Redirect to original deactivation URL
1194 window.location.href = url;
1195 }
1196 );
1197 });
1198 $('#wpinsights-goodbye-options > ul ').on('click', 'li label, li > input', function( e ){
1199 var parent = $(this).parents('li');
1200 parent.siblings().find('label').next('input, textarea').css('display', 'none');
1201 parent.find('label').next('input, textarea').css('display', 'block');
1202 });
1203 // If we click outside the form, the form will close
1204 $('.wpinsights-goodbye-form-bg, #wpinsights-goodbye-form > .tr__modal-close-btn').on('click',function(){
1205 $("#wpinsights-goodbye-form").fadeOut();
1206 $('body').removeClass('wpinsights-form-active-<?php echo esc_attr($class_plugin_name); ?>');
1207 });
1208 });
1209 });
1210 </script>
1211 <?php }
1212
1213 /**
1214 * Get Used Elements Count
1215 *
1216 * Optional data payload describing which ThinkRank features are enabled
1217 * on the site. Reported as a name => count map (1 = enabled) so the
1218 * insights backend can aggregate feature adoption.
1219 *
1220 * @return array
1221 *
1222 * @since 1.12.0
1223 */
1224 public static function get_used_elements_count() {
1225 $used = [];
1226
1227 // AI provider in use.
1228 $ai_provider = get_option('thinkrank_ai_provider', '');
1229 if (!empty($ai_provider)) {
1230 $used['ai_provider_' . sanitize_key($ai_provider)] = 1;
1231 }
1232
1233 // Boolean feature flags stored as standalone options.
1234 $flag_options = [
1235 'thinkrank_auto_optimize' => 'auto_optimize',
1236 'thinkrank_enable_logging' => 'logging',
1237 ];
1238 foreach ($flag_options as $option_name => $label) {
1239 if (!empty(get_option($option_name))) {
1240 $used[$label] = 1;
1241 }
1242 }
1243
1244 // Feature settings groups: report when configured/enabled.
1245 $feature_settings = [
1246 'thinkrank_instant_indexing_settings' => 'instant_indexing',
1247 'thinkrank_author_archives_settings' => 'author_archives',
1248 'thinkrank_image_seo_settings' => 'image_seo',
1249 ];
1250 foreach ($feature_settings as $option_name => $label) {
1251 $settings = get_option($option_name, []);
1252 if (is_array($settings) && !empty($settings['enabled'])) {
1253 $used[$label] = 1;
1254 }
1255 }
1256
1257 // Indexed content volume buckets (lightweight published-post count).
1258 $published = (int) wp_count_posts('post')->publish;
1259 if ($published > 0) {
1260 $used['published_posts'] = $published;
1261 }
1262
1263 return $used;
1264 }
1265 }
1266