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 +1100 -0 0.2.5.5 → 3.3.3 View file →
@@ -1,0 +1,1100 @@
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 +}