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/Core/Rest/Popup.php +32 -4 3.2.7 → 3.3.3 View file →
@@ -57,10 +57,11 @@
57 57 'callback' => [ $this , 'handle_popup_submission' ],
58 58 'permission_callback' => '__return_true',
59 59 'args' => [
60 60 'nx_id' => [
61 - 'required' => true,
62 - 'type' => 'string',
61 + 'required' => true,
62 + 'type' => 'integer',
63 + 'sanitize_callback' => 'absint',
63 64 ],
64 65 'email' => [
65 66 'type' => 'string',
66 67 'sanitize_callback' => 'sanitize_email',
@@ -72,10 +73,22 @@
72 73 'name' => [
73 74 'type' => 'string',
74 75 'sanitize_callback' => 'sanitize_textarea_field',
75 76 ],
77 + // `title` and `theme` are persisted into the entry data and later
78 + // shown in the admin Feedback Entries screen and CSV export, so
79 + // sanitize them on the way in instead of storing raw input.
80 + 'title' => [
81 + 'type' => 'string',
82 + 'sanitize_callback' => 'sanitize_text_field',
83 + ],
84 + 'theme' => [
85 + 'type' => 'string',
86 + 'sanitize_callback' => 'sanitize_text_field',
87 + ],
76 88 'timestamp' => [
77 - 'type' => 'integer',
89 + 'type' => 'integer',
90 + 'sanitize_callback' => 'absint',
78 91 ],
79 92 ],
80 93 ]);
81 94
@@ -217,16 +230,20 @@
217 230 $where_clause = implode(' AND ', $where_conditions);
218 231
219 232 // Get total count for pagination
220 233 $total_query = $wpdb->prepare(
234 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- 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.
221 235 "SELECT COUNT(*) FROM {$table_name} e WHERE {$where_clause}",
222 236 ...$where_values
223 237 );
238 + // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- 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.
224 239 $total_items = (int) $wpdb->get_var($total_query);
225 240
226 241 // Get paginated entries with notification information
227 242 $posts_table = $wpdb->prefix . 'nx_posts';
243 + // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- 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.
228 244 $entries_query = $wpdb->prepare(
245 + // phpcs:disable 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.
229 246 "SELECT e.*, p.title as notification_name, p.nx_id as notification_id
230 247 FROM {$table_name} e
231 248 LEFT JOIN {$posts_table} p ON e.nx_id = p.nx_id
232 249 WHERE {$where_clause}
@@ -233,8 +250,10 @@
233 250 ORDER BY e.created_at DESC
234 251 LIMIT %d OFFSET %d",
235 252 ...array_merge($where_values, [$per_page, $offset])
236 253 );
254 + // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
255 + // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- 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.
237 256 $entries = $wpdb->get_results($entries_query, ARRAY_A);
238 257
239 258 $formatted_entries = [];
240 259 foreach ($entries as $entry) {
@@ -277,11 +296,13 @@
277 296
278 297 $sources = $this->form_sources();
279 298 $src_placeholders = implode(',', array_fill(0, count($sources), '%s'));
280 299 $delete_query = $wpdb->prepare(
300 + // phpcs:ignore 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.
281 301 "DELETE FROM {$table_name} WHERE entry_id = %d AND source IN ({$src_placeholders})",
282 302 array_merge([$entry_id], $sources)
283 303 );
304 + // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- 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.
284 305 $result = $wpdb->query($delete_query);
285 306
286 307 if ($result === false) {
287 308 return new \WP_REST_Response([
@@ -332,12 +353,14 @@
332 353 $src_placeholders = implode(',', array_fill(0, count($sources), '%s'));
333 354
334 355 // Prepare the query with source filter
335 356 $query = $wpdb->prepare(
357 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- 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.
336 358 "DELETE FROM {$table_name} WHERE entry_id IN ({$placeholders}) AND source IN ({$src_placeholders})",
337 359 array_merge($entry_ids, $sources)
338 360 );
339 361
362 + // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- 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.
340 363 $result = $wpdb->query($query);
341 364
342 365 if ($result === false) {
343 366 return new \WP_REST_Response([
@@ -394,8 +417,9 @@
394 417
395 418 // Get all entries for export (no pagination)
396 419 $posts_table = $wpdb->prefix . 'nx_posts';
397 420 $query = $wpdb->prepare(
421 + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- 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.
398 422 "SELECT e.entry_id, e.nx_id, e.data, e.created_at, p.title as notification_name
399 423 FROM {$table_name} e
400 424 LEFT JOIN {$posts_table} p ON e.nx_id = p.nx_id
401 425 WHERE {$where_clause}
@@ -402,9 +426,11 @@
402 426 ORDER BY e.created_at DESC",
403 427 ...$where_values
404 428 );
405 429
430 + // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- 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.
406 431 $entries = $wpdb->get_results($query, ARRAY_A);
432 + // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
407 433
408 434 if (empty($entries)) {
409 435 return new \WP_REST_Response([
410 436 'success' => false,
@@ -415,9 +441,9 @@
415 441 // Generate CSV content
416 442 $csv_data = $this->generate_csv_data($entries);
417 443
418 444 // Generate filename
419 - $filename = 'notificationx-feedback-entries-' . date('Y-m-d-H-i-s') . '.csv';
445 + $filename = 'notificationx-feedback-entries-' . gmdate('Y-m-d-H-i-s') . '.csv';
420 446
421 447 return new \WP_REST_Response([
422 448 'success' => true,
423 449 'csv_content' => $csv_data,
@@ -422,8 +448,9 @@
422 448 'success' => true,
423 449 'csv_content' => $csv_data,
424 450 'filename' => $filename,
425 451 'total_entries' => count($entries),
452 + /* translators: %d: number of entries prepared for export */
426 453 'message' => sprintf(__('Successfully prepared %d entries for export', 'notificationx'), count($entries))
427 454 ], 200);
428 455 }
429 456
@@ -461,8 +488,9 @@
461 488
462 489 $row = [
463 490 $counter++,
464 491 $date->format('F j, Y'),
492 + /* translators: %d: notification ID */
465 493 $entry['notification_name'] ?: sprintf(__('Notification #%d', 'notificationx'), $entry['nx_id']),
466 494 ];
467 495
468 496 if ($is_pro) {