PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 3.3.1
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v3.3.1
3.3.1 V-3.3.0 3.2.2 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 V3.0.3 V3.0.2 -3.0.1 V_3.0.0 1.1.1 1.1.8 1.2 1.3 1.4 1.4.18 1.5.2 1.9 2.0 2.10.0 2.10.1 All 138 releases
← All changes | includes/Core/Database/Model.php +605 -417 1.1.13.3.1 View file →
@@ -9,451 +9,639 @@
9 9 /**
10 10 * Undocumented class
11 11 */
12 12
13 +use BitCode\BitForm\Core\Util\Log;
13 14 use WP_Error;
14 15
15 16 class Model
16 17 {
17 - protected static $table;
18 - protected static $primary_key;
19 - protected $app_db;
20 - protected $table_name;
21 - protected $db_response;
22 - /**
23 - * Undocumented function
24 - */
25 - public function __construct()
26 - {
27 - global $wpdb;
28 - $this->app_db = $wpdb;
29 - $this->table_name = $wpdb->prefix . static::$table;
18 + protected static $table;
19 + protected static $primary_key;
20 + protected $app_db;
21 + protected $table_name;
22 + protected $db_response;
23 +
24 + /**
25 + * Undocumented function
26 + */
27 + public function __construct()
28 + {
29 + global $wpdb;
30 + $this->app_db = $wpdb;
31 + $this->table_name = $wpdb->prefix . static::$table;
32 + }
33 +
34 + /**
35 + * Insert a row
36 + *
37 + * @return mixed insert id on success, WP_Error on failure
38 + */
39 + public function insert($data = [])
40 + {
41 + if (is_null($data)) {
42 + return new WP_Error('empty_data', 'Form data is empty');
30 43 }
31 - /**
32 - * Undocumented function
33 - *
34 - * @return void
35 - */
36 - public function insert($data = array())
37 - {
38 - if (is_null($data)) {
39 - return new WP_Error('empty_data', __('Form data is empty', 'bitform'));
40 - }
41 - $result = $this->app_db->insert(
42 - $this->table_name,
43 - $data
44 - );
45 - return $this->getResult($result);
44 + $result = $this->app_db->insert(
45 + $this->table_name,
46 + $data
47 + );
48 + return $this->getResult($result);
49 + }
50 +
51 + /**
52 + * Undocumented function
53 + *
54 + * @param string|string[] $item
55 + * @param array $condition
56 + *
57 + * @return mixed
58 + */
59 + public function get($item = '*', $condition = [], $limit = null, $offset = null, $order_by = null, $order_follow = null)
60 + {
61 + static $tableExistsCache = [];
62 + if (!isset($tableExistsCache[$this->table_name])) {
63 + $tableExistsCache[$this->table_name] = !is_null(
64 + $this->app_db->get_var(
65 + $this->app_db->prepare('SHOW TABLES LIKE %s', $this->table_name)
66 + )
67 + );
46 68 }
47 - /**
48 - * Undocumented function
49 - *
50 - * @param string $item
51 - * @param array $condition
52 - *
53 - * @return void
54 - */
55 - public function get($item = "*", $condition = [], $limit = null, $offset = null, $order_by = null, $order_follow = null)
56 - {
57 - if (\is_array($item)) {
58 - $column_to_select = implode(",", $item);
59 - } else {
60 - $column_to_select = $item;
61 - }
62 - $checkCondition = $this->checkCondition($condition);
63 - if (is_wp_error($checkCondition)) {
64 - return $checkCondition;
65 - }
66 - $order = null;
67 - if (!\is_null($order_by)) {
68 - $order_follow = \is_null($order_follow) ? 'ASC' : $order_follow;
69 - $order .= " ORDER BY $order_by $order_follow";
70 - }
71 - $paginate = null;
72 - if (!\is_null($limit)) {
73 - $limit = \intval($limit);
74 - $paginate .= " LIMIT $limit ";
75 - }
76 - if (!\is_null($offset)) {
77 - $offset = \intval($offset);
78 - $paginate .= " OFFSET $offset ";
79 - }
80 - if (empty($condition)) {
81 - $sql = "SELECT $column_to_select FROM `$this->table_name` $order $paginate";
82 - $all_values = null;
83 - } else {
84 - $formatted_conditions = $this->getFormatedCondition($condition);
85 - if ($formatted_conditions) {
86 - $condition_to_check = $formatted_conditions['conditions'];
87 - $all_values = $formatted_conditions['values'];
88 - } else {
89 - $condition_to_check = null;
90 - $all_values = null;
91 - }
92 - $sql = "SELECT $column_to_select FROM `$this->table_name`"
93 - . $condition_to_check . $order . $paginate;
94 - }
95 - // var_dump($sql);
96 - return $this->execute($sql, $all_values)->getResult();
69 + if (!$tableExistsCache[$this->table_name]) {
70 + return [];
97 71 }
98 - /**
99 - * Undocumented function
100 - *
101 - * @param string $item
102 - * @param array $condition
103 - *
104 - * @return void
105 - */
106 - public function count($condition = null)
107 - {
108 - $checkCondition = $this->checkCondition($condition);
109 - if (is_wp_error($checkCondition)) {
110 - return $checkCondition;
111 - }
112 - if (empty($condition)) {
113 - $result = $this->app_db->query(
114 - "SELECT COUNT(*) FROM `$this->table_name`"
115 - );
116 - } else {
117 - $formatted_conditions = $this->getFormatedCondition($condition);
118 - if ($formatted_conditions) {
119 - $condition_to_check = $formatted_conditions['conditions'];
120 - $all_values = $formatted_conditions['values'];
121 - } else {
122 - $condition_to_check = null;
123 - $all_values = null;
124 - }
125 - $result = $this->app_db->query(
126 - $this->app_db->prepare(
127 - "SELECT COUNT(*) as count FROM `$this->table_name`"
128 - . $condition_to_check,
129 - $all_values
130 - )
131 - );
132 - }
133 - if (!$result) {
134 - if ($this->app_db->last_error) {
135 - return new WP_Error('db_error', $this->app_db->last_error);
136 - }
137 - return new WP_Error('db_error', __("Result is empty", "bitform"));
138 - } else {
139 - return $this->app_db->last_result;
140 - }
72 + if (\is_array($item)) {
73 + $column_to_select = implode(',', $item);
74 + } else {
75 + $column_to_select = $item;
141 76 }
142 - /**
143 - * Undocumented function
144 - *
145 - * @param array $data_to_update
146 - * @param array $condition
147 - *
148 - * @return void
149 - */
150 - public function update(array $data, array $condition)
151 - {
152 - if (
153 - !\is_null($data)
154 - && \is_array($data)
155 - && array_keys($data) !== range(0, count($data) - 1)
156 - ) {
157 - $data_to_update = $data;
158 - } else {
159 - return new WP_Error(
160 - 'update_error',
161 - __('Nothing to update', 'bitform')
162 - );
163 - }
164 - $update_condition = (!\is_null($condition) &&
165 - array_keys($condition) !== range(0, count($condition) - 1)) ? $condition : null;
166 - $result = $this->app_db->update(
167 - $this->table_name,
168 - $data_to_update,
169 - $update_condition
170 - );
171 - return $this->getResult($result);
77 + $checkCondition = $this->checkCondition($condition);
78 + if (is_wp_error($checkCondition)) {
79 + return $checkCondition;
172 80 }
173 - /**
174 - * Undocumented function
175 - *
176 - * @param array $data_to_update
177 - * @param array $condition
178 - *
179 - * @return void
180 - */
181 - public function bulkUpdate(array $data = null, array $condition = null)
182 - {
183 - if (
184 - !\is_null($data)
185 - && \is_array($data)
186 - && array_keys($data) !== range(0, count($data) - 1)
187 - ) {
188 - $data_to_update = $data;
189 - } else {
190 - return new WP_Error(
191 - 'update_error',
192 - __('Nothing to update', 'bitform')
193 - );
194 - }
81 + $order = null;
82 + if (!\is_null($order_by)) {
83 + $order_follow = \is_null($order_follow) ? 'ASC' : $order_follow;
84 + $direction = \is_string($order_follow) ? strtoupper(trim($order_follow)) : '';
85 + if ($this->isSafeConditionIdentifier($order_by) && \in_array($direction, ['ASC', 'DESC'], true)) {
86 + $order .= ' ORDER BY ' . $this->quoteIdentifier($order_by) . ' ' . $direction;
87 + } else {
88 + Log::debug_log([
89 + 'message' => 'Model::get() ignored an unsafe ORDER BY',
90 + 'table' => $this->table_name,
91 + 'order_by' => $order_by,
92 + 'order_follow' => $order_follow,
93 + ]);
94 + }
95 + }
96 + $paginate = null;
97 + if (!\is_null($limit)) {
98 + $limit = \intval($limit);
99 + $paginate .= " LIMIT $limit ";
100 + }
101 + if (!\is_null($offset)) {
102 + $offset = \intval($offset);
103 + $paginate .= " OFFSET $offset ";
104 + }
105 + if (empty($condition)) {
106 + $sql = "SELECT $column_to_select FROM `$this->table_name` $order $paginate";
107 + $all_values = null;
108 + } else {
109 + $formatted_conditions = $this->getFormatedCondition($condition);
110 + if ($formatted_conditions) {
111 + $condition_to_check = $formatted_conditions['conditions'];
112 + $all_values = $formatted_conditions['values'];
113 + } else {
114 + $condition_to_check = null;
115 + $all_values = null;
116 + }
117 + $sql = "SELECT $column_to_select FROM `$this->table_name`"
118 + . $condition_to_check . $order . $paginate;
119 + }
120 + return $this->execute($sql, $all_values)->getResult();
121 + }
195 122
196 - $update_fields = '';
197 - $all_values = array();
198 - $index_checker = 0;
199 - $data_count = count($data_to_update) - 1;
200 - foreach ($data_to_update as $field_name => $field_value) {
201 - $update_fields .= $field_name . ' = ' . $this->getFieldFormat($field_value);
202 - if ($index_checker < $data_count) {
203 - $update_fields .= ',';
204 - }
205 - $index_checker = $index_checker + 1;
206 - $all_values[] = $field_value;
207 - }
208 - $update_condition = (!\is_null($condition) &&
209 - array_keys($condition) !== range(0, count($condition) - 1)) ? $condition : null;
210 - $formatted_conditions = $this->getFormatedCondition($update_condition);
211 - if ($formatted_conditions) {
212 - $condition_to_check = $formatted_conditions['conditions'];
213 - $all_values = array_merge($all_values, $formatted_conditions['values']);
214 - } else {
215 - $condition_to_check = null;
216 - }
217 - $result = $this->app_db->query(
218 - $this->app_db->prepare(
219 - "UPDATE $this->table_name SET $update_fields $condition_to_check",
220 - $all_values
221 - )
222 - );
223 - return $this->getResult($result);
123 + /**
124 + * Undocumented function
125 + *
126 + * @param string $item
127 + * @param array $condition
128 + *
129 + * @return void
130 + */
131 + public function count($condition = null)
132 + {
133 + $checkCondition = $this->checkCondition($condition);
134 + if (is_wp_error($checkCondition)) {
135 + return $checkCondition;
224 136 }
225 - /**
226 - * Duplicate's row
227 - *
228 - * @param array $data_to_update
229 - * @param array $condition
230 - *
231 - * @return void
232 - */
233 - public function duplicate(array $columns, array $duplicate, array $condition)
234 - {
235 - if (!(!\is_null($columns)
236 - && \is_array($columns)
237 - && array_keys($columns) === range(0, count($columns) - 1)
238 - && !\is_null($duplicate)
239 - && \is_array($duplicate)
240 - && array_keys($duplicate) === range(0, count($duplicate) - 1))) {
241 - return new WP_Error(
242 - 'duplicate_error',
243 - __('Nothing to duplicate', 'bitform')
244 - );
245 - }
137 + if (empty($condition)) {
138 + $result = $this->app_db->query(
139 + "SELECT COUNT(*) FROM `$this->table_name`"
140 + );
141 + } else {
142 + $formatted_conditions = $this->getFormatedCondition($condition);
143 + if ($formatted_conditions) {
144 + $condition_to_check = $formatted_conditions['conditions'];
145 + $all_values = $formatted_conditions['values'];
146 + } else {
147 + $condition_to_check = null;
148 + $all_values = null;
149 + }
150 + $result = $this->app_db->query(
151 + $this->app_db->prepare(
152 + "SELECT COUNT(*) as count FROM `{$this->table_name}`"
153 + . $condition_to_check,
154 + $all_values
155 + )
156 + );
157 + }
158 + if (!$result) {
159 + if ($this->app_db->last_error) {
160 + return new WP_Error('db_error', $this->app_db->last_error);
161 + }
162 + return new WP_Error('db_error', 'Result is empty');
163 + } else {
164 + return $this->app_db->last_result;
165 + }
166 + }
246 167
247 - $dupCol = '';
248 - $insCol = \implode(',', $columns);
249 - $all_values = array();
250 - $data_count = count($duplicate) - 1;
251 - foreach ($duplicate as $dupKey => $dupColName) {
252 - if (in_array($dupColName, $columns)) {
253 - $dupCol .= $dupColName;
254 - } else {
255 - $dupCol .= $this->getFieldFormat($dupColName);
256 - $all_values[] = $dupColName;
257 - }
258 - if ($dupKey < $data_count) {
259 - $dupCol .= ',';
260 - }
261 - }
262 - $condition_to_check = null;
263 - $update_condition = (!\is_null($condition) &&
264 - array_keys($condition) !== range(0, count($condition) - 1)) ? $condition : null;
265 - $formatted_conditions = $this->getFormatedCondition($update_condition);
266 - if ($formatted_conditions) {
267 - $condition_to_check = $formatted_conditions['conditions'];
268 - $all_values = array_merge($all_values, $formatted_conditions['values']);
269 - }
270 - $query = "INSERT INTO $this->table_name ($insCol)
271 - SELECT $dupCol FROM $this->table_name $condition_to_check";
272 - $this->execute($query, $all_values);
273 - return $this->getResult($result);
168 + /**
169 + * Undocumented function
170 + *
171 + * @param array $data_to_update
172 + * @param array $condition
173 + *
174 + * @return mixed affected-row count on success, WP_Error on failure or when no row matched
175 + */
176 + public function update(array $data, array $condition)
177 + {
178 + if (
179 + !\is_null($data)
180 + && \is_array($data)
181 + && array_keys($data) !== range(0, count($data) - 1)
182 + ) {
183 + $data_to_update = $data;
184 + } else {
185 + return new WP_Error(
186 + 'update_error',
187 + 'Nothing to update'
188 + );
274 189 }
275 - public function trash(array $condition = null)
276 - {
277 - if (
278 - !\is_null($condition)
279 - && \is_array($condition)
280 - && array_keys($condition) !== range(0, count($condition) - 1)
281 - ) {
282 - $delete_condition = $condition;
283 - } else {
284 - return new WP_Error(
285 - 'deletion_error',
286 - __('At least 1 condition needed', 'bitform')
287 - );
288 - }
289 - $update_condition = (!\is_null($condition) &&
290 - array_keys($condition) !== range(0, count($condition) - 1)) ? $condition : null;
291 - $result = $this->app_db->update(
292 - $this->table_name,
293 - $data_to_update,
294 - $update_condition
295 - );
296 - return $this->getResult($result);
190 + $update_condition = (!\is_null($condition) &&
191 + array_keys($condition) !== range(0, count($condition) - 1)) ? $condition : null;
192 + $result = $this->app_db->update(
193 + $this->table_name,
194 + $data_to_update,
195 + $update_condition
196 + );
197 + return $this->getResult($result);
198 + }
199 +
200 + /**
201 + * Undocumented function
202 + *
203 + * @param array $data_to_update
204 + * @param array $condition
205 + *
206 + * @return void
207 + */
208 + public function bulkUpdate(array $data = null, array $condition = null)
209 + {
210 + if (
211 + !\is_null($data)
212 + && \is_array($data)
213 + && array_keys($data) !== range(0, count($data) - 1)
214 + ) {
215 + $data_to_update = $data;
216 + } else {
217 + return new WP_Error(
218 + 'update_error',
219 + 'Nothing to update'
220 + );
297 221 }
298 - public function delete(array $condition = null)
299 - {
300 - if (
301 - !\is_null($condition)
302 - && \is_array($condition)
303 - && array_keys($condition) !== range(0, count($condition) - 1)
304 - ) {
305 - $delete_condition = $condition;
306 - } else {
307 - return new WP_Error(
308 - 'deletion_error',
309 - __('At least 1 condition needed', 'bitform')
310 - );
311 - }
312 - $result = $this->app_db->delete(
313 - $this->table_name,
314 - $delete_condition
315 - );
316 - return $this->getResult($result);
222 +
223 + $update_fields = '';
224 + $all_values = [];
225 + $index_checker = 0;
226 + $data_count = count($data_to_update) - 1;
227 + foreach ($data_to_update as $field_name => $field_value) {
228 + $update_fields .= $field_name . ' = ' . $this->getFieldFormat($field_value);
229 + if ($index_checker < $data_count) {
230 + $update_fields .= ',';
231 + }
232 + $index_checker = $index_checker + 1;
233 + $all_values[] = $field_value;
317 234 }
318 - public function bulkDelete(array $condition = null)
319 - {
320 - if (
321 - !\is_null($condition)
322 - && \is_array($condition)
323 - && array_keys($condition) !== range(0, count($condition) - 1)
324 - ) {
325 - $delete_condition = $condition;
326 - } else {
327 - return new WP_Error(
328 - 'deletion_error',
329 - __('At least 1 condition needed', 'bitform')
330 - );
331 - }
332 - $formatted_conditions = $this->getFormatedCondition($delete_condition);
333 - if ($formatted_conditions) {
334 - $condition_to_check = $formatted_conditions['conditions'];
335 - $all_values = $formatted_conditions['values'];
336 - } else {
337 - $condition_to_check = null;
338 - return new WP_Error(
339 - 'deletion_error',
340 - __('At least 1 condition needed', 'bitform')
341 - );
342 - }
343 - $result = $this->app_db->query(
344 - $this->app_db->prepare(
345 - "DELETE FROM $this->table_name $condition_to_check",
346 - $all_values
347 - )
348 - );
349 - return $this->getResult($result);
235 + $update_condition = (!\is_null($condition) &&
236 + array_keys($condition) !== range(0, count($condition) - 1)) ? $condition : null;
237 + $formatted_conditions = $this->getFormatedCondition($update_condition);
238 + if ($formatted_conditions) {
239 + $condition_to_check = $formatted_conditions['conditions'];
240 + $all_values = array_merge($all_values, $formatted_conditions['values']);
241 + } else {
242 + $condition_to_check = null;
350 243 }
244 + $result = $this->app_db->query(
245 + $this->app_db->prepare(
246 + "UPDATE `{$this->table_name}` SET $update_fields $condition_to_check",
247 + $all_values
248 + )
249 + );
250 + return $this->getResult($result);
251 + }
351 252
352 - protected function getFieldFormat($value)
353 - {
354 - return (gettype($value) == 'integer') ?
355 - '%d' : ((gettype($value) == 'double') ? '%f' : '%s');
253 + /**
254 + * Duplicate's row
255 + *
256 + * @param array $data_to_update
257 + * @param array $condition
258 + *
259 + * @return void
260 + */
261 + public function duplicate(array $columns, array $duplicate, array $condition)
262 + {
263 + if (!(!\is_null($columns)
264 + && \is_array($columns)
265 + && array_keys($columns) === range(0, count($columns) - 1)
266 + && !\is_null($duplicate)
267 + && \is_array($duplicate)
268 + && array_keys($duplicate) === range(0, count($duplicate) - 1))) {
269 + return new WP_Error(
270 + 'duplicate_error',
271 + 'Nothing to duplicate'
272 + );
356 273 }
357 274
358 - protected function getFormatedCondition($condition, $check_operator = null, $join_operator = ' AND ')
359 - {
360 - if (\is_null($condition)) {
361 - return false;
362 - }
363 - $no_condition = count($condition);
364 - $index_checker = 0;
365 - $condition_to_check = ' WHERE ';
366 - $all_values = array();
367 - foreach ($condition as $key => $value) {
368 - $value_type = '';
369 - if (is_array($value)) {
370 - if (isset($value['operator'])) {
371 - $set_check_operator = $value['operator'];
372 - $value_type .= $this->getFieldFormat($value['value']);
373 - $all_values[] = $value['value'];
374 - } else {
375 - $set_check_operator = \is_null($check_operator) ? 'in' : $check_operator;
376 - $value_type .= ' ( ';
377 - $value_index_checker = 0;
378 - $value_count = count($value) - 1;
379 - foreach ($value as $condKey => $condValue) {
380 - $value_type .= $this->getFieldFormat($condValue);
381 - $all_values[] = $condValue;
382 - if ($value_index_checker < $value_count) {
383 - $value_type .= ', ';
384 - }
385 - $value_index_checker = $value_index_checker + 1;
386 - }
387 - $value_type .= ' )';
388 - }
389 - } else {
390 - $set_check_operator = \is_null($check_operator) ? '=' : $check_operator;
391 - $value_type .= $this->getFieldFormat($value);
392 - $all_values[] = $value;
275 + $dupCol = '';
276 + $insCol = \implode(',', $columns);
277 + $all_values = [];
278 + $data_count = count($duplicate) - 1;
279 + foreach ($duplicate as $dupKey => $dupColName) {
280 + if (in_array($dupColName, $columns)) {
281 + $dupCol .= $dupColName;
282 + } else {
283 + $dupCol .= $this->getFieldFormat($dupColName);
284 + $all_values[] = $dupColName;
285 + }
286 + if ($dupKey < $data_count) {
287 + $dupCol .= ',';
288 + }
289 + }
290 + $condition_to_check = null;
291 + $update_condition = (!\is_null($condition) &&
292 + array_keys($condition) !== range(0, count($condition) - 1)) ? $condition : null;
293 + $formatted_conditions = $this->getFormatedCondition($update_condition);
294 + if ($formatted_conditions) {
295 + $condition_to_check = $formatted_conditions['conditions'];
296 + $all_values = array_merge($all_values, $formatted_conditions['values']);
297 + }
298 + $query = "INSERT INTO `{$this->table_name}` ($insCol)
299 + SELECT $dupCol FROM `{$this->table_name}` $condition_to_check";
300 + $this->execute($query, $all_values);
301 + return $this->getResult();
302 + }
303 +
304 + public function trash(array $condition = null)
305 + {
306 + if (
307 + !\is_null($condition)
308 + && \is_array($condition)
309 + && array_keys($condition) !== range(0, count($condition) - 1)
310 + ) {
311 + $delete_condition = $condition;
312 + } else {
313 + return new WP_Error(
314 + 'deletion_error',
315 + 'At least 1 condition needed'
316 + );
317 + }
318 + $update_condition = (!\is_null($condition) &&
319 + array_keys($condition) !== range(0, count($condition) - 1)) ? $condition : null;
320 + $result = $this->app_db->update(
321 + $this->table_name,
322 + $data_to_update,
323 + $update_condition
324 + );
325 + return $this->getResult($result);
326 + }
327 +
328 + public function delete(array $condition = null)
329 + {
330 + if (
331 + !\is_null($condition)
332 + && \is_array($condition)
333 + && array_keys($condition) !== range(0, count($condition) - 1)
334 + ) {
335 + $delete_condition = $condition;
336 + } else {
337 + return new WP_Error(
338 + 'deletion_error',
339 + 'At least 1 condition needed'
340 + );
341 + }
342 + $result = $this->app_db->delete(
343 + $this->table_name,
344 + $delete_condition
345 + );
346 + return $this->getResult($result);
347 + }
348 +
349 + public function bulkDelete(array $condition = null)
350 + {
351 + if (
352 + !\is_null($condition)
353 + && \is_array($condition)
354 + && array_keys($condition) !== range(0, count($condition) - 1)
355 + ) {
356 + $delete_condition = $condition;
357 + } else {
358 + return new WP_Error(
359 + 'deletion_error',
360 + 'At least 1 condition needed'
361 + );
362 + }
363 + // $formatted_conditions = $this->getFormatedCondition($delete_condition, $check_operator);
364 + $formatted_conditions = $this->getFormatedCondition($delete_condition);
365 + if ($formatted_conditions) {
366 + $condition_to_check = $formatted_conditions['conditions'];
367 + $all_values = $formatted_conditions['values'];
368 + } else {
369 + $condition_to_check = null;
370 + return new WP_Error(
371 + 'deletion_error',
372 + 'At least 1 condition needed'
373 + );
374 + }
375 + $result = $this->app_db->query(
376 + $this->app_db->prepare(
377 + "DELETE FROM `{$this->table_name}` $condition_to_check",
378 + $all_values
379 + )
380 + );
381 + return $this->getResult($result);
382 + }
383 +
384 + protected function getFieldFormat($value)
385 + {
386 + return ('integer' === gettype($value)) ?
387 + '%d' : (('double' === gettype($value)) ? '%f' : '%s');
388 + }
389 +
390 + /**
391 + *
392 + * @param mixed $identifier
393 + *
394 + * @return bool
395 + */
396 + protected function isSafeConditionIdentifier($identifier)
397 + {
398 + if (!\is_string($identifier)) {
399 + return false;
400 + }
401 + $identifier = trim($identifier);
402 + if ('' === $identifier) {
403 + return false;
404 + }
405 +
406 + // A condition column may be table-qualified and backtick-quoted — the multi-table JOIN DELETE
407 + // in FormEntryModel::bulkDelete() *must* pass `wp_bitforms_form_entries`.`id`, because a bare
408 + // `id` is ambiguous across the two joined tables. Validate each segment on its own.
409 + $parts = explode('.', $identifier);
410 + if (count($parts) > 2) {
411 + return false;
412 + }
413 + foreach ($parts as $part) {
414 + $part = trim($part);
415 + if (\strlen($part) > 1 && '`' === $part[0] && '`' === substr($part, -1)) {
416 + $part = substr($part, 1, -1);
417 + }
418 + if (1 !== preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $part)) {
419 + return false;
420 + }
421 + }
422 +
423 + return true;
424 + }
425 +
426 + /**
427 + * Backtick-quote a validated identifier, leaving an already-quoted or table-qualified one alone.
428 + * Only ever call this on a value isSafeConditionIdentifier() has approved.
429 + *
430 + * @param string $identifier
431 + *
432 + * @return string
433 + */
434 + protected function quoteIdentifier($identifier)
435 + {
436 + $identifier = trim($identifier);
437 + if (false !== strpos($identifier, '`') || false !== strpos($identifier, '.')) {
438 + return $identifier;
439 + }
440 +
441 + return '`' . $identifier . '`';
442 + }
443 +
444 + /**
445 + * @param mixed $operator
446 + *
447 + * @return bool
448 + */
449 + protected function isSafeConditionOperator($operator)
450 + {
451 + static $allowed = ['=', '!=', '<>', '<', '>', '<=', '>=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'IS', 'IS NOT'];
452 +
453 + return \is_string($operator) && \in_array(strtoupper(trim($operator)), $allowed, true);
454 + }
455 +
456 + /**
457 + * A WHERE that matches nothing. Keeps a placeholder so the caller's
458 + * $wpdb->prepare($sql, $values) still has something to bind.
459 + *
460 + * @return array
461 + */
462 + private function impossibleCondition()
463 + {
464 + return [
465 + 'conditions' => ' WHERE 1=%d ',
466 + 'values' => [0],
467 + ];
468 + }
469 +
470 + protected function getFormatedCondition($condition, $check_operator = null, $join_operator = ' AND ')
471 + {
472 + if (\is_null($condition)) {
473 + return false;
474 + }
475 + $no_condition = count($condition);
476 + $index_checker = 0;
477 + $condition_to_check = ' WHERE ';
478 + $all_values = [];
479 + foreach ($condition as $key => $value) {
480 + if (!$this->isSafeConditionIdentifier($key)) {
481 + return $this->impossibleCondition();
482 + }
483 + $value_type = '';
484 + if (is_array($value)) {
485 + // Check for raw SQL values first
486 + if (isset($value['raw'])) {
487 + if (!\is_string($value['raw'])) {
488 + return $this->impossibleCondition();
489 + }
490 + $set_check_operator = isset($value['operator']) ? $value['operator'] : '=';
491 + $value_type = $value['raw']; // Use raw SQL directly
492 + // Don't add to $all_values since it's raw SQL
493 + } elseif (isset($value['operator'])) {
494 + // logic for operator arrays
495 + $set_check_operator = $value['operator'];
496 + $value_type .= $this->getFieldFormat($value['value']);
497 + $all_values[] = $value['value'];
498 + } else {
499 + // logic for IN conditions
500 + $set_check_operator = \is_null($check_operator) ? 'in' : $check_operator;
501 + $value_type .= ' ( ';
502 + $value_index_checker = 0;
503 + $value_count = count($value) - 1;
504 + foreach ($value as $condKey => $condValue) {
505 + $value_type .= $this->getFieldFormat($condValue);
506 + $all_values[] = $condValue;
507 + if ($value_index_checker < $value_count) {
508 + $value_type .= ', ';
393 509 }
394 - $condition_to_check = $condition_to_check . $key . " $set_check_operator " . $value_type;
395 - if ($index_checker < $no_condition - 1) {
396 - $condition_to_check = $condition_to_check . " $join_operator ";
397 - }
398 - $index_checker = $index_checker + 1;
510 + $value_index_checker = $value_index_checker + 1;
511 + }
512 + $value_type .= ' )';
399 513 }
400 - return array(
401 - 'conditions' => $condition_to_check,
402 - 'values' => $all_values
403 - );
514 + } else {
515 + // logic for simple values
516 + $set_check_operator = \is_null($check_operator) ? '=' : $check_operator;
517 + $value_type .= $this->getFieldFormat($value);
518 + $all_values[] = $value;
519 + }
520 + if (!$this->isSafeConditionOperator($set_check_operator)) {
521 + return $this->impossibleCondition();
522 + }
523 + $condition_to_check = $condition_to_check . $key . " $set_check_operator " . $value_type;
524 + if ($index_checker < $no_condition - 1) {
525 + $condition_to_check = $condition_to_check . " $join_operator ";
526 + }
527 + $index_checker = $index_checker + 1;
404 528 }
529 + return [
530 + 'conditions' => $condition_to_check,
531 + 'values' => $all_values
532 + ];
533 + }
405 534
406 - protected function checkCondition(array $condition)
407 - {
408 - if (!is_null($condition) && array_keys($condition) === range(0, count($condition) - 1)) {
409 - return new WP_Error(
410 - 'get_condition',
411 - 'Require ASSOC_ARRAY but found N_ARRAY'
412 - );
413 - }
414 - return true;
535 + /**
536 + * @param array $values values about to be bound by $wpdb->prepare()
537 + *
538 + * @return string|null the offending PHP type, or null when every value is bindable
539 + */
540 + private function findUnbindableValue(array $values)
541 + {
542 + foreach ($values as $value) {
543 + if (!is_scalar($value) && !is_null($value)) {
544 + return \gettype($value);
545 + }
415 546 }
416 547
417 - protected function execute($sql, $values = null)
418 - {
419 - if (is_null($values)) {
420 - $preparedQuery = $this->app_db->prepare($sql);
421 - } else {
422 - $preparedQuery = $this->app_db->prepare($sql, $values);
423 - }
424 - // echo " Q S " . $preparedQuery . " Q EE";
425 - if (empty($preparedQuery)) {
426 - $this->db_response = new WP_Error('null_query', __("prepared query is empty", "bitform"));
427 - } else {
428 - $this->db_response = stripos($preparedQuery, 'DELETE') !== false ? $this->app_db->query($preparedQuery)
429 - : $this->app_db->get_results($preparedQuery, OBJECT_K);
430 - }
431 - // print_r($this->app_db->last_query);
548 + return null;
549 + }
550 +
551 + protected function checkCondition(array $condition)
552 + {
553 + if (!is_null($condition) && array_keys($condition) === range(0, count($condition) - 1)) {
554 + return new WP_Error(
555 + 'get_condition',
556 + 'Require ASSOC_ARRAY but found N_ARRAY'
557 + );
558 + }
559 + return true;
560 + }
561 +
562 + protected function execute($sql, $values = null)
563 + {
564 + // Clear the previous call's outcome before running a new query, so a failure can never be
565 + // read back by whatever this instance is used for next.
566 + $this->db_response = null;
567 + if (is_null($values)) {
568 + $preparedQuery = $sql;
569 + } else {
570 + $invalid = $this->findUnbindableValue((array) $values);
571 + if (null !== $invalid) {
572 + Log::debug_log([
573 + 'message' => 'Model::execute() received an unbindable condition value',
574 + 'table' => $this->table_name,
575 + 'type' => $invalid,
576 + 'sql' => $sql,
577 + ]);
578 + $this->db_response = new WP_Error('invalid_query_value', 'Query value must be scalar, ' . $invalid . ' given');
579 +
432 580 return $this;
581 + }
582 + $preparedQuery = $this->app_db->prepare($sql, $values);
433 583 }
584 + // echo " Q S " . $preparedQuery . " Q EE";
585 + if (empty($preparedQuery)) {
586 + $this->db_response = new WP_Error('null_query', 'prepared query is empty');
587 + } else {
588 + $this->db_response = false !== stripos($preparedQuery, 'DELETE') ? $this->app_db->query($preparedQuery)
589 + : $this->app_db->get_results($preparedQuery, OBJECT_K);
590 + }
591 + // print_r($this->app_db->last_query);
592 + return $this;
593 + }
434 594
435 - protected function getResult($db_response = null)
436 - {
437 - $db_response = !empty($this->db_response) ? $this->db_response : $db_response;
438 - if (!empty($this->app_db->last_error)) {
439 - return new WP_Error('db_error', $this->app_db->last_error);
440 - }
441 - if (!$db_response) {
442 - if ($this->app_db->num_rows > 0) {
443 - $response = $this->app_db->num_rows;
444 - }
445 - if (is_wp_error($db_response)) {
446 - $response = $db_response;
447 - }
448 - $response = new WP_Error('result_empty', __("Result is empty", "bitform"));
449 - } elseif (is_array($this->app_db->last_result) && !empty($this->app_db->last_result)) {
450 - $response = $this->app_db->last_result;
451 - } elseif ($this->app_db->insert_id) {
452 - $response = $this->app_db->insert_id;
453 - } else {
454 - $response = $db_response;
455 - }
456 - $this->app_db->flush();
457 - return $response;
595 + protected function getResult($db_response = null)
596 + {
597 + // The caller's own result wins. $db_response is an instance property that only execute()
598 + // writes, and models are reused (AdminFormHandler keeps a static FormModel for the whole
599 + // request), so letting the property override an explicitly passed result made insert() and
600 + // update() report the outcome of some earlier, unrelated query on the same object.
601 + // Without this fallback, execute()->getResult() (which passes no argument) never sees the
602 + // query it just ran and every read returns 'result_empty'.
603 + if (null === $db_response) {
604 + $db_response = $this->db_response;
458 605 }
606 +
607 + if (is_wp_error($db_response)) {
608 + return $db_response;
609 + }
610 + if (!empty($this->app_db->last_error)) {
611 + return new WP_Error('db_error', $this->app_db->last_error);
612 + }
613 + if (!$db_response) {
614 + if ($this->app_db->num_rows > 0) {
615 + $response = $this->app_db->num_rows;
616 + }
617 + if (is_wp_error($db_response)) {
618 + $response = $db_response;
619 + }
620 + $response = new WP_Error('result_empty', 'Result is empty');
621 + } elseif (is_array($this->app_db->last_result) && !empty($this->app_db->last_result)) {
622 + $response = $this->app_db->last_result;
623 + } elseif ($this->app_db->insert_id) {
624 + $response = $this->app_db->insert_id;
625 + } else {
626 + $response = $db_response;
627 + }
628 + $this->app_db->flush();
629 + return $response;
630 + }
631 +
632 + /**
633 + * Get last inserted id
634 + *
635 + * @return int
636 + */
637 + public function lastId()
638 + {
639 + $sql = "SELECT id FROM `{$this->table_name}`
640 + ORDER BY id DESC LIMIT 1";
641 + $result = $this->execute($sql)->getResult();
642 + if (is_wp_error($result)) {
643 + return 0;
644 + }
645 + return $result[0]->id;
646 + }
459 647 }