PluginProbe
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar / trunk
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar vtrunk
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 / Abilities / Read / EntriesReader.php

EntriesReader.php in NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar trunk, at includes/Abilities/Read/EntriesReader.php

147 lines 5.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Shared query/formatting for the entry-reading abilities (list + export).
4 *
5 * Entries are the submissions collected by NotificationX form notifications —
6 * the Popup and Exit Intent forms — and stored in the `nx_entries` table. They
7 * are the same rows surfaced on the admin "Feedback Entries" screen and its CSV
8 * export, so this trait mirrors that feature exactly: the same two form sources,
9 * the same search/notification filters, and the same Free/Pro field split
10 * (message on Free; name + email added when Pro is active).
11 *
12 * @package NotificationX\Abilities\Read
13 */
14
15 namespace NotificationX\Abilities\Read;
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit;
19 }
20
21 trait EntriesReader {
22
23 /**
24 * The notification sources whose submissions are treated as form entries —
25 * identical to the admin Feedback Entries screen.
26 *
27 * @return string[]
28 */
29 protected function form_sources() {
30 return array( 'popup_notification', 'exit_intent_custom' );
31 }
32
33 /**
34 * Whether Pro is active (unlocks the name + email fields, exactly like the
35 * admin CSV export's `is_pro()` branch).
36 *
37 * @return bool
38 */
39 protected function entries_is_pro() {
40 return class_exists( '\NotificationXPro\NotificationX' ) && \NotificationX\NotificationX::is_pro();
41 }
42
43 /**
44 * Query form entries with optional notification/source/search filters and
45 * optional pagination.
46 *
47 * @param array $args nx_id, source, search.
48 * @param int|null $limit Max rows (null = no limit, for export).
49 * @param int $offset Row offset.
50 * @return array{0: array[], 1: int} [rows, total_matching]
51 */
52 protected function query_entries( $args, $limit = null, $offset = 0 ) {
53 global $wpdb;
54
55 $entries_table = $wpdb->prefix . 'nx_entries';
56 $posts_table = $wpdb->prefix . 'nx_posts';
57
58 // Default to both form sources; allow narrowing to one of them.
59 $sources = $this->form_sources();
60 if ( ! empty( $args['source'] ) && in_array( $args['source'], $sources, true ) ) {
61 $sources = array( $args['source'] );
62 }
63
64 $placeholders = implode( ',', array_fill( 0, count( $sources ), '%s' ) );
65 $where = array( "e.source IN ({$placeholders})" );
66 $values = $sources;
67
68 if ( ! empty( $args['nx_id'] ) ) {
69 $where[] = 'e.nx_id = %d';
70 $values[] = (int) $args['nx_id'];
71 }
72 if ( isset( $args['search'] ) && '' !== $args['search'] ) {
73 $where[] = '(e.data LIKE %s OR e.created_at LIKE %s)';
74 $like = '%' . $wpdb->esc_like( (string) $args['search'] ) . '%';
75 $values[] = $like;
76 $values[] = $like;
77 }
78
79 $where_sql = implode( ' AND ', $where );
80
81 // Total matching (before pagination).
82 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Prepared below; only $wpdb->prefix table names are interpolated.
83 $total = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$entries_table} e WHERE {$where_sql}", ...$values ) );
84
85 $limit_sql = '';
86 $page_values = $values;
87 if ( null !== $limit ) {
88 $limit_sql = ' LIMIT %d OFFSET %d';
89 $page_values[] = (int) $limit;
90 $page_values[] = max( 0, (int) $offset );
91 }
92
93 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Prepared below; only $wpdb->prefix table names are interpolated.
94 $rows = $wpdb->get_results(
95 $wpdb->prepare(
96 "SELECT e.entry_id, e.nx_id, e.source, e.data, e.created_at, p.title AS notification_name
97 FROM {$entries_table} e
98 LEFT JOIN {$posts_table} p ON e.nx_id = p.nx_id
99 WHERE {$where_sql}
100 ORDER BY e.created_at DESC{$limit_sql}",
101 ...$page_values
102 ),
103 ARRAY_A
104 );
105
106 return array( is_array( $rows ) ? $rows : array(), $total );
107 }
108
109 /**
110 * Flatten a raw entry row into the response shape. `message` is always
111 * present; `name` and `email` are included only when Pro is active.
112 *
113 * @param array $row Raw DB row (entry_id, nx_id, source, data, created_at, notification_name).
114 * @param bool $is_pro Whether to expose the Pro contact fields.
115 * @return array
116 */
117 protected function map_entry_row( $row, $is_pro ) {
118 $data = maybe_unserialize( isset( $row['data'] ) ? $row['data'] : '' );
119 if ( ! is_array( $data ) ) {
120 $data = array();
121 }
122
123 $nx_id = isset( $row['nx_id'] ) ? (int) $row['nx_id'] : 0;
124 $title = ! empty( $row['notification_name'] )
125 ? $row['notification_name']
126 /* translators: %d: notification id. */
127 : sprintf( __( 'Notification #%d', 'notificationx' ), $nx_id );
128
129 $entry = array(
130 'entry_id' => isset( $row['entry_id'] ) ? (int) $row['entry_id'] : 0,
131 'nx_id' => $nx_id,
132 'notification' => $title,
133 'source' => isset( $row['source'] ) ? $row['source'] : '',
134 'created_at' => isset( $row['created_at'] ) ? $row['created_at'] : '',
135 );
136
137 if ( $is_pro ) {
138 $entry['name'] = isset( $data['name'] ) ? $data['name'] : '';
139 $entry['email'] = isset( $data['email'] ) ? $data['email'] : '';
140 }
141
142 $entry['message'] = isset( $data['message'] ) ? $data['message'] : '';
143
144 return $entry;
145 }
146 }
147