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/base.php +68 -11 2.12.6 → 2.12.8 View file →
@@ -88,9 +88,9 @@
88 88 *
89 89 * @var array<string>
90 90 * @since 1.8.0
91 91 */
92 - private $allowed_where_operators = [ 'LIKE', 'IN', '=', '!=', '>', '<', '>=', '<=' ];
92 + private $allowed_where_operators = [ 'LIKE', 'IN', 'NOT IN', '=', '!=', '>', '<', '>=', '<=' ];
93 93
94 94 /**
95 95 * Init class.
96 96 *
@@ -789,9 +789,14 @@
789 789 * @since 0.0.13
790 790 * @return int|false The number of rows deleted, or false on error.
791 791 */
792 792 public function use_delete( $where, $where_format = null ) {
793 - return $this->wpdb->delete( $this->get_tablename(), $where, $where_format );
793 + $result = $this->wpdb->delete( $this->get_tablename(), $where, $where_format );
794 +
795 + // Reset cache so subsequent queries in the same request exclude the deleted row.
796 + $this->cache_reset();
797 +
798 + return $result;
794 799 }
795 800
796 801 /**
797 802 * Retrieve results from the database based on the given WHERE clauses and selected columns.
@@ -828,10 +833,12 @@
828 833 // Add a semicolon at the end of the query.
829 834 $query = rtrim( trim( $query ), ';' ) . ';';
830 835
831 836 $cached_results = $this->cache_get( $query );
832 - if ( $cached_results ) {
833 - // Return the cached data if exists.
837 + if ( null !== $cached_results ) {
838 + // Return the cached data if exists. Tested against null rather than
839 + // truthiness: an empty result set is a real answer, and re-running the
840 + // query for it means every no-match lookup runs once per caller.
834 841 return Helper::get_array_value( $cached_results );
835 842 }
836 843
837 844 // phpcs:ignore
@@ -917,10 +924,12 @@
917 924 // Add a semicolon at the end of the query.
918 925 $query = rtrim( trim( $query ), ';' ) . ';';
919 926
920 927 $cached_results = $this->cache_get( $query );
921 - if ( $cached_results ) {
922 - // Return the cached data if exists.
928 + if ( null !== $cached_results ) {
929 + // Return the cached data if exists. Tested against null rather than
930 + // truthiness: a count of zero is a real answer, and the editor exclusion
931 + // makes zero the common case rather than the exception.
923 932 return Helper::get_integer_value( $cached_results );
924 933 }
925 934
926 935 // phpcs:ignore
@@ -1051,8 +1060,9 @@
1051 1060 * @type string $RELATION Optional. The logical relation ('AND' or 'OR').
1052 1061 * }
1053 1062 * }
1054 1063 *
1064 + * @since 2.12.7 -- Added support for "NOT IN" compare.
1055 1065 * @since 1.1.1 -- Added support for "IN" compare.
1056 1066 * @since 0.0.13
1057 1067 * @return string The prepared SQL WHERE clause with placeholders, or an empty string if no clauses were provided.
1058 1068 */
@@ -1077,10 +1087,19 @@
1077 1087 if ( is_int( $key ) ) {
1078 1088 $clause_parts = [];
1079 1089 foreach ( $value as $_key => $_value ) {
1080 1090 if ( is_int( $_key ) ) {
1091 + // Normalised before the allowlist test. Payments'
1092 + // builder upper-cases and trims, this one compared
1093 + // strictly -- so a caller writing 'not in' was honoured
1094 + // by one and silently dropped by the other. A dropped
1095 + // condition used to be harmless; now that NOT IN is the
1096 + // exclusion primitive, dropping it disables the
1097 + // exclusion without a word.
1098 + $compare = strtoupper( trim( Helper::get_string_value( $_value['compare'] ) ) );
1099 +
1081 1100 // Check if the operator is allowed.
1082 - if ( ! in_array( $_value['compare'], $this->allowed_where_operators, true ) ) {
1101 + if ( ! in_array( $compare, $this->allowed_where_operators, true ) ) {
1083 1102 continue;
1084 1103 }
1085 1104
1086 1105 // Skip if key is not in schema.
@@ -1087,9 +1106,9 @@
1087 1106 if ( ! isset( $schema[ $_value['key'] ] ) ) {
1088 1107 continue;
1089 1108 }
1090 1109
1091 - switch ( $_value['compare'] ) {
1110 + switch ( $compare ) {
1092 1111 case 'LIKE':
1093 1112 // Single quotes to match WP core. Under a MySQL session with
1094 1113 // ANSI_QUOTES set (not in WP's incompatible_modes list, which
1095 1114 // only names the compound ANSI mode) a double-quoted pattern
@@ -1094,21 +1113,51 @@
1094 1113 // ANSI_QUOTES set (not in WP's incompatible_modes list, which
1095 1114 // only names the compound ANSI mode) a double-quoted pattern
1096 1115 // parses as an identifier and the query hard-fails, taking out
1097 1116 // both the listing and its COUNT(*).
1098 - $clause_parts[] = $_value['key'] . ' ' . $_value['compare'] . " '%%" . $this->get_format_by_datatype( Helper::get_string_value( $schema[ $_value['key'] ]['type'] ) ) . "%%'";
1117 + $clause_parts[] = $_value['key'] . ' ' . $compare . " '%%" . $this->get_format_by_datatype( Helper::get_string_value( $schema[ $_value['key'] ]['type'] ) ) . "%%'";
1099 1118 $values[] = $_value['value'];
1100 1119 break;
1101 1120
1102 1121 case 'IN':
1122 + case 'NOT IN':
1123 + // A scalar is a caller bug, not an empty set, and it must
1124 + // surface. 'NOT IN' with value 5 -- a plausible typo for
1125 + // [ 5 ] -- would otherwise drop the condition and exclude
1126 + // nobody, with no error and a green test suite, while the
1127 + // same typo on 'IN' fails closed. On a primitive whose only
1128 + // job is scoping data, that asymmetry is a hazard.
1129 + if ( ! is_array( $_value['value'] ) ) {
1130 + _doing_it_wrong(
1131 + __METHOD__,
1132 + esc_html( "{$compare} requires an array value, received " . gettype( $_value['value'] ) . '.' ),
1133 + '2.12.7'
1134 + );
1135 + break;
1136 + }
1137 +
1138 + // An empty list cannot be interpolated: "col IN ()" is a syntax
1139 + // error that fails the whole query, listing and COUNT alike.
1140 + // An empty IN matches nothing, so '1 = 0' says that in any
1141 + // relation. An empty NOT IN excludes nothing, but a literal
1142 + // would be '1 = 1', and that makes an enclosing OR group
1143 + // unconditionally true. Dropping the condition means the same
1144 + // thing under AND and stays fail-closed under OR.
1145 + if ( [] === $_value['value'] ) {
1146 + if ( 'IN' === $compare ) {
1147 + $clause_parts[] = '1 = 0';
1148 + }
1149 + break;
1150 + }
1151 +
1103 1152 // Based on the number of values and datatype, it will create WHERE clause for $wpdb::prepare method. Eg: for ID with three values column: ID IN (%d, %d, %d).
1104 1153 $datatype = $this->get_format_by_datatype( Helper::get_string_value( $schema[ $_value['key'] ]['type'] ) );
1105 - $clause_parts[] = $_value['key'] . ' ' . $_value['compare'] . ' (' . implode( ', ', array_fill( 0, count( $_value['value'] ), $datatype ) ) . ')';
1154 + $clause_parts[] = $_value['key'] . ' ' . $compare . ' (' . implode( ', ', array_fill( 0, count( $_value['value'] ), $datatype ) ) . ')';
1106 1155 $values = array_merge( $values, $_value['value'] );
1107 1156 break;
1108 1157
1109 1158 default:
1110 - $clause_parts[] = $_value['key'] . ' ' . $_value['compare'] . ' ' . $this->get_format_by_datatype( Helper::get_string_value( $schema[ $_value['key'] ]['type'] ) );
1159 + $clause_parts[] = $_value['key'] . ' ' . $compare . ' ' . $this->get_format_by_datatype( Helper::get_string_value( $schema[ $_value['key'] ]['type'] ) );
1111 1160 $values[] = $_value['value'];
1112 1161 break;
1113 1162 }
1114 1163 }
@@ -1133,8 +1182,16 @@
1133 1182 return '';
1134 1183 }
1135 1184
1136 1185 $where = ' WHERE ' . implode( ' AND ', $groups );
1186 +
1187 + if ( [] === $values ) {
1188 + // Every branch that builds a placeholder also pushes a value, so an
1189 + // empty list here means the only conditions were constant ones. There
1190 + // is nothing for prepare() to fill, and calling it with no placeholder
1191 + // trips _doing_it_wrong.
1192 + return $where;
1193 + }
1137 1194
1138 1195 // Prepare the query with placeholders.
1139 1196 // @phpstan-ignore-next-line -- We are already assigning non-literal string above using "get_format_by_datatype" methods.
1140 1197 return $wpdb->prepare( $where, ...$values ); // phpcs:ignore -- We are returning prepared sql query here. We are already using necessary placeholders in $where variable.