PluginProbe
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar / 3.3.3
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar v3.3.3
3.3.3 3.3.2 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 All 158 releases
← All changes | includes/Admin/PluginInsights.php +82 -37 3.2.10 → 3.3.3 View file →
@@ -162,10 +162,11 @@
162 162 *
163 163 * @return void
164 164 */
165 165 private function redirect_to() {
166 - $request_uri = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH );
167 - $query_string = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_QUERY );
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 );
168 169 parse_str( $query_string, $current_url );
169 170
170 171 $unset_array = array( 'dismiss', 'plugin', '_wpnonce', 'later', 'plugin_action', 'marketing_optin' );
171 172
@@ -187,12 +188,17 @@
187 188 public function force_tracking() {
188 189 $this->do_tracking( true );
189 190 }
190 191 /**
191 - * Programmatically opt the site into tracking and (optionally) send the
192 - * data to the insights API immediately. Used by the onboarding Setup Wizard
193 - * when the user proceeds past the Welcome step (which is the consent point).
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).
194 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 + *
195 201 * @param bool $send Send the collected data right away.
196 202 * @return bool|\WP_Error
197 203 */
198 204 public function optin( $send = true ) {
@@ -239,25 +245,51 @@
239 245 */
240 246 return $this->send_data( $body );
241 247 }
242 248 /**
243 - * Is tracking allowed?
249 + * Is data collection allowed?
244 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 + *
245 263 * @since 1.0.0
246 264 */
247 265 private function is_tracking_allowed() {
248 - // First, check if the user has changed their mind and opted out of tracking
266 + // A programmatic opt-out (an option flagged via the `options` arg) is
267 + // still honoured and clears any recorded consent.
249 268 if ( $this->has_user_opted_out() ) {
250 269 $this->set_is_tracking_allowed( false, $this->plugin_name );
251 270 return false;
252 271 }
253 - // The wpins_allow_tracking option is an array of plugins that are being tracked
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.
254 290 $allow_tracking = get_option( 'wpins_allow_tracking' );
255 - // If this plugin is in the array, then tracking is allowed
256 - if ( isset( $allow_tracking[ $this->plugin_name ] ) ) {
257 - return true;
258 - }
259 - return false;
291 + return is_array( $allow_tracking ) && isset( $allow_tracking[ $this->plugin_name ] );
260 292 }
261 293 /**
262 294 * Set a flag in DB If tracking is allowed.
263 295 *
@@ -363,9 +395,9 @@
363 395 $body['email'] = $email;
364 396 }
365 397 }
366 398 $body['marketing_method'] = $this->marketing;
367 - $body['server'] = isset( $_SERVER['SERVER_SOFTWARE'] ) ? $_SERVER['SERVER_SOFTWARE'] : '';
399 + $body['server'] = isset( $_SERVER['SERVER_SOFTWARE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) : '';
368 400
369 401 /**
370 402 * Collect all active and inactive plugins
371 403 */
@@ -392,9 +424,9 @@
392 424 * @since 3.0.0
393 425 */
394 426 $plugin = $this->plugin_data();
395 427 if ( empty( $plugin ) ) {
396 - $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.', 'plugin-usage-tracker' );
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' );
397 429 $body['status'] = 'NOT FOUND';
398 430 } else {
399 431 if ( isset( $plugin['Name'] ) ) {
400 432 $body['plugin'] = sanitize_text_field( $plugin['Name'] );
@@ -426,8 +458,9 @@
426 458 * @since 3.3.0
427 459 * @param array $body Collected tracking data.
428 460 * @param PluginInsights $this Current insights instance.
429 461 */
462 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
430 463 $body = apply_filters( 'nx_plugin_usage_tracker_data', $body, $this );
431 464
432 465 return $body;
433 466 }
@@ -466,9 +499,9 @@
466 499 * Send Initial Data to API
467 500 */
468 501 if ( $site_id == false && $this->item_id !== false && $original_site_url === false ) {
469 502 if ( isset( $_SERVER['REMOTE_ADDR'] ) && ! empty( $_SERVER['REMOTE_ADDR'] && $_SERVER['REMOTE_ADDR'] != '127.0.0.1' ) ) {
470 - $country_request = wp_remote_get( 'http://ip-api.com/json/' . $_SERVER['REMOTE_ADDR'] . '?fields=country' );
503 + $country_request = wp_remote_get( 'http://ip-api.com/json/' . sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) . '?fields=country' );
471 504 if ( ! is_wp_error( $country_request ) && $country_request['response']['code'] == 200 ) {
472 505 $ip_data = json_decode( $country_request['body'] );
473 506 $body['country'] = isset( $ip_data->country ) ? $ip_data->country : 'NOT SET';
474 507 }
@@ -673,11 +706,11 @@
673 706 * @return void
674 707 */
675 708 public function set_notice_options( $options = [] ) {
676 709 $default_options = [
677 - 'consent_button_text' => __( 'What we collect.', 'wpinsight' ),
678 - 'yes' => __( 'Sure, I\'d like to help', 'wpinsight' ),
679 - 'no' => __( 'No Thanks.', 'wpinsight' ),
710 + 'consent_button_text' => __( 'What we collect.', 'notificationx' ),
711 + 'yes' => __( 'Sure, I\'d like to help', 'notificationx' ),
712 + 'no' => __( 'No Thanks.', 'notificationx' ),
680 713 ];
681 714 $options = wp_parse_args( $options, $default_options );
682 715 $this->notice_options = $options;
683 716 }
@@ -691,14 +724,14 @@
691 724 if ( isset( $_GET['tab'] ) && $_GET['tab'] === 'plugin-information' ) {
692 725 return;
693 726 }
694 727
695 - if( ! wp_verify_nonce( $_GET[ '_wpnonce' ], '_wpnonce_optin_' . $this->plugin_name ) ) {
728 + if( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET[ '_wpnonce' ] ) ), '_wpnonce_optin_' . $this->plugin_name ) ) {
696 729 return;
697 730 }
698 731
699 - $plugin = sanitize_text_field( $_GET['plugin'] );
700 - $action = sanitize_text_field( $_GET['plugin_action'] );
732 + $plugin = sanitize_text_field( wp_unslash( $_GET['plugin'] ) );
733 + $action = sanitize_text_field( wp_unslash( $_GET['plugin_action'] ) );
701 734 if ( $action == 'yes' ) {
702 735 $this->schedule_tracking();
703 736 $this->set_is_tracking_allowed( true, $plugin );
704 737 if ( $this->do_tracking( true ) ) {
@@ -743,13 +776,13 @@
743 776 */
744 777 public function deactivate_reasons_form_submit() {
745 778 check_ajax_referer( 'wpins_deactivation_nonce', 'security' );
746 779 if ( isset( $_POST['values'] ) ) {
747 - $values = sanitize_text_field( $_POST['values'] );
780 + $values = sanitize_text_field( wp_unslash( $_POST['values'] ) );
748 781 update_option( 'wpins_deactivation_reason_' . $this->plugin_name, $values, 'no' );
749 782 }
750 783 if ( isset( $_POST['details'] ) ) {
751 - $details = sanitize_text_field( $_POST['details'] );
784 + $details = sanitize_text_field( wp_unslash( $_POST['details'] ) );
752 785 update_option( 'wpins_deactivation_details_' . $this->plugin_name, $details, 'no' );
753 786 }
754 787 echo 'success';
755 788 wp_die();
@@ -760,9 +793,12 @@
760 793 * @since 3.0.0
761 794 */
762 795 public function deactivate_action_links( $links ) {
763 796 /**
764 - * Check is tracking allowed or not.
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.
765 801 */
766 802 if ( ! $this->is_tracking_allowed() ) {
767 803 return $links;
768 804 }
@@ -782,25 +818,26 @@
782 818 * @since 3.0.0
783 819 */
784 820 public function deactivation_reasons() {
785 821 $form = array();
786 - $form['heading'] = __( 'Sorry to see you go', 'wpinsight' );
787 - $form['body'] = __( 'Before you deactivate the plugin, would you quickly give us your reason for doing so?', 'wpinsight' );
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' );
788 824
789 825 $form['options'] = array(
790 - __( 'I no longer need the plugin', 'wpinsight' ),
826 + __( 'I no longer need the plugin', 'notificationx' ),
791 827 [
792 - 'label' => __( 'I found a better plugin', 'wpinsight' ),
793 - 'extra_field' => __( 'Please share which plugin', 'wpinsight' ),
828 + 'label' => __( 'I found a better plugin', 'notificationx' ),
829 + 'extra_field' => __( 'Please share which plugin', 'notificationx' ),
794 830 ],
795 - __( "I couldn't get the plugin to work", 'wpinsight' ),
796 - __( 'It\'s a temporary deactivation', 'wpinsight' ),
831 + __( "I couldn't get the plugin to work", 'notificationx' ),
832 + __( 'It\'s a temporary deactivation', 'notificationx' ),
797 833 [
798 - 'label' => __( 'Other', 'wpinsight' ),
799 - 'extra_field' => __( 'Please share the reason', 'wpinsight' ),
834 + 'label' => __( 'Other', 'notificationx' ),
835 + 'extra_field' => __( 'Please share the reason', 'notificationx' ),
800 836 'type' => 'textarea',
801 837 ],
802 838 );
839 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
803 840 return apply_filters( 'wpins_form_text_' . $this->plugin_name, $form );
804 841 }
805 842 /**
806 843 * Deactivate Reasons Form.
@@ -987,9 +1024,9 @@
987 1024 }
988 1025 $html .= '</ul></div><!-- .wpinsights-' . esc_attr( $this->plugin_name ) . '-goodbye-options -->';
989 1026 }
990 1027 $html .= '</div><!-- .wpinsights-goodbye-form-body -->';
991 - $html .= '<p class="deactivating-spinner"><span class="spinner"></span> ' . __( 'Submitting form', 'wpinsight' ) . '</p>';
1028 + $html .= '<p class="deactivating-spinner"><span class="spinner"></span> ' . __( 'Submitting form', 'notificationx' ) . '</p>';
992 1029
993 1030 ?>
994 1031 <script type="text/javascript">
995 1032 jQuery(document).ready(function($){
@@ -997,9 +1034,17 @@
997 1034 // We'll send the user to this deactivation link when they've completed or dismissed the form
998 1035 var url = document.getElementById("wpinsights-goodbye-link-<?php echo esc_attr( $this->plugin_name ); ?>");
999 1036 $('body').toggleClass('wpinsights-form-active-<?php echo esc_attr( $this->plugin_name ); ?>');
1000 1037 $(".wpinsights-goodbye-form-wrapper-<?php echo esc_attr( $this->plugin_name ); ?> #wpinsights-goodbye-form").fadeIn();
1001 - $(".wpinsights-goodbye-form-wrapper-<?php echo esc_attr( $this->plugin_name ); ?> #wpinsights-goodbye-form").html( '<?php echo $html; ?>' + '<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', 'wpinsight' ); ?></a>&nbsp;<a class="wpsp-put-deactivate-btn" href="'+url+'"><?php esc_html_e( 'Just Deactivate', 'wpinsight' ); ?></a></div></div>');
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>');
1002 1047 $('#wpinsights-submit-form-<?php echo esc_attr( $this->plugin_name ); ?>').on('click', function(e){
1003 1048 // As soon as we click, the body of the form should disappear
1004 1049 $("#wpinsights-goodbye-form-<?php echo esc_attr( $this->plugin_name ); ?> .wpinsights-goodbye-form-body").fadeOut();
1005 1050 $("#wpinsights-goodbye-form-<?php echo esc_attr( $this->plugin_name ); ?> .wpinsights-goodbye-form-footer").fadeOut();
@@ -1023,9 +1068,9 @@
1023 1068 var data = {
1024 1069 'action': 'deactivation_form_<?php echo esc_attr( $this->plugin_name ); ?>',
1025 1070 'values': checkedInputVal,
1026 1071 'details': details,
1027 - 'security': "<?php echo wp_create_nonce( 'wpins_deactivation_nonce' ); ?>",
1072 + 'security': "<?php echo esc_js( wp_create_nonce( 'wpins_deactivation_nonce' ) ); ?>",
1028 1073 'dataType': "json"
1029 1074 }
1030 1075
1031 1076 $.post(