PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.26.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.26.0
2.7.0 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 All 48 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 1.26.0, at includes/core/class-plugin-usage-tracker.php

1,259 lines 54.1 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 if (isset($_POST['values'])) {
811 $values = sanitize_text_field(wp_unslash($_POST['values']));
812 update_option('wpins_deactivation_reason_' . $this->plugin_name, $values);
813 }
814 if (isset($_POST['details'])) {
815 $details = sanitize_text_field(wp_unslash($_POST['details']));
816 update_option('wpins_deactivation_details_' . $this->plugin_name, $details);
817 }
818 echo 'success';
819 wp_die();
820 }
821
822 /**
823 * Filter the deactivation link to allow us to present a form when the user
824 * deactivates the plugin
825 *
826 * @since 1.12.0
827 */
828 public function deactivate_action_links($links) {
829 /**
830 * Check is tracking allowed or not.
831 */
832 if (!$this->is_tracking_allowed()) {
833 return $links;
834 }
835 if (isset($links['deactivate']) && $this->include_goodbye_form) {
836 $deactivation_link = $links['deactivate'];
837 /**
838 * Change the default deactivate button link.
839 */
840 $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);
841 $links['deactivate'] = $deactivation_link;
842 }
843 return $links;
844 }
845
846 /**
847 * ALL Deactivate Reasons.
848 *
849 * @since 1.12.0
850 */
851 public function deactivation_reasons() {
852 $form = array();
853 $form['heading'] = __('We Value Your Feedback', 'thinkrank');
854 $form['body'] = __("Could you please tell us why you're deactivating our plugin? Your insights will help us to improve.", 'thinkrank');
855
856 $form['options'] = array(
857 __('I no longer need the plugin', 'thinkrank'),
858 [
859 'label' => __('I found a better plugin', 'thinkrank'),
860 'extra_field' => __('Please share which plugin', 'thinkrank'),
861 ],
862 __("I couldn't get the plugin to work", 'thinkrank'),
863 __('It\'s a temporary deactivation', 'thinkrank'),
864 [
865 'label' => __('Other', 'thinkrank'),
866 'extra_field' => __('Please share the reason', 'thinkrank'),
867 'type' => 'textarea',
868 ],
869 );
870 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
871 return apply_filters('wpins_form_text_' . $this->plugin_name, $form);
872 }
873
874 /**
875 * Deactivate Reasons Form.
876 * This form will appears when user wants to deactivate the plugin to send
877 * you deactivated reasons.
878 *
879 * @since 1.12.0
880 */
881 public function deactivate_reasons_form() {
882 $form = $this->deactivation_reasons();
883 $class_plugin_name = esc_attr($this->plugin_name);
884 $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>';
885 $html .= '<div class="wpinsights-goodbye-form-body"><p class="wpinsights-goodbye-form-caption">' . esc_html($form['body']) . '</p>';
886 if (is_array($form['options'])) {
887 $html .= '<div id="wpinsights-goodbye-options" class="wpinsights-goodbye-options"><ul>';
888 foreach ($form['options'] as $option) {
889 if (is_array($option)) {
890 $id = strtolower(str_replace(" ", "_", esc_attr($option['label'])));
891 $id = $id . '_' . $class_plugin_name;
892 $html .= '<li class="has-goodbye-extra">';
893 $html .= '<input type="radio" name="wpinsights-' . esc_attr($class_plugin_name) . '-goodbye-options" id="' . esc_attr($id) . '" value="' . esc_attr($option['label']) . '">';
894 $html .= '<div><label for="' . $id . '">' . esc_attr($option['label']) . '</label>';
895 if (isset($option['extra_field']) && !isset($option['type'])) {
896 $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']) . '">';
897 }
898 if (isset($option['extra_field']) && isset($option['type'])) {
899 $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'] . '>';
900 }
901 $html .= '</div></li>';
902 } else {
903 $id = strtolower(str_replace(" ", "_", esc_attr($option)));
904 $id = $id . '_' . $class_plugin_name;
905 $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>';
906 }
907 }
908 $html .= '</ul></div><!-- .wpinsights-' . $class_plugin_name . '-goodbye-options -->';
909 }
910 $html .= '</div><!-- .wpinsights-goodbye-form-body -->';
911 $html .= '<p class="deactivating-spinner"><span class="spinner"></span> ' . esc_html__('Submitting form', 'thinkrank') . '</p>';
912
913 $wrapper_class = '.wpinsights-goodbye-form-wrapper-' . $class_plugin_name;
914
915 $styles = '';
916 $styles .= '<style type="text/css">';
917 $styles .= '.wpinsights-form-active-' . $class_plugin_name . ' .wpinsights-goodbye-form-bg {';
918 $styles .= 'background: rgba( 0, 0, 0, .8 );position: fixed;top: 0;left: 0;width: 100%;height: 100%;z-index: 9;';
919 $styles .= '}';
920 $styles .= $wrapper_class . '{';
921 $styles .= 'position: relative; display: none;';
922 $styles .= '}';
923 $styles .= '.wpinsights-form-active-' . $class_plugin_name . ' ' . $wrapper_class . '{';
924 $styles .= 'display: flex !important; position: fixed;top: 0;left: 0;width: 100%;height: 100%; justify-content: center; align-items: center;';
925 $styles .= '}';
926 $styles .= $wrapper_class . ' .wpinsights-goodbye-form { display: none; }';
927 $styles .= '.wpinsights-form-active-' . $class_plugin_name . ' .wpinsights-goodbye-form {';
928 $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;';
929 $styles .= '}';
930 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-head {';
931 $styles .= 'background: #fff; color: #495157; padding: 18px; box-shadow: 0 0 8px rgba(0,0,0,.1); font-size: 15px;';
932 $styles .= '}';
933 $styles .= $wrapper_class . ' .wpinsights-goodbye-form .wpinsights-goodbye-form-head strong { font-size: 15px; }';
934 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-body { padding: 8px 18px; color: #333; }';
935 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-body label { padding-left: 5px; color: #6d7882; }';
936 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-body .wpinsights-goodbye-form-caption {';
937 $styles .= 'font-weight: 500; font-size: 15px; color: #495157; line-height: 1.4;';
938 $styles .= '}';
939 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options { padding-top: 5px; }';
940 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li { margin-bottom: 15px; }';
941 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li > div { display: inline; padding-left: 3px; }';
942 $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 {';
943 $styles .= 'margin: 10px 18px; padding: 8px; width: 80%;';
944 $styles .= '}';
945 $styles .= $wrapper_class . ' .deactivating-spinner { display: none; padding-bottom: 20px !important; }';
946 $styles .= $wrapper_class . ' .deactivating-spinner .spinner { float: none; margin: 4px 4px 0 18px; vertical-align: bottom; visibility: visible; }';
947 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-footer { padding: 8px 18px; margin-bottom: 15px; }';
948 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-footer > .wpinsights-goodbye-form-buttons { display: flex; align-items: center; justify-content: space-between; }';
949 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-footer .wpinsights-submit-btn {';
950 $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;';
951 $styles .= '}';
952 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-footer .wpinsights-submit-btn:hover {';
953 $styles .= 'background-color: #3a46e0;';
954 $styles .= '}';
955 $styles .= $wrapper_class . ' .wpinsights-goodbye-form-footer .wpinsights-deactivate-btn {';
956 $styles .= 'font-size: 13px; color: #a4afb7; background: none; float: right; padding-right: 10px; width: auto; text-decoration: underline;';
957 $styles .= '}';
958 $styles .= '
959 .wpinsights-form-active-' . $class_plugin_name . ' .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' {
960 display: flex !important;
961 position: fixed;
962 top: 0;
963 left: 0;
964 right: 0;
965 bottom: 0;
966 width: 100%;
967 z-index: 9999;
968 height: 100%;
969 justify-content: center;
970 align-items: center;
971 }
972 .wpinsights-form-active-' . $class_plugin_name . ' .wpinsights-goodbye-form {
973 border-radius: 8px;
974 width: 563px;
975 overflow: visible;
976 position: relative;
977 font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
978 }
979 .wpinsights-goodbye-form .tr__modal-close-btn {
980 position: absolute;
981 top: 0;
982 right: -55px;
983 width: 40px;
984 height: 40px;
985 background: #fff;
986 border-radius: 50%;
987 display: flex;
988 justify-content: center;
989 align-items: center;
990 font-size: 12px;
991 font-weight: 500;
992 color: #475467;
993 cursor: pointer;
994 }
995 .wpinsights-goodbye-form .tr__modal-close-btn svg {
996 width: 20px;
997 }
998 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-head {
999 background: #fff;
1000 color: #495157;
1001 padding: 24px;
1002 border-radius: 8px 8px 0 0;
1003 box-shadow: none;
1004 border-bottom: 1px solid #EAECF0;
1005 display: flex;
1006 flex-direction: column;
1007 gap: 16px;
1008 align-items: center;
1009 }
1010 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-head > span {
1011 display: inline-flex;
1012 }
1013 .wpinsights-goodbye-form-head h1 {
1014 font-size: 22px;
1015 font-weight: 450;
1016 color: #1D2939;
1017 padding: 0;
1018 }
1019 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-body {
1020 padding: 32px 40px;
1021 max-height: calc(70vh - 230px);
1022 overflow: auto;
1023 }
1024 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-body .wpinsights-goodbye-form-caption {
1025 font-size: 18px;
1026 color: #1D2939;
1027 margin: 0;
1028 padding-bottom: 16px;
1029 font-weight: 400;
1030 }
1031 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options {
1032 padding-top: 0px;
1033 }
1034 .widefat td ul {
1035 font-size: 14px;
1036 margin: 0;
1037 color: #475467;
1038 line-height: 1.4em;
1039 }
1040 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li:not(:last-child) {
1041 margin-bottom: 16px;
1042 }
1043 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li:last-child {
1044 margin-bottom: 0;
1045 }
1046 input[type=radio] {
1047 border-radius: 50%;
1048 margin-right: .25rem;
1049 line-height: .71428571;
1050 margin: 0;
1051 }
1052 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-body label {
1053 padding-left: 4px;
1054 color: #475467;
1055 font-size: 14px;
1056 }
1057 .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 {
1058 padding: 10px 16px;
1059 border-radius: 8px;
1060 width: 100%;
1061 border: 1px solid #D0D5DD;
1062 margin: 0;
1063 margin-top: 16px;
1064 color: #1D2939;
1065 font-size: 14px;
1066 line-height: 1.5em;
1067 resize: none;
1068 max-height: 44px;
1069 }
1070 input[type=radio] {
1071 border: 1px solid #D0D5DD;
1072 box-shadow: none;
1073 }
1074 input[type=radio]:checked::before {
1075 content: "";
1076 border-radius: 50%;
1077 width: .88rem;
1078 height: .88rem;
1079 margin: -.95px;
1080 background-color: #4451ff;
1081 border: 1px solid #c7ccff;
1082 line-height: 1.14285714;
1083 outline: 0;
1084 }
1085 input[type=radio]:focus {
1086 border-color: #D0D5DD;
1087 box-shadow: none;
1088 outline: 0;
1089 }
1090 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li > div > input::placeholder,
1091 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul > li > div > textarea::placeholder{
1092 color: #C6CBD2 !important;
1093 }
1094 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-footer {
1095 padding: 16px 36px;
1096 margin-bottom: 0;
1097 border-top: 1px solid #EAECF0;
1098 }
1099 input:focus,
1100 textarea:focus {
1101 box-shadow: none !important;
1102 outline: 0 !important;
1103 }
1104 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-footer > .wpinsights-goodbye-form-buttons {
1105 display: flex;
1106 align-items: center;
1107 justify-content: flex-start;
1108 }
1109 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-footer .wpinsights-submit-btn {
1110 background: #4451ff;
1111 -webkit-border-radius: 8px;
1112 border-radius: 8px;
1113 color: #fff;
1114 padding: 8px 16px;
1115 font-size: 14px;
1116 font-weight: 500;
1117 line-height: 1.5em;
1118 text-transform: capitalize;
1119 transition: .3s;
1120 }
1121 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-footer .wpinsights-submit-btn:hover {
1122 background: #3a46e0;
1123 }
1124 .wpinsights-goodbye-form-wrapper-' . $class_plugin_name . ' .wpinsights-goodbye-form-footer .wpsp-put-deactivate-btn {
1125 font-size: 14px;
1126 font-weight: 500;
1127 color: #344054;
1128 margin-left: 10px;
1129 }
1130 .wp-person a:focus .gravatar, a:focus, a:focus .media-icon img, a:focus .plugin-icon {
1131 color: #043959;
1132 box-shadow: none;
1133 outline: 0;
1134 }
1135 ';
1136 $styles .= '</style>';
1137
1138 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1139 echo $styles;
1140 ?>
1141 <script type="text/javascript">
1142 jQuery(document).ready(function($){
1143 $("#wpinsights-goodbye-link-<?php echo esc_js($class_plugin_name); ?>").on("click",function(){
1144 // We'll send the user to this deactivation link when they've completed or dismissed the form
1145 var url = document.getElementById("wpinsights-goodbye-link-<?php echo esc_js($class_plugin_name); ?>");
1146 $('body').toggleClass('wpinsights-form-active-<?php echo esc_js($class_plugin_name); ?>');
1147 $(".wpinsights-goodbye-form-wrapper-<?php echo esc_js($class_plugin_name); ?> #wpinsights-goodbye-form").fadeIn();
1148 $(".wpinsights-goodbye-form-wrapper-<?php echo esc_js($class_plugin_name); ?> #wpinsights-goodbye-form").html( '<?php
1149 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1150 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>');
1151 $('#wpinsights-submit-form-<?php echo esc_js($class_plugin_name); ?>').on('click', function(e){
1152 // As soon as we click, the body of the form should disappear
1153 $("#wpinsights-goodbye-form-<?php echo esc_js($class_plugin_name); ?> .wpinsights-goodbye-form-body").fadeOut();
1154 $("#wpinsights-goodbye-form-<?php echo esc_js($class_plugin_name); ?> .wpinsights-goodbye-form-footer").fadeOut();
1155 // Fade in spinner
1156 $("#wpinsights-goodbye-form-<?php echo esc_js($class_plugin_name); ?> .deactivating-spinner").fadeIn();
1157 e.preventDefault();
1158 var checkedInput = $("input[name='wpinsights-<?php echo esc_attr($class_plugin_name); ?>-goodbye-options']:checked"),
1159 checkedInputVal, details;
1160 if( checkedInput.length > 0 ) {
1161 checkedInputVal = checkedInput.val();
1162 details = $('input[name="'+ checkedInput[0].id +'"], textarea[name="'+ checkedInput[0].id +'"]').val();
1163 }
1164
1165 if( typeof details === 'undefined' ) {
1166 details = '';
1167 }
1168 if( typeof checkedInputVal === 'undefined' ) {
1169 checkedInputVal = 'No Reason';
1170 }
1171
1172 var data = {
1173 'action': 'deactivation_form_<?php echo esc_attr($class_plugin_name); ?>',
1174 'values': checkedInputVal,
1175 'details': details,
1176 'security': "<?php
1177 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1178 echo esc_js(wp_create_nonce('wpins_deactivation_nonce')); ?>",
1179 'dataType': "json"
1180 }
1181
1182 $.post(
1183 ajaxurl,
1184 data,
1185 function(response){
1186 // Redirect to original deactivation URL
1187 window.location.href = url;
1188 }
1189 );
1190 });
1191 $('#wpinsights-goodbye-options > ul ').on('click', 'li label, li > input', function( e ){
1192 var parent = $(this).parents('li');
1193 parent.siblings().find('label').next('input, textarea').css('display', 'none');
1194 parent.find('label').next('input, textarea').css('display', 'block');
1195 });
1196 // If we click outside the form, the form will close
1197 $('.wpinsights-goodbye-form-bg, #wpinsights-goodbye-form > .tr__modal-close-btn').on('click',function(){
1198 $("#wpinsights-goodbye-form").fadeOut();
1199 $('body').removeClass('wpinsights-form-active-<?php echo esc_attr($class_plugin_name); ?>');
1200 });
1201 });
1202 });
1203 </script>
1204 <?php }
1205
1206 /**
1207 * Get Used Elements Count
1208 *
1209 * Optional data payload describing which ThinkRank features are enabled
1210 * on the site. Reported as a name => count map (1 = enabled) so the
1211 * insights backend can aggregate feature adoption.
1212 *
1213 * @return array
1214 *
1215 * @since 1.12.0
1216 */
1217 public static function get_used_elements_count() {
1218 $used = [];
1219
1220 // AI provider in use.
1221 $ai_provider = get_option('thinkrank_ai_provider', '');
1222 if (!empty($ai_provider)) {
1223 $used['ai_provider_' . sanitize_key($ai_provider)] = 1;
1224 }
1225
1226 // Boolean feature flags stored as standalone options.
1227 $flag_options = [
1228 'thinkrank_auto_optimize' => 'auto_optimize',
1229 'thinkrank_enable_logging' => 'logging',
1230 ];
1231 foreach ($flag_options as $option_name => $label) {
1232 if (!empty(get_option($option_name))) {
1233 $used[$label] = 1;
1234 }
1235 }
1236
1237 // Feature settings groups: report when configured/enabled.
1238 $feature_settings = [
1239 'thinkrank_instant_indexing_settings' => 'instant_indexing',
1240 'thinkrank_author_archives_settings' => 'author_archives',
1241 'thinkrank_image_seo_settings' => 'image_seo',
1242 ];
1243 foreach ($feature_settings as $option_name => $label) {
1244 $settings = get_option($option_name, []);
1245 if (is_array($settings) && !empty($settings['enabled'])) {
1246 $used[$label] = 1;
1247 }
1248 }
1249
1250 // Indexed content volume buckets (lightweight published-post count).
1251 $published = (int) wp_count_posts('post')->publish;
1252 if ($published > 0) {
1253 $used['published_posts'] = $published;
1254 }
1255
1256 return $used;
1257 }
1258 }
1259