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 +95 -38 3.2.9 → 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 ) {
@@ -213,9 +219,9 @@
213 219 public function do_tracking( $force = false ) {
214 220 /**
215 221 * Check URL is set or not.
216 222 */
217 - if ( defined('NX_DEBUG') || empty( self::API_URL ) ) {
223 + if ( ( defined('NX_DEBUG') && NX_DEBUG ) || empty( self::API_URL ) ) {
218 224 return;
219 225 }
220 226 /**
221 227 * Check is tracking allowed or not.
@@ -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'] );
@@ -416,8 +448,21 @@
416 448 }
417 449 if ( $theme->Version ) {
418 450 $body['theme_version'] = sanitize_text_field( $theme->Version );
419 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 +
420 465 return $body;
421 466 }
422 467
423 468 /**
@@ -454,9 +499,9 @@
454 499 * Send Initial Data to API
455 500 */
456 501 if ( $site_id == false && $this->item_id !== false && $original_site_url === false ) {
457 502 if ( isset( $_SERVER['REMOTE_ADDR'] ) && ! empty( $_SERVER['REMOTE_ADDR'] && $_SERVER['REMOTE_ADDR'] != '127.0.0.1' ) ) {
458 - $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' );
459 504 if ( ! is_wp_error( $country_request ) && $country_request['response']['code'] == 200 ) {
460 505 $ip_data = json_decode( $country_request['body'] );
461 506 $body['country'] = isset( $ip_data->country ) ? $ip_data->country : 'NOT SET';
462 507 }
@@ -661,11 +706,11 @@
661 706 * @return void
662 707 */
663 708 public function set_notice_options( $options = [] ) {
664 709 $default_options = [
665 - 'consent_button_text' => __( 'What we collect.', 'wpinsight' ),
666 - 'yes' => __( 'Sure, I\'d like to help', 'wpinsight' ),
667 - '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' ),
668 713 ];
669 714 $options = wp_parse_args( $options, $default_options );
670 715 $this->notice_options = $options;
671 716 }
@@ -679,14 +724,14 @@
679 724 if ( isset( $_GET['tab'] ) && $_GET['tab'] === 'plugin-information' ) {
680 725 return;
681 726 }
682 727
683 - 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 ) ) {
684 729 return;
685 730 }
686 731
687 - $plugin = sanitize_text_field( $_GET['plugin'] );
688 - $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'] ) );
689 734 if ( $action == 'yes' ) {
690 735 $this->schedule_tracking();
691 736 $this->set_is_tracking_allowed( true, $plugin );
692 737 if ( $this->do_tracking( true ) ) {
@@ -731,13 +776,13 @@
731 776 */
732 777 public function deactivate_reasons_form_submit() {
733 778 check_ajax_referer( 'wpins_deactivation_nonce', 'security' );
734 779 if ( isset( $_POST['values'] ) ) {
735 - $values = sanitize_text_field( $_POST['values'] );
780 + $values = sanitize_text_field( wp_unslash( $_POST['values'] ) );
736 781 update_option( 'wpins_deactivation_reason_' . $this->plugin_name, $values, 'no' );
737 782 }
738 783 if ( isset( $_POST['details'] ) ) {
739 - $details = sanitize_text_field( $_POST['details'] );
784 + $details = sanitize_text_field( wp_unslash( $_POST['details'] ) );
740 785 update_option( 'wpins_deactivation_details_' . $this->plugin_name, $details, 'no' );
741 786 }
742 787 echo 'success';
743 788 wp_die();
@@ -748,9 +793,12 @@
748 793 * @since 3.0.0
749 794 */
750 795 public function deactivate_action_links( $links ) {
751 796 /**
752 - * 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.
753 801 */
754 802 if ( ! $this->is_tracking_allowed() ) {
755 803 return $links;
756 804 }
@@ -770,25 +818,26 @@
770 818 * @since 3.0.0
771 819 */
772 820 public function deactivation_reasons() {
773 821 $form = array();
774 - $form['heading'] = __( 'Sorry to see you go', 'wpinsight' );
775 - $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' );
776 824
777 825 $form['options'] = array(
778 - __( 'I no longer need the plugin', 'wpinsight' ),
826 + __( 'I no longer need the plugin', 'notificationx' ),
779 827 [
780 - 'label' => __( 'I found a better plugin', 'wpinsight' ),
781 - 'extra_field' => __( 'Please share which plugin', 'wpinsight' ),
828 + 'label' => __( 'I found a better plugin', 'notificationx' ),
829 + 'extra_field' => __( 'Please share which plugin', 'notificationx' ),
782 830 ],
783 - __( "I couldn't get the plugin to work", 'wpinsight' ),
784 - __( 'It\'s a temporary deactivation', 'wpinsight' ),
831 + __( "I couldn't get the plugin to work", 'notificationx' ),
832 + __( 'It\'s a temporary deactivation', 'notificationx' ),
785 833 [
786 - 'label' => __( 'Other', 'wpinsight' ),
787 - 'extra_field' => __( 'Please share the reason', 'wpinsight' ),
834 + 'label' => __( 'Other', 'notificationx' ),
835 + 'extra_field' => __( 'Please share the reason', 'notificationx' ),
788 836 'type' => 'textarea',
789 837 ],
790 838 );
839 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
791 840 return apply_filters( 'wpins_form_text_' . $this->plugin_name, $form );
792 841 }
793 842 /**
794 843 * Deactivate Reasons Form.
@@ -975,9 +1024,9 @@
975 1024 }
976 1025 $html .= '</ul></div><!-- .wpinsights-' . esc_attr( $this->plugin_name ) . '-goodbye-options -->';
977 1026 }
978 1027 $html .= '</div><!-- .wpinsights-goodbye-form-body -->';
979 - $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>';
980 1029
981 1030 ?>
982 1031 <script type="text/javascript">
983 1032 jQuery(document).ready(function($){
@@ -985,9 +1034,17 @@
985 1034 // We'll send the user to this deactivation link when they've completed or dismissed the form
986 1035 var url = document.getElementById("wpinsights-goodbye-link-<?php echo esc_attr( $this->plugin_name ); ?>");
987 1036 $('body').toggleClass('wpinsights-form-active-<?php echo esc_attr( $this->plugin_name ); ?>');
988 1037 $(".wpinsights-goodbye-form-wrapper-<?php echo esc_attr( $this->plugin_name ); ?> #wpinsights-goodbye-form").fadeIn();
989 - $(".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>');
990 1047 $('#wpinsights-submit-form-<?php echo esc_attr( $this->plugin_name ); ?>').on('click', function(e){
991 1048 // As soon as we click, the body of the form should disappear
992 1049 $("#wpinsights-goodbye-form-<?php echo esc_attr( $this->plugin_name ); ?> .wpinsights-goodbye-form-body").fadeOut();
993 1050 $("#wpinsights-goodbye-form-<?php echo esc_attr( $this->plugin_name ); ?> .wpinsights-goodbye-form-footer").fadeOut();
@@ -1011,9 +1068,9 @@
1011 1068 var data = {
1012 1069 'action': 'deactivation_form_<?php echo esc_attr( $this->plugin_name ); ?>',
1013 1070 'values': checkedInputVal,
1014 1071 'details': details,
1015 - 'security': "<?php echo wp_create_nonce( 'wpins_deactivation_nonce' ); ?>",
1072 + 'security': "<?php echo esc_js( wp_create_nonce( 'wpins_deactivation_nonce' ) ); ?>",
1016 1073 'dataType': "json"
1017 1074 }
1018 1075
1019 1076 $.post(