PluginProbe
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar / 3.2.13
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar v3.2.13
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 / MilestoneNotification.php

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

601 lines 25.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Milestone Notification for WordPress Admin Dashboard
5 *
6 * Displays a beautiful animated notification on the main WordPress dashboard (/wp-admin/)
7 * showing milestone achievements and encouraging premium upgrades.
8 *
9 * Features:
10 * - Automatically appears on dashboard after 2 seconds
11 * - Slides in from bottom-right with smooth animation
12 * - Fully responsive design
13 * - Close on button click, overlay click, or Escape key
14 * - Customizable milestone data and stats
15 *
16 * Usage:
17 * - Automatically initialized in NotificationX main class
18 * - Only loads on WordPress dashboard (index.php)
19 * - Customize data in get_milestone_data() method
20 * - Control display logic in should_show_milestone() method
21 *
22 * @package NotificationX
23 * @author NotificationX <help@notificationx.com>
24 * @copyright Copyright (C) 2023 WPDeveloper. All rights reserved.
25 * @license GPLv3 or later
26 * @since 1.0.0
27 */
28
29 namespace NotificationX\Admin;
30
31 use NotificationX\NotificationX;
32 use NotificationX\GetInstance;
33 use NotificationX\Core\Analytics;
34 use NotificationX\Core\Helper;
35
36 defined('ABSPATH') or die("No direct script access allowed.");
37
38 class MilestoneNotification
39 {
40 use GetInstance;
41
42 /**
43 * Constructor - Initialize the milestone notification
44 */
45 public function __construct()
46 {
47 // Hook into admin footer to inject the notification
48 add_action('admin_footer', [$this, 'render_milestone_notification']);
49
50 // Enqueue styles and scripts
51 add_action('admin_enqueue_scripts', [$this, 'enqueue_assets']);
52
53 // AJAX handler to mark milestone as seen
54 add_action('wp_ajax_notificationx_mark_milestone_seen', [$this, 'ajax_mark_milestone_seen']);
55 }
56
57 /**
58 * Enqueue CSS and JS for milestone notification
59 */
60 public function enqueue_assets($hook)
61 {
62 // Only load on main dashboard page
63 if ($hook !== 'index.php') {
64 return;
65 }
66
67 // Enqueue the milestone CSS - use existing admin CSS file
68 wp_enqueue_style(
69 'notificationx-milestone',
70 Helper::file('admin/css/admin.css', true),
71 [],
72 NOTIFICATIONX_VERSION
73 );
74
75 // Enqueue inline script for milestone functionality
76 wp_add_inline_script('jquery', $this->get_milestone_script());
77 }
78
79 /**
80 * Get the JavaScript for milestone functionality
81 */
82 private function get_milestone_script()
83 {
84 return "
85 jQuery(document).ready(function($) {
86 // Auto-show milestone after 2 seconds
87 setTimeout(function() {
88 showNotificationXMilestone();
89 }, 2000);
90 });
91
92 function showNotificationXMilestone() {
93 var container = document.getElementById('notificationx-milestone-container');
94 if (!container) return;
95
96 container.style.display = 'block';
97
98 // Trigger animation
99 setTimeout(function() {
100 var overlay = container.querySelector('.milestone-overlay');
101 var notification = container.querySelector('.milestone-notification');
102
103 if (overlay) overlay.classList.add('milestone-overlay--visible');
104 if (notification) notification.classList.add('milestone-notification--visible');
105 }, 100);
106 }
107
108 function hideNotificationXMilestone(event) {
109 if (event) event.preventDefault();
110
111 var container = document.getElementById('notificationx-milestone-container');
112 if (!container) return;
113
114 var overlay = container.querySelector('.milestone-overlay');
115 var notification = container.querySelector('.milestone-notification');
116
117 if (overlay) overlay.classList.remove('milestone-overlay--visible');
118 if (notification) notification.classList.remove('milestone-notification--visible');
119
120 // Remove from DOM after animation
121 setTimeout(function() {
122 container.style.display = 'none';
123 }, 400);
124
125 // Mark milestone as seen via AJAX
126 jQuery.post(ajaxurl, {
127 action: 'notificationx_mark_milestone_seen',
128 nonce: '" . wp_create_nonce('notificationx_milestone_nonce') . "'
129 });
130 }
131
132 // Close on Escape key
133 document.addEventListener('keydown', function(e) {
134 if (e.key === 'Escape') {
135 hideNotificationXMilestone();
136 }
137 });
138
139 // Close on overlay click
140 document.addEventListener('click', function(e) {
141 if (e.target && e.target.classList.contains('milestone-overlay')) {
142 hideNotificationXMilestone();
143 }
144 });
145 ";
146 }
147
148 /**
149 * Render the milestone notification HTML
150 */
151 public function render_milestone_notification()
152 {
153 // Only show on main dashboard
154 $screen = get_current_screen();
155 if (!$screen || $screen->id !== 'dashboard') {
156 return;
157 }
158
159 // Check if milestone should be shown
160 if (!$this->should_show_milestone()) {
161 return;
162 }
163
164 // Get milestone data
165 $data = $this->get_milestone_data();
166 $black_friday_notice = esc_url( NOTIFICATIONX_PUBLIC_URL . 'image/reports/black-friday-small.webp' );
167 $nx_icon = esc_url( NOTIFICATIONX_ADMIN_URL . 'images/nx-icon.svg' );
168
169 ?>
170 <div id="notificationx-milestone-container" style="display: none;">
171 <div class="milestone-overlay">
172 <div class="milestone-notification" onclick="event.stopPropagation()">
173 <!-- Header -->
174 <div class="milestone-header">
175 <h2 class="milestone-title">
176 <img width="24" src="<?php echo esc_url( $nx_icon ) ?>" alt="">
177 <?php echo esc_html__('NotificationX Milestones', 'notificationx'); ?>
178 </h2>
179 <button class="milestone-close" onclick="hideNotificationXMilestone(event)" aria-label="Close">
180 <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
181 <path d="M14 1.41L12.59 0L7 5.59L1.41 0L0 1.41L5.59 7L0 12.59L1.41 14L7 8.41L12.59 14L14 12.59L8.41 7L14 1.41Z" fill="currentColor" />
182 </svg>
183 </button>
184 </div>
185
186 <!-- Content -->
187 <div class="milestone-content">
188 <!-- Achievement Banner -->
189 <div class="milestone-achievement">
190 <h3 class="milestone-achievement-title">
191 <?php echo esc_html( $data['emoji'] ); ?> <?php echo wp_kses_post($data['title']); ?>
192 </h3>
193 <p class="milestone-achievement-subtitle">
194 <?php echo wp_kses_post($data['subtitle']); ?>
195 </p>
196 <a href="<?php echo esc_url(admin_url('admin.php?page=nx-analytics')); ?>" class="milestone-link <?php echo NotificationX::is_pro() ? 'pro-activated' : 'pro-deactivated' ?>">
197 <?php echo ! NotificationX::is_pro() ? esc_html__('View Analytics', 'notificationx') : esc_html__('View Detail Insights', 'notificationx'); ?>
198 </a>
199 </div>
200
201 <!-- Stats Grid -->
202 <div class="milestone-stats">
203 <div class="milestone-stats-inner-wrapper">
204 <div class="milestone-stats-inner">
205 <?php foreach ($data['stats'] as $stat) : ?>
206 <div class="milestone-stat-card">
207 <div class="milestone-stat-label"><?php echo esc_html($stat['label']); ?></div>
208 <div class="milestone-stat-value"><?php echo esc_html($stat['value']); ?></div>
209 </div>
210 <?php endforeach; ?>
211 </div>
212
213 <!-- CTA Button -->
214 <a href="<?php echo esc_url('https://notificationx.com/pricing/'); ?>" target="_blank" class="milestone-cta">
215 <?php echo esc_html__('Unlock Advanced Analytics Data', 'notificationx'); ?>
216 </a>
217 </div>
218 </div>
219 </div>
220 </div>
221 </div>
222 </div>
223 <?php
224 }
225
226 /**
227 * Get milestone data
228 * Fetches real analytics data from the database to show user's notification performance
229 */
230 private function get_milestone_data()
231 {
232 // Get Analytics instance
233 $analytics = Analytics::get_instance();
234
235 // Get real analytics data
236 $total_notifications = 0;
237 $total_views = 0;
238 $total_clicks = 0;
239 $total_ctr = 0;
240
241 try {
242 // Get total count data
243 $analytics_data = $analytics->get_total_count();
244
245 if (is_array($analytics_data)) {
246 $total_views = isset($analytics_data['totalViews']) ? $this->parse_number($analytics_data['totalViews']) : 0;
247 $total_clicks = isset($analytics_data['totalClicks']) ? $this->parse_number($analytics_data['totalClicks']) : 0;
248 $total_ctr = isset($analytics_data['totalCtr']) ? $analytics_data['totalCtr'] : 0;
249 }
250
251 // Get total notifications count
252 global $wpdb;
253 $table_name = $wpdb->prefix . 'nx_posts';
254 // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- False positive: the query is prepared via $this->wpdb->prepare(), which this sniff does not recognise, and only $wpdb->prefix table names are interpolated. Audited 2026-07-16.
255 $total_notifications = $wpdb->get_var("SELECT COUNT(*) FROM {$table_name} WHERE enabled = 1");
256 $total_notifications = $total_notifications ? intval($total_notifications) : 0;
257
258 } catch (\Exception $e) {
259 // Fallback to default values if there's an error
260 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- deliberate failure logging, not debug output.
261 error_log('NotificationX Milestone: Error fetching analytics data - ' . $e->getMessage());
262 }
263
264 // Format numbers for display
265 $notifications_formatted = $this->format_number($total_notifications);
266 $views_formatted = $this->format_number($total_views);
267 $clicks_formatted = $this->format_number($total_clicks);
268 $ctr_formatted = number_format($total_ctr, 2) . '%';
269
270 // Calculate total interactions (sum of all metrics)
271 $total_interactions = $total_notifications + $total_views + $total_clicks;
272
273 // Get milestone configuration based on total interactions
274 $milestone_config = $this->get_milestone_config($total_interactions);
275
276 return array_merge($milestone_config, [
277 'stats' => [
278 [
279 'label' => esc_html__('Total Views', 'notificationx'),
280 'value' => $views_formatted
281 ],
282 [
283 'label' => esc_html__('Total Clicks', 'notificationx'),
284 'value' => $clicks_formatted
285 ],
286 [
287 'label' => esc_html__('Click Rate', 'notificationx'),
288 'value' => $ctr_formatted
289 ]
290 ]
291 ]);
292 }
293
294 /**
295 * Parse number from formatted string (e.g., "1.2K" -> 1200)
296 */
297 private function parse_number($formatted_number)
298 {
299 if (is_numeric($formatted_number)) {
300 return intval($formatted_number);
301 }
302
303 $formatted_number = strtoupper(trim($formatted_number));
304 $multiplier = 1;
305
306 if (strpos($formatted_number, 'K') !== false) {
307 $multiplier = 1000;
308 $formatted_number = str_replace('K', '', $formatted_number);
309 } elseif (strpos($formatted_number, 'M') !== false) {
310 $multiplier = 1000000;
311 $formatted_number = str_replace('M', '', $formatted_number);
312 }
313
314 return intval(floatval($formatted_number) * $multiplier);
315 }
316
317 /**
318 * Get milestone configuration based on total interactions
319 * Returns unique title, subtitle, and emoji for each milestone level
320 */
321 private function get_milestone_config($total_interactions)
322 {
323 // Define milestone levels with unique messages
324 $milestones = [
325 2000000 => [
326 'emoji' => '',
327 'title' => wp_kses_post( __('<strong>Mega Milestone Unlocked!</strong> 🚀', 'notificationx') ),
328 'subtitle' => esc_html__( 'Your website notifications are on fire! Over 2M+ interactions have taken place across the globe.', 'notificationx' ),
329 'level' => '2m'
330 ],
331
332 1000000 => [
333 'emoji' => '',
334 'title' => wp_kses_post( __('<strong>Huge milestone achieved!</strong> 🎉', 'notificationx') ),
335 'subtitle' => esc_html__( 'You have achieved something unbelievable. 1M+ interactions have already happened on your website globally.', 'notificationx' ),
336 'level' => '1m'
337 ],
338
339 700000 => [
340 'emoji' => '',
341 'title' => wp_kses_post( __('700,000 <strong>interaction milestone reached!</strong> 🎉', 'notificationx') ),
342 'subtitle' => esc_html__( 'New interactions milestone achieved! Create more amazing notifications to reach out to a wider audience globally.', 'notificationx' ),
343 'level' => '700k'
344 ],
345
346 500000 => [
347 'emoji' => '',
348 'title' => wp_kses_post( __('<strong>You are doing amazing!</strong> 🎉', 'notificationx') ),
349 'subtitle' => esc_html__( 'Your notification is doing great and getting global clicks and impressions. See the detailed analytics below.', 'notificationx' ),
350 'level' => '500k'
351 ],
352
353 300000 => [
354 'emoji' => '',
355 'title' => wp_kses_post( __('<strong>Legendary achievement!</strong> 🎉', 'notificationx') ),
356 'subtitle' => esc_html__( 'New interactions milestone achieved! Create more amazing notifications to reach out to a wider audience globally.', 'notificationx' ),
357 'level' => '300k'
358 ],
359
360 250000 => [
361 'emoji' => '',
362 'title' => wp_kses_post( __('<strong>Diamond Status!!</strong> 🎉', 'notificationx') ),
363 'subtitle' => esc_html__( 'Your notification is grabbing attention globally. Check the detailed analytics to learn more about the performance.', 'notificationx' ),
364 'level' => '250k'
365 ],
366
367 200000 => [
368 'emoji' => '',
369 'title' => wp_kses_post( __('200,000 <strong>interaction milestone reached!</strong> 🎉', 'notificationx') ),
370 'subtitle' => esc_html__( 'Globally, 200,000 interactions are happening on your website. Check the detailed analytics to grow your audience further.', 'notificationx' ),
371 'level' => '200k'
372 ],
373
374 150000 => [
375 'emoji' => '',
376 'title' => wp_kses_post( __('Congratulations! <strong>Another milestone</strong> 🎉', 'notificationx') ),
377 'subtitle' => esc_html__( 'Your notifications are performing incredibly well worldwide. Check out the detailed analytics below!', 'notificationx' ),
378 'level' => '150k'
379 ],
380
381 100000 => [
382 'emoji' => '',
383 'title' => wp_kses_post( __('100,000 <strong>interaction milestone reached!</strong> 🎉', 'notificationx') ),
384 'subtitle' => esc_html__( 'New interactions milestone achieved! Create more amazing notifications to reach out to a wider audience globally.', 'notificationx' ),
385 'level' => '100k'
386 ],
387
388 75000 => [
389 'emoji' => '',
390 'title' => wp_kses_post( __('75,000+ <strong>interactions achieved today!</strong> 🎉', 'notificationx') ),
391 'subtitle' => esc_html__( 'Your notification is doing great and getting global clicks and impressions. See the detailed analytics below.', 'notificationx' ),
392 'level' => '75k'
393 ],
394
395 50000 => [
396 'emoji' => '',
397 'title' => wp_kses_post( __('Incredible, <strong>50,000 interactions unlocked!</strong> 🎉', 'notificationx') ),
398 'subtitle' => esc_html__( 'You are going great with your notification. Do not forget to check the detailed analytics to learn more.', 'notificationx' ),
399 'level' => '50k'
400 ],
401
402 25000 => [
403 'emoji' => '',
404 'title' => wp_kses_post( __('25,000 <strong>interaction milestone reached!</strong> 🎉', 'notificationx') ),
405 'subtitle' => esc_html__( 'Globally, 25,000 interactions are happening on your website. Check the detailed analytics to grow your audience further.', 'notificationx' ),
406 'level' => '25k'
407 ],
408
409 10000 => [
410 'emoji' => '',
411 'title' => wp_kses_post( __('Fantastic! <strong>10,000 interactions reached!</strong> 🎉', 'notificationx') ),
412 'subtitle' => esc_html__( 'New interactions milestone achieved! Create more amazing notifications to reach out to a wider audience globally.', 'notificationx' ),
413 'level' => '10k'
414 ],
415
416 5000 => [
417 'emoji' => '',
418 'title' => wp_kses_post( __('Awesome - <strong>you got 5,000 interactions!</strong> 🎉', 'notificationx') ),
419 'subtitle' => esc_html__( 'Your amazing website reached 5,000 interactions from your notification. Check the detailed analytics and achieve more.', 'notificationx' ),
420 'level' => '5k'
421 ],
422 2000 => [
423 'emoji' => '',
424 'title' => wp_kses_post( __('2,000 <strong>interaction milestone achieved!</strong> 🎉', 'notificationx') ),
425 'subtitle' => esc_html__( 'Your website has more than 2,000 interactions from your notification. Do not forget to check the detailed analytics and achieve more.', 'notificationx' ),
426 'level' => '2000'
427 ],
428
429 1000 => [
430 'emoji' => '',
431 'title' => wp_kses_post( __('Great - <strong>1,000 interactions achieved!</strong> 👏', 'notificationx') ),
432 'subtitle' => esc_html__( 'Your notifications are performing incredibly well worldwide. Check out the detailed analytics below!', 'notificationx' ),
433 'level' => '1000'
434 ],
435
436 100 => [
437 'emoji' => '',
438 'title' => wp_kses_post( __('You\'ve got <strong>100 interactions overall!</strong> 🎉', 'notificationx') ),
439 'subtitle' => esc_html__( 'Your notification is doing great and getting global clicks and impressions. See the detailed analytics below.', 'notificationx' ),
440 'level' => '100'
441 ],
442 ];
443
444
445 // Find the appropriate milestone level
446 foreach ($milestones as $threshold => $config) {
447 if ($total_interactions >= $threshold) {
448 return $config;
449 }
450 }
451
452 // Default for users below 1K interactions
453 return [];
454 }
455
456 /**
457 * Format number for display (e.g., 1000 -> 1K, 1000000 -> 1M)
458 *
459 * @param int $number
460 * @return string
461 */
462 private function format_number($number)
463 {
464 $number = (int) $number;
465
466 if ($number === 0) {
467 return '0';
468 }
469
470 if ($number >= 1000000) {
471 return round($number / 1000000, 1) . 'M';
472 }
473
474 if ($number >= 1000) {
475 return round($number / 1000, 1) . 'K';
476 }
477
478 return (string) $number;
479 }
480
481 /**
482 * Check if milestone should be shown
483 * Shows milestone only when site reaches a new level (site-wide, not per-user)
484 */
485 private function should_show_milestone()
486 {
487 // Get Analytics instance
488 $analytics = Analytics::get_instance();
489
490 // Get real analytics data
491 $total_notifications = 0;
492 $total_views = 0;
493 $total_clicks = 0;
494
495 try {
496 // Get total count data
497 $analytics_data = $analytics->get_total_count();
498
499 if (is_array($analytics_data)) {
500 $total_views = isset($analytics_data['totalViews']) ? $this->parse_number($analytics_data['totalViews']) : 0;
501 $total_clicks = isset($analytics_data['totalClicks']) ? $this->parse_number($analytics_data['totalClicks']) : 0;
502 }
503
504 // Get total notifications count
505 global $wpdb;
506 $table_name = $wpdb->prefix . 'nx_posts';
507 // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- False positive: the query is prepared via $this->wpdb->prepare(), which this sniff does not recognise, and only $wpdb->prefix table names are interpolated. Audited 2026-07-16.
508 $total_notifications = $wpdb->get_var("SELECT COUNT(*) FROM {$table_name} WHERE enabled = 1");
509 $total_notifications = $total_notifications ? intval($total_notifications) : 0;
510
511 } catch (\Exception $e) {
512 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- deliberate failure logging, not debug output.
513 error_log('NotificationX Milestone: Error fetching analytics data - ' . $e->getMessage());
514 return false;
515 }
516
517 // Calculate total interactions
518 $total_interactions = $total_notifications + $total_views + $total_clicks;
519
520 // Determine current milestone level
521 $current_level = 'starter';
522 if ($total_interactions >= 2000000) {
523 $current_level = '2m';
524 } elseif ($total_interactions >= 1000000) {
525 $current_level = '1m';
526 } elseif ($total_interactions >= 700000) {
527 $current_level = '700k';
528 } elseif ($total_interactions >= 500000) {
529 $current_level = '500k';
530 } elseif ($total_interactions >= 300000) {
531 $current_level = '300k';
532 } elseif ($total_interactions >= 250000) {
533 $current_level = '250k';
534 } elseif ($total_interactions >= 200000) {
535 $current_level = '200k';
536 } elseif ($total_interactions >= 150000) {
537 $current_level = '150k';
538 } elseif ($total_interactions >= 100000) {
539 $current_level = '100k';
540 } elseif ($total_interactions >= 75000) {
541 $current_level = '75k';
542 } elseif ($total_interactions >= 50000) {
543 $current_level = '50k';
544 } elseif ($total_interactions >= 25000) {
545 $current_level = '25k';
546 } elseif ($total_interactions >= 10000) {
547 $current_level = '10k';
548 } elseif ($total_interactions >= 5000) {
549 $current_level = '5k';
550 } elseif ($total_interactions >= 2000) {
551 $current_level = '2k';
552 } elseif ($total_interactions >= 1000) {
553 $current_level = '1k';
554 } elseif ($total_interactions >= 100) {
555 $current_level = '100';
556 } elseif( $total_interactions < 100 ) {
557 return false;
558 }
559
560
561 // Get the last seen milestone level (site-wide option)
562 $last_seen_level = get_option('notificationx_milestone_level', '');
563
564 // Show milestone if:
565 // 1. Site has never seen any milestone, OR
566 // 2. Site has reached a new milestone level
567 if (empty($last_seen_level) || $last_seen_level !== $current_level) {
568 // Don't update here - only update when user closes the notification
569 // Store current level temporarily so we can update it later
570 update_option('notificationx_milestone_current_level', $current_level);
571 return true;
572 }
573
574 return false;
575 }
576
577 /**
578 * AJAX handler to mark milestone as seen
579 */
580 public function ajax_mark_milestone_seen()
581 {
582 // Verify nonce
583 if (!isset($_POST['nonce']) || !wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'notificationx_milestone_nonce')) {
584 wp_send_json_error('Invalid nonce');
585 return;
586 }
587
588 // Get the current milestone level that was shown
589 $current_level = get_option('notificationx_milestone_current_level', '');
590
591 if (!empty($current_level)) {
592 // Mark this milestone level as seen (site-wide)
593 update_option('notificationx_milestone_level', $current_level);
594 update_option('notificationx_force_milestone', '1');
595 // Clean up the temporary option
596 delete_option('notificationx_milestone_current_level');
597 }
598
599 wp_send_json_success();
600 }
601 }