PluginProbe
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar / 3.3.1
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar v3.3.1
3.3.1 3.3.0 3.2.14 3.2.13 3.2.12 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 trunk 0.2.5.5 0.2.5.6 0.2.5.7 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2.0 1.2.1 All 156 releases
notificationx / includes / Admin / PluginInsights.php

PluginInsights.php in NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar 3.3.1, at includes/Admin/PluginInsights.php

1,101 lines 44.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace NotificationX\Admin;
4 #[\AllowDynamicProperties]
5 class PluginInsights {
6 /**
7 * WP Insights Version
8 */
9 const WPINS_VERSION = '3.0.1';
10 /**
11 * API URL
12 */
13 const API_URL = 'https://send.wpinsight.com/process-plugin-data';
14 /**
15 * Installed Plugin File
16 *
17 * @var string
18 */
19 private $plugin_file = null;
20 /**
21 * Installed Plugin Name
22 *
23 * @var string
24 */
25 private $plugin_name = null;
26 /**
27 * How often the event should subsequently
28 *
29 * @var string
30 */
31 public $recurrence = 'daily';
32 private $event_hook = null;
33 private $has_notice = false;
34 /**
35 * Instace of WPInsights_NotificationX
36 *
37 * @var WPInsights_NotificationX
38 */
39 private static $_instance = null;
40 /**
41 * Get Instance of WPInsights_NotificationX
42 *
43 * @return WPInsights_NotificationX
44 */
45 public static function get_instance( $plugin_file, $args = [] ) {
46 if ( is_null( static::$_instance ) ) {
47 static::$_instance = new static( $plugin_file, $args );
48 }
49 return static::$_instance;
50 }
51 /**
52 * Automatically Invoked when initialized.
53 *
54 * @param array $args
55 */
56 public function __construct( $plugin_file, $args = [] ) {
57 $this->plugin_file = $plugin_file;
58 $this->plugin_name = basename( $this->plugin_file, '.php' );
59 $this->disabled_wp_cron = defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON == true;
60 $this->enable_self_cron = $this->disabled_wp_cron == true ? true : false;
61
62 $this->event_hook = 'put_do_weekly_action';
63
64 $this->require_optin = isset( $args['opt_in'] ) ? $args['opt_in'] : true;
65 $this->include_goodbye_form = isset( $args['goodbye_form'] ) ? $args['goodbye_form'] : true;
66 $this->marketing = isset( $args['email_marketing'] ) ? $args['email_marketing'] : true;
67 $this->options = isset( $args['options'] ) ? $args['options'] : [];
68 $this->item_id = isset( $args['item_id'] ) ? $args['item_id'] : false;
69 /**
70 * Activation Hook
71 */
72 register_activation_hook( $this->plugin_file, array( $this, 'activate_this_plugin' ) );
73 /**
74 * Deactivation Hook
75 */
76 register_deactivation_hook( $this->plugin_file, array( $this, 'deactivate_this_plugin' ) );
77 }
78 /**
79 * When user agreed to opt-in tracking schedule is enabled.
80 *
81 * @since 3.0.0
82 */
83 public function schedule_tracking() {
84 if ( $this->disabled_wp_cron ) {
85 return;
86 }
87 if ( ! wp_next_scheduled( $this->event_hook ) ) {
88 wp_schedule_event( time(), $this->recurrence, $this->event_hook );
89 }
90 }
91 /**
92 * Add the schedule event if the plugin is tracked.
93 *
94 * @return void
95 */
96 public function activate_this_plugin() {
97 $allow_tracking = $this->is_tracking_allowed();
98 if ( ! $allow_tracking ) {
99 return;
100 }
101 $this->schedule_tracking();
102 }
103 /**
104 * Remove the schedule event when plugin is deactivated and send the deactivated reason to inishghts if user submitted.
105 *
106 * @since 3.0.0
107 */
108 public function deactivate_this_plugin() {
109 /**
110 * Check tracking is allowed or not.
111 */
112 $allow_tracking = $this->is_tracking_allowed();
113 if ( ! $allow_tracking ) {
114 return;
115 }
116 $body = $this->get_data();
117 $body['status'] = 'Deactivated';
118 $body['deactivated_date'] = time();
119
120 // Check deactivation reason and add for insights data.
121 if ( false !== get_option( 'wpins_deactivation_reason_' . $this->plugin_name ) ) {
122 $body['deactivation_reason'] = get_option( 'wpins_deactivation_reason_' . $this->plugin_name );
123 }
124 if ( false !== get_option( 'wpins_deactivation_details_' . $this->plugin_name ) ) {
125 $body['deactivation_details'] = get_option( 'wpins_deactivation_details_' . $this->plugin_name );
126 }
127
128 $this->send_data( $body );
129 delete_option( 'wpins_deactivation_reason_' . $this->plugin_name );
130 delete_option( 'wpins_deactivation_details_' . $this->plugin_name );
131 /**
132 * Clear the event schedule.
133 */
134 if ( ! $this->disabled_wp_cron ) {
135 wp_clear_scheduled_hook( $this->event_hook );
136 }
137 }
138 /**
139 * Initial Method to Hook Everything.
140 *
141 * @return void
142 */
143 public function init() {
144 // $this->clicked();
145 add_action( 'wpdeveloper_notice_clicked_for_' . $this->plugin_name, array( $this, 'clicked' ) );
146 add_action( $this->event_hook, array( $this, 'do_tracking' ) );
147 // For Test
148 // add_action( 'admin_init', array( $this, 'force_tracking' ) );
149 // add_action( 'admin_notices', array( $this, 'notice' ) );
150 add_action( 'wpdeveloper_optin_notice_for_' . $this->plugin_name, array( $this, 'notice' ) );
151 /**
152 * Deactivation Reason Form and Submit Data to Insights.
153 */
154 add_filter( 'plugin_action_links_' . plugin_basename( $this->plugin_file ), array( $this, 'deactivate_action_links' ) );
155 add_action( 'admin_print_footer_scripts', array( $this, 'notice_script' ) );
156 add_action( 'admin_print_footer_scripts-plugins.php', array( $this, 'deactivate_reasons_form_script' ) );
157 add_action( 'admin_print_styles-plugins.php', array( $this, 'deactivate_reasons_form_style' ) );
158 add_action( 'wp_ajax_deactivation_form_' . esc_attr( $this->plugin_name ), array( $this, 'deactivate_reasons_form_submit' ) );
159 }
160 /**
161 * For Redirecting Current Page without Arguments!
162 *
163 * @return void
164 */
165 private function redirect_to() {
166 $current_uri = isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
167 $request_uri = wp_parse_url( $current_uri, PHP_URL_PATH );
168 $query_string = wp_parse_url( $current_uri, PHP_URL_QUERY );
169 parse_str( $query_string, $current_url );
170
171 $unset_array = array( 'dismiss', 'plugin', '_wpnonce', 'later', 'plugin_action', 'marketing_optin' );
172
173 foreach ( $unset_array as $value ) {
174 if ( isset( $current_url[ $value ] ) ) {
175 unset( $current_url[ $value ] );
176 }
177 }
178
179 $current_url = http_build_query( $current_url );
180 $redirect_url = $request_uri . '?' . $current_url;
181 return $redirect_url;
182 }
183 /**
184 * This method forcing the do_tracking method to execute instant.
185 *
186 * @return void
187 */
188 public function force_tracking() {
189 $this->do_tracking( true );
190 }
191 /**
192 * Record the user's explicit opt-in and (optionally) send the data to the
193 * insights API immediately. Used by the onboarding Setup Wizard when the
194 * user proceeds past the Welcome step (which is the consent point).
195 *
196 * Collection itself no longer depends on this call — see
197 * {@see self::is_tracking_allowed()}; this only stores the consent state
198 * (which suppresses the opt-in notice and enables the feedback form) and
199 * triggers an immediate send.
200 *
201 * @param bool $send Send the collected data right away.
202 * @return bool|\WP_Error
203 */
204 public function optin( $send = true ) {
205 $this->schedule_tracking();
206 $this->set_is_tracking_allowed( true, $this->plugin_name );
207 $this->update_block_notice( $this->plugin_name );
208 if ( $send ) {
209 return $this->do_tracking( true );
210 }
211 return true;
212 }
213 /**
214 * This method is responsible for all the magic from the front of the plugin.
215 *
216 * @since 3.0.0
217 * @param $force Force tracking if it's not the correct time to track/
218 */
219 public function do_tracking( $force = false ) {
220 /**
221 * Check URL is set or not.
222 */
223 if ( ( defined('NX_DEBUG') && NX_DEBUG ) || empty( self::API_URL ) ) {
224 return;
225 }
226 /**
227 * Check is tracking allowed or not.
228 */
229 if ( ! $this->is_tracking_allowed() ) {
230 return;
231 }
232 /**
233 * Check is this the correct time to track or not.
234 * or Force to track.
235 */
236 if ( ! $this->is_time_to_track() && ! $force ) {
237 return;
238 }
239 /**
240 * Get All Data.
241 */
242 $body = $this->get_data();
243 /**
244 * Send all data.
245 */
246 return $this->send_data( $body );
247 }
248 /**
249 * Is data collection allowed?
250 *
251 * Since 3.3.0 collection is no longer gated behind the opt-in notice or
252 * the Setup Wizard: it is enabled from the backend for every install, so
253 * the payload is collected whether the user accepts, rejects, ignores or
254 * never opens the consent/onboarding flow. The only remaining hard stop is
255 * the programmatic opt-out exposed through the `options` constructor
256 * argument (see {@see self::has_user_opted_out()}).
257 *
258 * The deactivation feedback form follows this same gate, so it is
259 * available as soon as the plugin is activated. The user's explicit
260 * consent state is still recorded separately and is readable through
261 * {@see self::has_user_consented()}, but nothing is gated on it.
262 *
263 * @since 1.0.0
264 */
265 private function is_tracking_allowed() {
266 // A programmatic opt-out (an option flagged via the `options` arg) is
267 // still honoured and clears any recorded consent.
268 if ( $this->has_user_opted_out() ) {
269 $this->set_is_tracking_allowed( false, $this->plugin_name );
270 return false;
271 }
272 return true;
273 }
274 /**
275 * Has the user explicitly consented to tracking?
276 *
277 * This is the legacy `wpins_allow_tracking` state, set when the user
278 * accepts the opt-in notice or proceeds past the Setup Wizard welcome
279 * step. It is recorded for reporting only: neither data collection nor the
280 * deactivation feedback form is gated on it any more.
281 *
282 * @since 3.3.0
283 * @return bool
284 */
285 public function has_user_consented() {
286 if ( $this->has_user_opted_out() ) {
287 return false;
288 }
289 // The wpins_allow_tracking option is an array of plugins the user has opted in for.
290 $allow_tracking = get_option( 'wpins_allow_tracking' );
291 return is_array( $allow_tracking ) && isset( $allow_tracking[ $this->plugin_name ] );
292 }
293 /**
294 * Set a flag in DB If tracking is allowed.
295 *
296 * @since 3.0.0
297 * @param $is_allowed Boolean true if is allowed.
298 */
299 protected function set_is_tracking_allowed( $is_allowed, $plugin = null ) {
300 if ( empty( $plugin ) ) {
301 $plugin = $this->plugin_name;
302 }
303 /**
304 * Get All Tracked Plugin List using this Tracker.
305 */
306 $allow_tracking = get_option( 'wpins_allow_tracking' );
307 /**
308 * Check user is opted out for tracking or not.
309 */
310 if ( $this->has_user_opted_out() ) {
311 if ( isset( $allow_tracking[ $plugin ] ) ) {
312 unset( $allow_tracking[ $plugin ] );
313 }
314 } elseif ( $is_allowed || ! $this->require_optin ) {
315 /**
316 * If user has agreed to allow tracking
317 */
318 if ( empty( $allow_tracking ) || ! is_array( $allow_tracking ) ) {
319 $allow_tracking = array( $plugin => $plugin );
320 } else {
321 $allow_tracking[ $plugin ] = $plugin;
322 }
323 } else {
324 if ( isset( $allow_tracking[ $plugin ] ) ) {
325 unset( $allow_tracking[ $plugin ] );
326 }
327 }
328 update_option( 'wpins_allow_tracking', $allow_tracking, 'no' );
329 }
330
331 /**
332 * Check the user has opted out or not.
333 *
334 * @since 3.0.0
335 * @return Boolean
336 */
337 protected function has_user_opted_out() {
338 if ( ! empty( $this->options ) ) {
339 foreach ( $this->options as $option_name ) {
340 $options = get_option( $option_name );
341 if ( ! empty( $options['wpins_opt_out'] ) ) {
342 return true;
343 }
344 }
345 }
346 return false;
347 }
348 /**
349 * Check if it's time to track
350 *
351 * @since 3.0.0
352 */
353 public function is_time_to_track() {
354 $track_times = get_option( 'wpins_last_track_time', array() );
355 return ! isset( $track_times[ $this->plugin_name ] ) ? true :
356 ( ( isset( $track_times[ $this->plugin_name ] ) && $track_times[ $this->plugin_name ] ) < strtotime( '-1 day' ) ? true : false );
357 }
358 /**
359 * Set tracking time.
360 *
361 * @since 3.0.0
362 */
363 public function set_track_time() {
364 $track_times = get_option( 'wpins_last_track_time', array() );
365 $track_times[ $this->plugin_name ] = time();
366 update_option( 'wpins_last_track_time', $track_times, 'no' );
367 }
368 /**
369 * This method is responsible for collecting all data.
370 *
371 * @since 3.0.0
372 */
373 public function get_data() {
374 $body = array(
375 'plugin_slug' => sanitize_text_field( $this->plugin_name ),
376 'url' => get_bloginfo( 'url' ),
377 'site_name' => get_bloginfo( 'name' ),
378 'site_version' => get_bloginfo( 'version' ),
379 'site_language' => get_bloginfo( 'language' ),
380 'charset' => get_bloginfo( 'charset' ),
381 'wpins_version' => self::WPINS_VERSION,
382 'php_version' => phpversion(),
383 'multisite' => is_multisite(),
384 'file_location' => __FILE__,
385 );
386
387 // Collect the email if the correct option has been set
388 if ( $this->marketing ) {
389 if ( ! function_exists( 'wp_get_current_user' ) ) {
390 include ABSPATH . 'wp-includes/pluggable.php';
391 }
392 $current_user = wp_get_current_user();
393 $email = $current_user->user_email;
394 if ( is_email( $email ) ) {
395 $body['email'] = $email;
396 }
397 }
398 $body['marketing_method'] = $this->marketing;
399 $body['server'] = isset( $_SERVER['SERVER_SOFTWARE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) : '';
400
401 /**
402 * Collect all active and inactive plugins
403 */
404 if ( ! function_exists( 'get_plugins' ) ) {
405 include ABSPATH . '/wp-admin/includes/plugin.php';
406 }
407 $plugins = array_keys( get_plugins() );
408 $active_plugins = is_network_admin() ? array_keys( get_site_option( 'active_sitewide_plugins', array() ) ) : get_option( 'active_plugins', array() );
409 foreach ( $plugins as $key => $plugin ) {
410 if ( in_array( $plugin, $active_plugins ) ) {
411 unset( $plugins[ $key ] );
412 }
413 }
414 $body['active_plugins'] = $active_plugins;
415 $body['inactive_plugins'] = $plugins;
416
417 /**
418 * Text Direction.
419 */
420 $body['text_direction'] = ( function_exists( 'is_rtl' ) ? ( is_rtl() ? 'RTL' : 'LTR' ) : 'NOT SET' );
421 /**
422 * Get Our Plugin Data.
423 *
424 * @since 3.0.0
425 */
426 $plugin = $this->plugin_data();
427 if ( empty( $plugin ) ) {
428 $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.', 'notificationx' );
429 $body['status'] = 'NOT FOUND';
430 } else {
431 if ( isset( $plugin['Name'] ) ) {
432 $body['plugin'] = sanitize_text_field( $plugin['Name'] );
433 }
434 if ( isset( $plugin['Version'] ) ) {
435 $body['version'] = sanitize_text_field( $plugin['Version'] );
436 }
437 $body['status'] = 'Active';
438 }
439
440 /**
441 * Get active theme name and version
442 *
443 * @since 3.0.0
444 */
445 $theme = wp_get_theme();
446 if ( $theme->Name ) {
447 $body['theme'] = sanitize_text_field( $theme->Name );
448 }
449 if ( $theme->Version ) {
450 $body['theme_version'] = sanitize_text_field( $theme->Version );
451 }
452
453 /**
454 * Allow plugin-specific usage data to be merged into the tracking
455 * payload (e.g. NotificationX notification type & source breakdown).
456 *
457 * @see \NotificationX\Core\UsageTracker
458 * @since 3.3.0
459 * @param array $body Collected tracking data.
460 * @param PluginInsights $this Current insights instance.
461 */
462 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
463 $body = apply_filters( 'nx_plugin_usage_tracker_data', $body, $this );
464
465 return $body;
466 }
467
468 /**
469 * Collect plugin data,
470 * Retrieve current plugin information
471 *
472 * @since 3.0.0
473 */
474 public function plugin_data() {
475 if ( ! function_exists( 'get_plugin_data' ) ) {
476 include ABSPATH . '/wp-admin/includes/plugin.php';
477 }
478 $plugin = get_plugin_data( $this->plugin_file );
479 return $plugin;
480 }
481 /**
482 * Send the data to insights.
483 *
484 * @since 3.0.0
485 */
486 public function send_data( $body ) {
487 /**
488 * Get SITE ID
489 */
490 $site_id_key = "wpins_{$this->plugin_name}_site_id";
491 $site_id = get_option( $site_id_key, false );
492 $failed_data = [];
493 $site_url = get_bloginfo( 'url' );
494 $original_site_url = get_option( "wpins_{$this->plugin_name}_original_url", false );
495 if ( $original_site_url === false && version_compare( $body['wpins_version'], '3.0.1', '==' ) ) {
496 $site_id = false;
497 }
498 /**
499 * Send Initial Data to API
500 */
501 if ( $site_id == false && $this->item_id !== false && $original_site_url === false ) {
502 if ( isset( $_SERVER['REMOTE_ADDR'] ) && ! empty( $_SERVER['REMOTE_ADDR'] && $_SERVER['REMOTE_ADDR'] != '127.0.0.1' ) ) {
503 $country_request = wp_remote_get( 'http://ip-api.com/json/' . sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) . '?fields=country' );
504 if ( ! is_wp_error( $country_request ) && $country_request['response']['code'] == 200 ) {
505 $ip_data = json_decode( $country_request['body'] );
506 $body['country'] = isset( $ip_data->country ) ? $ip_data->country : 'NOT SET';
507 }
508 }
509
510 $body['plugin_slug'] = $this->plugin_name;
511 $body['url'] = $site_url;
512 $body['item_id'] = $this->item_id;
513
514 $request = $this->remote_post( $body );
515 if ( ! is_wp_error( $request ) && $request['response']['code'] == 200 ) {
516 $retrieved_body = json_decode( wp_remote_retrieve_body( $request ), true );
517 if ( is_array( $retrieved_body ) && isset( $retrieved_body['siteId'] ) ) {
518 update_option( $site_id_key, $retrieved_body['siteId'], 'no' );
519 update_option( "wpins_{$this->plugin_name}_original_url", $site_url, 'no' );
520 update_option( "wpins_{$this->plugin_name}_{$retrieved_body['siteId']}", $body, 'no' );
521 }
522 } else {
523 $failed_data = $body;
524 }
525 }
526
527 $site_id_data_key = "wpins_{$this->plugin_name}_{$site_id}";
528 $site_id_data_failed_key = "wpins_{$this->plugin_name}_{$site_id}_send_failed";
529
530 if ( $site_id != false ) {
531 $old_sent_data = get_option( $site_id_data_key, [] );
532 $diff_data = $this->diff( $body, $old_sent_data );
533 $failed_data = get_option( $site_id_data_failed_key, [] );
534 if ( ! empty( $failed_data ) && $diff_data != $failed_data ) {
535 $failed_data = array_merge( $failed_data, $diff_data );
536 }
537 }
538
539 if ( ! empty( $failed_data ) && $site_id != false ) {
540 $failed_data['plugin_slug'] = $this->plugin_name;
541 $failed_data['url'] = $site_url;
542 $failed_data['site_id'] = $site_id;
543 if ( $original_site_url != false ) {
544 $failed_data['original_url'] = $original_site_url;
545 }
546
547 $request = $this->remote_post( $failed_data );
548 if ( ! is_wp_error( $request ) ) {
549 delete_option( $site_id_data_failed_key );
550 $replaced_data = array_merge( $old_sent_data, $failed_data );
551 update_option( $site_id_data_key, $replaced_data, 'no' );
552 }
553 }
554
555 if ( ! empty( $diff_data ) && $site_id != false && empty( $failed_data ) ) {
556 $diff_data['plugin_slug'] = $this->plugin_name;
557 $diff_data['url'] = $site_url;
558 $diff_data['site_id'] = $site_id;
559 if ( $original_site_url != false ) {
560 $diff_data['original_url'] = $original_site_url;
561 }
562
563 $request = $this->remote_post( $diff_data );
564 if ( is_wp_error( $request ) ) {
565 update_option( $site_id_data_failed_key, $diff_data, 'no' );
566 } else {
567 $replaced_data = array_merge( $old_sent_data, $diff_data );
568 update_option( $site_id_data_key, $replaced_data, 'no' );
569 }
570 }
571
572 $this->set_track_time();
573
574 if ( isset( $request ) && is_wp_error( $request ) ) {
575 return $request;
576 }
577
578 if ( isset( $request ) ) {
579 return true;
580 }
581 return false;
582 }
583 /**
584 * WP_REMOTE_POST method responsible for send data to the API_URL
585 *
586 * @param array $data
587 * @param array $args
588 * @return void
589 */
590 protected function remote_post( $data = array(), $args = array() ) {
591 if ( empty( $data ) ) {
592 return;
593 }
594
595 $args = wp_parse_args( $args, array(
596 'method' => 'POST',
597 'timeout' => 30,
598 'redirection' => 5,
599 'httpversion' => '1.1',
600 'blocking' => true,
601 'body' => $data,
602 'user-agent' => 'PUT/1.0.0; ' . get_bloginfo( 'url' ),
603 )
604 );
605
606 $request = wp_remote_post( esc_url( self::API_URL ), $args );
607 if ( is_wp_error( $request ) || ( isset( $request['response'], $request['response']['code'] ) && $request['response']['code'] != 200 ) ) {
608 return new \WP_Error( 500, 'Something went wrong.' );
609 }
610 return $request;
611 }
612 /**
613 * Difference between old and new data
614 *
615 * @param array $new_data
616 * @param array $old_data
617 * @return void
618 */
619 protected function diff( $new_data, $old_data ) {
620 $data = [];
621 if ( ! empty( $new_data ) ) {
622 foreach ( $new_data as $key => $value ) {
623 if ( isset( $old_data[ $key ] ) ) {
624 if ( $old_data[ $key ] == $value ) {
625 continue;
626 }
627 }
628 $data[ $key ] = $value;
629 }
630 }
631 return $data;
632 }
633 /**
634 * Display the admin notice to users to allow them to opt in
635 *
636 * @since 3.0.0
637 */
638 public function notice() {
639 /**
640 * Return if notice is not set.
641 */
642 if ( ! isset( $this->notice_options['notice'] ) ) {
643 return;
644 }
645 /**
646 * Check is allowed or blocked for notice.
647 */
648 $block_notice = get_option( 'wpins_block_notice' );
649 if ( isset( $block_notice[ $this->plugin_name ] ) ) {
650 return;
651 }
652 if ( ! current_user_can( 'manage_options' ) ) {
653 return;
654 }
655
656 $this->has_notice = true;
657
658 $url_yes = add_query_arg( [
659 'plugin' => $this->plugin_name,
660 'plugin_action' => 'yes',
661 ]
662 );
663 $url_no = add_query_arg( array(
664 'plugin' => $this->plugin_name,
665 'plugin_action' => 'no',
666 )
667 );
668
669 $url_yes = wp_nonce_url( $url_yes, '_wpnonce_optin_' . $this->plugin_name );
670 $url_no = wp_nonce_url( $url_no, '_wpnonce_optin_' . $this->plugin_name );
671
672 // Decide on notice text
673 $notice_text = $this->notice_options['notice'] . ' <a href="#" class="wpinsights-' . esc_attr( $this->plugin_name ) . '-collect" >' . $this->notice_options['consent_button_text'] . '</a>';
674 $extra_notice_text = $this->notice_options['extra_notice'];
675
676 ?>
677
678 <div class="nx-optin">
679 <p><?php echo wp_kses_post( $notice_text ); ?></p>
680 <div class="wpinsights-data" style="display: none;">
681 <p><?php echo wp_kses_post( $extra_notice_text ); ?></p>
682 </div>
683 <p>
684 <a href="<?php echo esc_url( $url_yes ); ?>" class="button-primary">
685 <?php echo esc_html( $this->notice_options['yes'] ); ?>
686 </a>&nbsp;
687 <a href="<?php echo esc_url( $url_no ); ?>" class="button-secondary">
688 <?php echo esc_html( $this->notice_options['no'] ); ?>
689 </a>
690 </p>
691 </div>
692
693 <?php
694 }
695
696 public function notice_script() {
697 if ( $this->has_notice ) {
698 echo "<script type='text/javascript'>jQuery('.wpinsights-" . esc_attr( $this->plugin_name ) . "-collect').on('click', function(e) {e.preventDefault();jQuery('.wpinsights-data').slideToggle('fast');});</script>";
699 }
700 }
701 /**
702 * Set all notice options to customized notice.
703 *
704 * @since 3.0.0
705 * @param array $options
706 * @return void
707 */
708 public function set_notice_options( $options = [] ) {
709 $default_options = [
710 'consent_button_text' => __( 'What we collect.', 'notificationx' ),
711 'yes' => __( 'Sure, I\'d like to help', 'notificationx' ),
712 'no' => __( 'No Thanks.', 'notificationx' ),
713 ];
714 $options = wp_parse_args( $options, $default_options );
715 $this->notice_options = $options;
716 }
717 /**
718 * Responsible for track the click from Notice.
719 *
720 * @return void
721 */
722 public function clicked( $notice = null ) {
723 if ( isset( $_GET[ '_wpnonce' ] ) && isset( $_GET['plugin'] ) && isset( $_GET['plugin_action'] ) ) {
724 if ( isset( $_GET['tab'] ) && $_GET['tab'] === 'plugin-information' ) {
725 return;
726 }
727
728 if( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET[ '_wpnonce' ] ) ), '_wpnonce_optin_' . $this->plugin_name ) ) {
729 return;
730 }
731
732 $plugin = sanitize_text_field( wp_unslash( $_GET['plugin'] ) );
733 $action = sanitize_text_field( wp_unslash( $_GET['plugin_action'] ) );
734 if ( $action == 'yes' ) {
735 $this->schedule_tracking();
736 $this->set_is_tracking_allowed( true, $plugin );
737 if ( $this->do_tracking( true ) ) {
738 $this->update_block_notice( $plugin );
739 }
740 } else {
741 $this->set_is_tracking_allowed( false, $plugin );
742 $this->update_block_notice( $plugin );
743 }
744
745 if( ! is_null ( $notice ) ) {
746 $notice->dismiss->dismiss_notice();
747 }
748
749 /**
750 * Redirect User To the Current URL, but without set query arguments.
751 */
752 wp_safe_redirect( $this->redirect_to() );
753 }
754 }
755 /**
756 * Set if we should block the opt-in notice for this plugin
757 *
758 * @since 3.0.0
759 */
760 public function update_block_notice( $plugin = null ) {
761 if ( empty( $plugin ) ) {
762 $plugin = $this->plugin_name;
763 }
764 $block_notice = get_option( 'wpins_block_notice' );
765 if ( empty( $block_notice ) || ! is_array( $block_notice ) ) {
766 $block_notice = array( $plugin => $plugin );
767 } else {
768 $block_notice[ $plugin ] = $plugin;
769 }
770 update_option( 'wpins_block_notice', $block_notice, 'no' );
771 }
772 /**
773 * AJAX callback when the deactivated form is submitted.
774 *
775 * @since 3.0.0
776 */
777 public function deactivate_reasons_form_submit() {
778 check_ajax_referer( 'wpins_deactivation_nonce', 'security' );
779 if ( isset( $_POST['values'] ) ) {
780 $values = sanitize_text_field( wp_unslash( $_POST['values'] ) );
781 update_option( 'wpins_deactivation_reason_' . $this->plugin_name, $values, 'no' );
782 }
783 if ( isset( $_POST['details'] ) ) {
784 $details = sanitize_text_field( wp_unslash( $_POST['details'] ) );
785 update_option( 'wpins_deactivation_details_' . $this->plugin_name, $details, 'no' );
786 }
787 echo 'success';
788 wp_die();
789 }
790 /**
791 * Filter the deactivation link to allow us to present a form when the user deactivates the plugin
792 *
793 * @since 3.0.0
794 */
795 public function deactivate_action_links( $links ) {
796 /**
797 * The feedback form follows data collection, not the opt-in choice:
798 * collection is enabled from the backend on activation, so the form is
799 * available from that moment too. The programmatic opt-out is still a
800 * hard stop, because it turns collection off entirely.
801 */
802 if ( ! $this->is_tracking_allowed() ) {
803 return $links;
804 }
805 if ( isset( $links['deactivate'] ) && $this->include_goodbye_form ) {
806 $deactivation_link = $links['deactivate'];
807 /**
808 * Change the default deactivate button link.
809 */
810 $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 );
811 $links['deactivate'] = $deactivation_link;
812 }
813 return $links;
814 }
815 /**
816 * ALL Deactivate Reasons.
817 *
818 * @since 3.0.0
819 */
820 public function deactivation_reasons() {
821 $form = array();
822 $form['heading'] = __( 'Sorry to see you go', 'notificationx' );
823 $form['body'] = __( 'Before you deactivate the plugin, would you quickly give us your reason for doing so?', 'notificationx' );
824
825 $form['options'] = array(
826 __( 'I no longer need the plugin', 'notificationx' ),
827 [
828 'label' => __( 'I found a better plugin', 'notificationx' ),
829 'extra_field' => __( 'Please share which plugin', 'notificationx' ),
830 ],
831 __( "I couldn't get the plugin to work", 'notificationx' ),
832 __( 'It\'s a temporary deactivation', 'notificationx' ),
833 [
834 'label' => __( 'Other', 'notificationx' ),
835 'extra_field' => __( 'Please share the reason', 'notificationx' ),
836 'type' => 'textarea',
837 ],
838 );
839 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
840 return apply_filters( 'wpins_form_text_' . $this->plugin_name, $form );
841 }
842 /**
843 * Deactivate Reasons Form.
844 * This form will appears when user wants to deactivate the plugin to send you deactivated reasons.
845 *
846 * @since 3.0.0
847 */
848 public function deactivate_reasons_form_style() {
849 ?>
850 <style type="text/css">
851 .wpinsights-form-active-notificationx .wpinsights-goodbye-form-bg {
852 background: rgba(0, 0, 0, .8);
853 position: fixed;
854 top: 0;
855 left: 0;
856 width: 100%;
857 height: 100%;
858 z-index: 9;
859 }
860
861 .wpinsights-goodbye-form-wrapper-notificationx {
862 position: relative;
863 display: none;
864 }
865
866 .wpinsights-form-active-notificationx .wpinsights-goodbye-form-wrapper-notificationx {
867 display: flex !important;
868 position: fixed;
869 top: 0;
870 left: 0;
871 width: 100%;
872 height: 100%;
873 justify-content: center;
874 align-items: center;
875 }
876
877 .wpinsights-goodbye-form-wrapper-notificationx .wpinsights-goodbye-form {
878 display: none;
879 }
880
881 .wpinsights-form-active-notificationx .wpinsights-goodbye-form {
882 position: relative !important;
883 width: 550px;
884 max-width: 80%;
885 background: #fff;
886 box-shadow: 2px 8px 23px 3px rgba(0, 0, 0, .2);
887 border-radius: 3px;
888 white-space: normal;
889 overflow: hidden;
890 display: block;
891 z-index: 999999;
892 }
893
894 .wpinsights-goodbye-form-wrapper-notificationx .wpinsights-goodbye-form-head {
895 background: #fff;
896 color: #495157;
897 padding: 18px;
898 box-shadow: 0 0 8px rgba(0, 0, 0, .1);
899 font-size: 15px;
900 }
901
902 .wpinsights-goodbye-form-wrapper-notificationx .wpinsights-goodbye-form .wpinsights-goodbye-form-head strong {
903 font-size: 15px;
904 }
905
906 .wpinsights-goodbye-form-wrapper-notificationx .wpinsights-goodbye-form-body {
907 padding: 8px 18px;
908 color: #333;
909 }
910
911 .wpinsights-goodbye-form-wrapper-notificationx .wpinsights-goodbye-form-body label {
912 padding-left: 5px;
913 color: #6d7882;
914 }
915
916 .wpinsights-goodbye-form-wrapper-notificationx .wpinsights-goodbye-form-body .wpinsights-goodbye-form-caption {
917 font-weight: 500;
918 font-size: 15px;
919 color: #495157;
920 line-height: 1.4;
921 }
922
923 .wpinsights-goodbye-form-wrapper-notificationx .wpinsights-goodbye-form-body #wpinsights-goodbye-options {
924 padding-top: 5px;
925 }
926
927 .wpinsights-goodbye-form-wrapper-notificationx .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul>li {
928 margin-bottom: 15px;
929 }
930
931 .wpinsights-goodbye-form-wrapper-notificationx .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul>li>div {
932 display: inline;
933 padding-left: 3px;
934 }
935
936 .wpinsights-goodbye-form-wrapper-notificationx .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul>li>div>input,
937 .wpinsights-goodbye-form-wrapper-notificationx .wpinsights-goodbye-form-body #wpinsights-goodbye-options ul>li>div>textarea {
938 margin: 10px 18px;
939 padding: 8px;
940 width: 80%;
941 }
942
943 .wpinsights-goodbye-form-wrapper-notificationx .deactivating-spinner {
944 display: none;
945 padding-bottom: 20px !important;
946 }
947
948 .wpinsights-goodbye-form-wrapper-notificationx .deactivating-spinner .spinner {
949 float: none;
950 margin: 4px 4px 0 18px;
951 vertical-align: bottom;
952 visibility: visible;
953 }
954
955 .wpinsights-goodbye-form-wrapper-notificationx .wpinsights-goodbye-form-footer {
956 padding: 8px 18px;
957 margin-bottom: 15px;
958 }
959
960 .wpinsights-goodbye-form-wrapper-notificationx .wpinsights-goodbye-form-footer>.wpinsights-goodbye-form-buttons {
961 display: flex;
962 align-items: center;
963 justify-content: space-between;
964 }
965
966 .wpinsights-goodbye-form-wrapper-notificationx .wpinsights-goodbye-form-footer .wpinsights-submit-btn {
967 background-color: #d30c5c;
968 -webkit-border-radius: 3px;
969 border-radius: 3px;
970 color: #fff;
971 line-height: 1;
972 padding: 15px 20px;
973 font-size: 13px;
974 }
975
976 .wpinsights-goodbye-form-wrapper-notificationx .wpinsights-goodbye-form-footer .wpinsights-deactivate-btn {
977 font-size: 13px;
978 color: #a4afb7;
979 background: none;
980 float: right;
981 padding-right: 10px;
982 width: auto;
983 text-decoration: underline;
984 }
985
986 .wpinsights-goodbye-form-wrapper-notificationx .test {}
987 </style>
988
989 <?php
990 }
991
992 /**
993 * Deactivate Reasons Form.
994 * This form will appears when user wants to deactivate the plugin to send you deactivated reasons.
995 *
996 * @since 3.0.0
997 */
998 public function deactivate_reasons_form_script() {
999 $form = $this->deactivation_reasons();
1000
1001 $html = '<div class="wpinsights-goodbye-form-head"><strong>' . esc_html( $form['heading'] ) . '</strong></div>';
1002 $html .= '<div class="wpinsights-goodbye-form-body"><p class="wpinsights-goodbye-form-caption">' . esc_html( $form['body'] ) . '</p>';
1003 if ( is_array( $form['options'] ) ) {
1004 $html .= '<div id="wpinsights-goodbye-options" class="wpinsights-goodbye-options"><ul>';
1005 foreach ( $form['options'] as $option ) {
1006 if ( is_array( $option ) ) {
1007 $id = strtolower( str_replace( ' ', '_', esc_attr( $option['label'] ) ) );
1008 $id = $id . '_' . $this->plugin_name;
1009 $html .= '<li class="has-goodbye-extra">';
1010 $html .= '<input type="radio" name="wpinsights-' . esc_attr( $this->plugin_name ) . '-goodbye-options" id="' . esc_attr( $id ) . '" value="' . esc_attr( $option['label'] ) . '">';
1011 $html .= '<div><label for="' . esc_attr( $id ) . '">' . esc_attr( $option['label'] ) . '</label>';
1012 if ( isset( $option['extra_field'] ) && ! isset( $option['type'] ) ) {
1013 $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'] ) . '">';
1014 }
1015 if ( isset( $option['extra_field'] ) && isset( $option['type'] ) ) {
1016 $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'] . '>';
1017 }
1018 $html .= '</div></li>';
1019 } else {
1020 $id = strtolower( str_replace( ' ', '_', esc_attr( $option ) ) );
1021 $id = $id . '_' . $this->plugin_name;
1022 $html .= '<li><input type="radio" name="wpinsights-' . esc_attr( $this->plugin_name ) . '-goodbye-options" id="' . esc_attr( $id ) . '" value="' . esc_attr( $option ) . '"> <label for="' . esc_attr( $id ) . '">' . esc_attr( $option ) . '</label></li>';
1023 }
1024 }
1025 $html .= '</ul></div><!-- .wpinsights-' . esc_attr( $this->plugin_name ) . '-goodbye-options -->';
1026 }
1027 $html .= '</div><!-- .wpinsights-goodbye-form-body -->';
1028 $html .= '<p class="deactivating-spinner"><span class="spinner"></span> ' . __( 'Submitting form', 'notificationx' ) . '</p>';
1029
1030 ?>
1031 <script type="text/javascript">
1032 jQuery(document).ready(function($){
1033 $("#wpinsights-goodbye-link-<?php echo esc_attr( $this->plugin_name ); ?>").on("click",function(){
1034 // We'll send the user to this deactivation link when they've completed or dismissed the form
1035 var url = document.getElementById("wpinsights-goodbye-link-<?php echo esc_attr( $this->plugin_name ); ?>");
1036 $('body').toggleClass('wpinsights-form-active-<?php echo esc_attr( $this->plugin_name ); ?>');
1037 $(".wpinsights-goodbye-form-wrapper-<?php echo esc_attr( $this->plugin_name ); ?> #wpinsights-goodbye-form").fadeIn();
1038 <?php
1039 /*
1040 * $html is assembled above with esc_html()/esc_attr() applied to every
1041 * interpolated value. It holds the radio/textarea controls of the
1042 * deactivation form, which wp_kses_post() and nx_allowed_html() would
1043 * both strip, breaking the form.
1044 */
1045 ?>
1046 $(".wpinsights-goodbye-form-wrapper-<?php echo esc_attr( $this->plugin_name ); ?> #wpinsights-goodbye-form").html( '<?php echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>' + '<div class="wpinsights-goodbye-form-footer"><div class="wpinsights-goodbye-form-buttons"><a id="wpinsights-submit-form-<?php echo esc_attr( $this->plugin_name ); ?>" class="wpinsights-submit-btn" href="#"><?php esc_html_e( 'Submit and Deactivate', 'notificationx' ); ?></a>&nbsp;<a class="wpsp-put-deactivate-btn" href="'+url+'"><?php esc_html_e( 'Just Deactivate', 'notificationx' ); ?></a></div></div>');
1047 $('#wpinsights-submit-form-<?php echo esc_attr( $this->plugin_name ); ?>').on('click', function(e){
1048 // As soon as we click, the body of the form should disappear
1049 $("#wpinsights-goodbye-form-<?php echo esc_attr( $this->plugin_name ); ?> .wpinsights-goodbye-form-body").fadeOut();
1050 $("#wpinsights-goodbye-form-<?php echo esc_attr( $this->plugin_name ); ?> .wpinsights-goodbye-form-footer").fadeOut();
1051 // Fade in spinner
1052 $("#wpinsights-goodbye-form-<?php echo esc_attr( $this->plugin_name ); ?> .deactivating-spinner").fadeIn();
1053 e.preventDefault();
1054 var checkedInput = $("input[name='wpinsights-<?php echo esc_attr( $this->plugin_name ); ?>-goodbye-options']:checked"),
1055 checkedInputVal, details;
1056 if( checkedInput.length > 0 ) {
1057 checkedInputVal = checkedInput.val();
1058 details = $('input[name="'+ checkedInput[0].id +'"], textarea[name="'+ checkedInput[0].id +'"]').val();
1059 }
1060
1061 if( typeof details === 'undefined' ) {
1062 details = '';
1063 }
1064 if( typeof checkedInputVal === 'undefined' ) {
1065 checkedInputVal = 'No Reason';
1066 }
1067
1068 var data = {
1069 'action': 'deactivation_form_<?php echo esc_attr( $this->plugin_name ); ?>',
1070 'values': checkedInputVal,
1071 'details': details,
1072 'security': "<?php echo esc_js( wp_create_nonce( 'wpins_deactivation_nonce' ) ); ?>",
1073 'dataType': "json"
1074 }
1075
1076 $.post(
1077 ajaxurl,
1078 data,
1079 function(response){
1080 // Redirect to original deactivation URL
1081 window.location.href = url;
1082 }
1083 );
1084 });
1085 $('#wpinsights-goodbye-options > ul ').on('click', 'li label, li > input', function( e ){
1086 var parent = $(this).parents('li');
1087 parent.siblings().find('label').next('input, textarea').css('display', 'none');
1088 parent.find('label').next('input, textarea').css('display', 'block');
1089 });
1090 // If we click outside the form, the form will close
1091 $('.wpinsights-goodbye-form-bg').on('click',function(){
1092 $("#wpinsights-goodbye-form").fadeOut();
1093 $('body').removeClass('wpinsights-form-active-<?php echo esc_attr( $this->plugin_name ); ?>');
1094 });
1095 });
1096 });
1097 </script>
1098 <?php
1099 }
1100 }
1101