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/Posts.php +21 -5 3.2.11 → 3.3.3 View file →
@@ -152,9 +152,9 @@
152 152 $params = $request->get_params();
153 153 $status = !empty($params['status']) ? $params['status'] : "all";
154 154 $page = !empty($params['page']) ? intval( $params['page'] ) : 1;
155 155 $per_page = !empty($params['per_page']) ? intval( $params['per_page'] ) : 20;
156 - $search_keyword = !empty($params['s']) ? $params['s'] : '';
156 + $search_keyword = !empty($params['s']) ? sanitize_text_field($params['s']) : '';
157 157 $start_from = ($page - 1) * $per_page;
158 158 $query = Database::get_instance()->query()
159 159 ->from('nx_posts a')
160 160 ->join('nx_stats b', 'b.nx_id', '=', 'a.nx_id')
@@ -164,11 +164,16 @@
164 164 if ($status !== 'all') {
165 165 $query->where('enabled', $status == 'enabled' ? true : false);
166 166 }
167 167 if( $search_keyword ) {
168 - $query->where(function($query) use ($search_keyword) {
169 - $query->where('title', 'LIKE', '%' . $search_keyword . '%')
170 - ->orWhere( 'a.nx_id', 'LIKE', '%'. $search_keyword . '%' );
168 + global $wpdb;
169 + // esc_like() so `%` and `_` typed into the search box match themselves
170 + // instead of acting as wildcards; the pattern is bound as a value by the
171 + // query builder.
172 + $like = '%' . $wpdb->esc_like( $search_keyword ) . '%';
173 + $query->where(function($query) use ($like) {
174 + $query->where('title', 'LIKE', $like)
175 + ->orWhere( 'a.nx_id', 'LIKE', $like );
171 176 });
172 177 }
173 178
174 179 $query->offset($start_from)
@@ -178,9 +183,11 @@
178 183
179 184 // Add entries count for popup notifications
180 185 global $wpdb;
181 186 $entries_table = $wpdb->prefix . 'nx_entries';
187 + // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- 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.
182 188 $entries_counts = $wpdb->get_results(
189 + // 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.
183 190 "SELECT nx_id, COUNT(*) as entries_count
184 191 FROM {$entries_table}
185 192 WHERE source IN ('popup_notification', 'exit_intent_custom')
186 193 GROUP BY nx_id",
@@ -185,8 +192,9 @@
185 192 WHERE source IN ('popup_notification', 'exit_intent_custom')
186 193 GROUP BY nx_id",
187 194 ARRAY_A
188 195 );
196 + // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
189 197
190 198 // Create a lookup array for entries counts
191 199 $entries_lookup = [];
192 200 foreach ($entries_counts as $entry) {
@@ -223,9 +231,17 @@
223 231 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
224 232 */
225 233 public function get_item($request) {
226 234 PostType::get_instance()->set_context( 'edit' );
227 - return PostType::get_instance()->get_post( absint( $request['id'] ) );
235 + $post = PostType::get_instance()->get_post( absint( $request['id'] ) );
236 + if ( empty( $post ) ) {
237 + return new WP_Error(
238 + 'rest_post_invalid_id',
239 + __( 'Invalid notification ID.', 'notificationx' ),
240 + array( 'status' => 404 )
241 + );
242 + }
243 + return $post;
228 244 }
229 245
230 246 /**
231 247 * Checks if a given request has access to create a post.