PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.12.8
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.12.8
2.12.8 2.12.7 2.12.6 2.12.5 2.12.4 2.12.3 2.12.2 2.12.1 2.12.0 2.11.1 2.11.0 2.10.1 2.10.0 2.9.1 2.9.0 2.8.2 2.8.1 2.7.0 2.7.1 2.8.0 trunk 0.0.10 0.0.11 0.0.12 0.0.13 All 98 releases
← All changes | inc/database/tables/payments.php +120 -72 2.8.1 → 2.12.8 View file →
@@ -1094,43 +1094,12 @@
1094 1094
1095 1095 // Handle additional where conditions if provided.
1096 1096 if ( ! empty( $_args['where'] ) ) {
1097 1097 foreach ( $_args['where'] as $where_group ) {
1098 - if ( is_array( $where_group ) ) {
1099 - foreach ( $where_group as $condition ) {
1100 - if ( isset( $condition['key'], $condition['compare'], $condition['value'] ) ) {
1101 - // Validate column name against whitelist.
1102 - if ( ! in_array( $condition['key'], self::ALLOWED_COLUMNS, true ) ) {
1103 - continue;
1104 - }
1105 -
1106 - // Validate and normalize the comparison operator.
1107 - $operator = strtoupper( trim( $condition['compare'] ) );
1108 -
1109 - // Skip this condition if operator is not in whitelist.
1110 - if ( ! in_array( $operator, self::ALLOWED_OPERATORS, true ) ) {
1111 - continue;
1112 - }
1113 -
1114 - $column = $condition['key'];
1115 -
1116 - if ( in_array( $operator, [ 'IN', 'NOT IN' ], true ) && is_array( $condition['value'] ) ) {
1117 - $ids = array_map( 'absint', $condition['value'] );
1118 - if ( empty( $ids ) ) {
1119 - $ids = [ 0 ];
1120 - }
1121 - $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
1122 - $where_clause .= " AND {$column} {$operator} ({$placeholders})";
1123 - foreach ( $ids as $id ) {
1124 - $params[] = $id;
1125 - }
1126 - } else {
1127 - $where_clause .= " AND {$column} {$operator} %s";
1128 - $params[] = $condition['value'];
1129 - }
1130 - }
1131 - }
1098 + if ( ! is_array( $where_group ) ) {
1099 + continue;
1132 1100 }
1101 + $where_clause .= self::build_clause_for_group( $where_group, $params );
1133 1102 }
1134 1103 }
1135 1104
1136 1105 // Order by.
@@ -1156,9 +1125,9 @@
1156 1125 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query string is built dynamically above based on conditions.
1157 1126 $query = $wpdb->prepare( $query, $params );
1158 1127 }
1159 1128
1160 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- Custom table query with dynamic preparation, caching not applicable for dynamic queries.
1129 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table query with dynamic preparation; table name internal, not user input; caching not applicable for dynamic queries.
1161 1130 $results = $wpdb->get_results( $query, ARRAY_A );
1162 1131
1163 1132 return is_array( $results ) ? $results : [];
1164 1133 }
@@ -1198,45 +1167,12 @@
1198 1167
1199 1168 // Handle additional where conditions if provided.
1200 1169 if ( ! empty( $where_conditions ) ) {
1201 1170 foreach ( $where_conditions as $where_group ) {
1202 - if ( is_array( $where_group ) ) {
1203 - foreach ( $where_group as $condition ) {
1204 - if ( isset( $condition['key'], $condition['compare'], $condition['value'] ) ) {
1205 - // Validate column name against whitelist.
1206 - if ( ! in_array( $condition['key'], self::ALLOWED_COLUMNS, true ) ) {
1207 - continue;
1208 - }
1209 -
1210 - // Validate and normalize the comparison operator.
1211 - $operator = strtoupper( trim( $condition['compare'] ) );
1212 -
1213 - // Skip this condition if operator is not in whitelist.
1214 - if ( ! in_array( $operator, self::ALLOWED_OPERATORS, true ) ) {
1215 - continue;
1216 - }
1217 -
1218 - $column = $condition['key'];
1219 -
1220 - // Special handling for IN/NOT IN with arrays.
1221 - if ( in_array( $operator, [ 'IN', 'NOT IN' ], true ) && is_array( $condition['value'] ) ) {
1222 - $ids = array_map( 'absint', $condition['value'] );
1223 - // Prevent empty IN ().
1224 - if ( empty( $ids ) ) {
1225 - $ids = [ 0 ];
1226 - }
1227 - $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
1228 - $where_clause .= " AND {$column} {$operator} ({$placeholders})";
1229 - foreach ( $ids as $id ) {
1230 - $params[] = $id;
1231 - }
1232 - } else {
1233 - $where_clause .= " AND {$column} {$operator} %s";
1234 - $params[] = $condition['value'];
1235 - }
1236 - }
1237 - }
1171 + if ( ! is_array( $where_group ) ) {
1172 + continue;
1238 1173 }
1174 + $where_clause .= self::build_clause_for_group( $where_group, $params );
1239 1175 }
1240 1176 }
1241 1177
1242 1178 // Build and execute query.
@@ -1246,9 +1182,9 @@
1246 1182 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query string is built dynamically above based on conditions.
1247 1183 $query = $wpdb->prepare( $query, $params );
1248 1184 }
1249 1185
1250 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- Custom table query with dynamic preparation, caching not applicable for count operations.
1186 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table query with dynamic preparation; table name internal, not user input; caching not applicable for count operations.
1251 1187 $result = $wpdb->get_var( $query );
1252 1188
1253 1189 return absint( $result );
1254 1190 }
@@ -1291,6 +1227,118 @@
1291 1227 return false;
1292 1228 }
1293 1229
1294 1230 return 'payment' === ( $payment['type'] ?? '' ) && ! empty( $payment['subscription_id'] );
1231 + }
1232 +
1233 + /**
1234 + * Build the SQL fragment for a single where group, handling both flat
1235 + * AND-only groups and explicit RELATION (OR/AND) groups.
1236 + *
1237 + * Flat group example:
1238 + * [ [ 'key' => 'status', 'compare' => '=', 'value' => 'paid' ] ]
1239 + * → " AND status = %s"
1240 + *
1241 + * RELATION group example:
1242 + * [
1243 + * 'RELATION' => 'OR',
1244 + * [ 'key' => 'status', 'compare' => '=', 'value' => 'canceled' ],
1245 + * [ 'key' => 'subscription_status', 'compare' => '=', 'value' => 'canceled' ],
1246 + * ]
1247 + * → " AND (status = %s OR subscription_status = %s)"
1248 + *
1249 + * Conditions with disallowed columns or operators are silently skipped.
1250 + * Always prefixes the returned fragment with " AND " so callers can append
1251 + * directly to a `WHERE 1=1` clause.
1252 + *
1253 + * @param array<int|string,mixed> $where_group Group of conditions, optionally with 'RELATION'.
1254 + * @param array<mixed> $params Reference to running params array; appended in place.
1255 + * @since 2.9.0
1256 + * @return string SQL fragment to append, or empty string if nothing valid.
1257 + */
1258 + private static function build_clause_for_group( array $where_group, array &$params ) {
1259 + if ( empty( $where_group ) ) {
1260 + return '';
1261 + }
1262 +
1263 + $is_relation_group = ! empty( $where_group['RELATION'] ) && is_string( $where_group['RELATION'] );
1264 + $relation = $is_relation_group && 'OR' === strtoupper( $where_group['RELATION'] )
1265 + ? 'OR'
1266 + : 'AND';
1267 +
1268 + $sub_clauses = [];
1269 +
1270 + foreach ( $where_group as $key => $condition ) {
1271 + if ( 'RELATION' === $key || ! is_array( $condition ) ) {
1272 + continue;
1273 + }
1274 + if ( ! isset( $condition['key'], $condition['compare'], $condition['value'] ) ) {
1275 + continue;
1276 + }
1277 + if ( ! in_array( $condition['key'], self::ALLOWED_COLUMNS, true ) ) {
1278 + continue;
1279 + }
1280 +
1281 + $operator = strtoupper( trim( (string) $condition['compare'] ) );
1282 + if ( ! in_array( $operator, self::ALLOWED_OPERATORS, true ) ) {
1283 + continue;
1284 + }
1285 +
1286 + $column = $condition['key'];
1287 +
1288 + if ( in_array( $operator, [ 'IN', 'NOT IN' ], true ) ) {
1289 + if ( ! is_array( $condition['value'] ) ) {
1290 + // A scalar used to fall through to the else branch and emit
1291 + // `col IN %s`, which is a syntax error that fails the whole
1292 + // query. Base::prepare_where_clauses() reports this and drops
1293 + // the condition; do the same rather than leave the two builders
1294 + // disagreeing on malformed input.
1295 + _doing_it_wrong(
1296 + __METHOD__,
1297 + esc_html( "{$operator} requires an array value, received " . gettype( $condition['value'] ) . '.' ),
1298 + '2.12.7'
1299 + );
1300 + continue;
1301 + }
1302 +
1303 + $ids = array_map( 'absint', $condition['value'] );
1304 + if ( [] === $ids ) {
1305 + // Same empty-list handling as Base::prepare_where_clauses(), so
1306 + // the two builders cannot disagree. An empty IN matches nothing.
1307 + // An empty NOT IN excludes nothing, and is dropped rather than
1308 + // written as a literal, because a literal true would make an
1309 + // enclosing OR group match every row.
1310 + //
1311 + // This builder serves the manage_options-gated admin payments
1312 + // listing only, through get_all_main_payments() and
1313 + // get_total_main_payments_by_status(). The payment-history
1314 + // shortcode's customer filter is compiled by
1315 + // Base::prepare_where_clauses() via Payments::get_all() -- that
1316 + // group is where an OR fail-open would actually leak, and it is
1317 + // covered by the change in base.php.
1318 + if ( 'IN' === $operator ) {
1319 + $sub_clauses[] = '1 = 0';
1320 + }
1321 + continue;
1322 + }
1323 + $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
1324 + $sub_clauses[] = "{$column} {$operator} ({$placeholders})";
1325 + foreach ( $ids as $id ) {
1326 + $params[] = $id;
1327 + }
1328 + } else {
1329 + $sub_clauses[] = "{$column} {$operator} %s";
1330 + $params[] = $condition['value'];
1331 + }
1332 + }
1333 +
1334 + if ( empty( $sub_clauses ) ) {
1335 + return '';
1336 + }
1337 +
1338 + if ( $is_relation_group ) {
1339 + return ' AND (' . implode( " {$relation} ", $sub_clauses ) . ')';
1340 + }
1341 +
1342 + return ' AND ' . implode( ' AND ', $sub_clauses );
1295 1343 }
1296 1344 }