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 +601 -429 1.4.183.3.1 View file →
@@ -9,467 +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 }
72 + if (\is_array($item)) {
73 + $column_to_select = implode(',', $item);
74 + } else {
75 + $column_to_select = $item;
76 + }
77 + $checkCondition = $this->checkCondition($condition);
78 + if (is_wp_error($checkCondition)) {
79 + return $checkCondition;
80 + }
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 + }
98 122
99 - public function single_row($condition, $item = "*")
100 - {
101 - if (\is_array($item)) {
102 - $column_to_select = implode(",", $item);
103 - } else {
104 - $column_to_select = $item;
105 - }
106 -
107 - $sql = "SELECT $column_to_select FROM `$this->table_name`"
108 - . $condition;
109 - return $this->app_db->get_row($sql);
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;
136 + }
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 + }
110 167
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 + );
111 189 }
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 + }
112 199
113 -
114 - /**
115 - * Undocumented function
116 - *
117 - * @param string $item
118 - * @param array $condition
119 - *
120 - * @return void
121 - */
122 - public function count($condition = null)
123 - {
124 - $checkCondition = $this->checkCondition($condition);
125 - if (is_wp_error($checkCondition)) {
126 - return $checkCondition;
127 - }
128 - if (empty($condition)) {
129 - $result = $this->app_db->query(
130 - "SELECT COUNT(*) FROM `$this->table_name`"
131 - );
132 - } else {
133 - $formatted_conditions = $this->getFormatedCondition($condition);
134 - if ($formatted_conditions) {
135 - $condition_to_check = $formatted_conditions['conditions'];
136 - $all_values = $formatted_conditions['values'];
137 - } else {
138 - $condition_to_check = null;
139 - $all_values = null;
140 - }
141 - $result = $this->app_db->query(
142 - $this->app_db->prepare(
143 - "SELECT COUNT(*) as count FROM `$this->table_name`"
144 - . $condition_to_check,
145 - $all_values
146 - )
147 - );
148 - }
149 - if (!$result) {
150 - if ($this->app_db->last_error) {
151 - return new WP_Error('db_error', $this->app_db->last_error);
152 - }
153 - return new WP_Error('db_error', __("Result is empty", "bitform"));
154 - } else {
155 - return $this->app_db->last_result;
156 - }
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 + );
157 221 }
158 - /**
159 - * Undocumented function
160 - *
161 - * @param array $data_to_update
162 - * @param array $condition
163 - *
164 - * @return void
165 - */
166 - public function update(array $data, array $condition)
167 - {
168 - if (
169 - !\is_null($data)
170 - && \is_array($data)
171 - && array_keys($data) !== range(0, count($data) - 1)
172 - ) {
173 - $data_to_update = $data;
174 - } else {
175 - return new WP_Error(
176 - 'update_error',
177 - __('Nothing to update', 'bitform')
178 - );
179 - }
180 - $update_condition = (!\is_null($condition) &&
181 - array_keys($condition) !== range(0, count($condition) - 1)) ? $condition : null;
182 - $result = $this->app_db->update(
183 - $this->table_name,
184 - $data_to_update,
185 - $update_condition
186 - );
187 - 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;
188 234 }
189 - /**
190 - * Undocumented function
191 - *
192 - * @param array $data_to_update
193 - * @param array $condition
194 - *
195 - * @return void
196 - */
197 - public function bulkUpdate(array $data = null, array $condition = null)
198 - {
199 - if (
200 - !\is_null($data)
201 - && \is_array($data)
202 - && array_keys($data) !== range(0, count($data) - 1)
203 - ) {
204 - $data_to_update = $data;
205 - } else {
206 - return new WP_Error(
207 - 'update_error',
208 - __('Nothing to update', 'bitform')
209 - );
210 - }
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;
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 + }
211 252
212 - $update_fields = '';
213 - $all_values = array();
214 - $index_checker = 0;
215 - $data_count = count($data_to_update) - 1;
216 - foreach ($data_to_update as $field_name => $field_value) {
217 - $update_fields .= $field_name . ' = ' . $this->getFieldFormat($field_value);
218 - if ($index_checker < $data_count) {
219 - $update_fields .= ',';
220 - }
221 - $index_checker = $index_checker + 1;
222 - $all_values[] = $field_value;
223 - }
224 - $update_condition = (!\is_null($condition) &&
225 - array_keys($condition) !== range(0, count($condition) - 1)) ? $condition : null;
226 - $formatted_conditions = $this->getFormatedCondition($update_condition);
227 - if ($formatted_conditions) {
228 - $condition_to_check = $formatted_conditions['conditions'];
229 - $all_values = array_merge($all_values, $formatted_conditions['values']);
230 - } else {
231 - $condition_to_check = null;
232 - }
233 - $result = $this->app_db->query(
234 - $this->app_db->prepare(
235 - "UPDATE $this->table_name SET $update_fields $condition_to_check",
236 - $all_values
237 - )
238 - );
239 - return $this->getResult($result);
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 + );
240 273 }
241 - /**
242 - * Duplicate's row
243 - *
244 - * @param array $data_to_update
245 - * @param array $condition
246 - *
247 - * @return void
248 - */
249 - public function duplicate(array $columns, array $duplicate, array $condition)
250 - {
251 - if (!(!\is_null($columns)
252 - && \is_array($columns)
253 - && array_keys($columns) === range(0, count($columns) - 1)
254 - && !\is_null($duplicate)
255 - && \is_array($duplicate)
256 - && array_keys($duplicate) === range(0, count($duplicate) - 1))) {
257 - return new WP_Error(
258 - 'duplicate_error',
259 - __('Nothing to duplicate', 'bitform')
260 - );
261 - }
262 274
263 - $dupCol = '';
264 - $insCol = \implode(',', $columns);
265 - $all_values = array();
266 - $data_count = count($duplicate) - 1;
267 - foreach ($duplicate as $dupKey => $dupColName) {
268 - if (in_array($dupColName, $columns)) {
269 - $dupCol .= $dupColName;
270 - } else {
271 - $dupCol .= $this->getFieldFormat($dupColName);
272 - $all_values[] = $dupColName;
273 - }
274 - if ($dupKey < $data_count) {
275 - $dupCol .= ',';
276 - }
277 - }
278 - $condition_to_check = null;
279 - $update_condition = (!\is_null($condition) &&
280 - array_keys($condition) !== range(0, count($condition) - 1)) ? $condition : null;
281 - $formatted_conditions = $this->getFormatedCondition($update_condition);
282 - if ($formatted_conditions) {
283 - $condition_to_check = $formatted_conditions['conditions'];
284 - $all_values = array_merge($all_values, $formatted_conditions['values']);
285 - }
286 - $query = "INSERT INTO $this->table_name ($insCol)
287 - SELECT $dupCol FROM $this->table_name $condition_to_check";
288 - $this->execute($query, $all_values);
289 - return $this->getResult();
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 + }
290 289 }
291 - public function trash(array $condition = null)
292 - {
293 - if (
294 - !\is_null($condition)
295 - && \is_array($condition)
296 - && array_keys($condition) !== range(0, count($condition) - 1)
297 - ) {
298 - $delete_condition = $condition;
299 - } else {
300 - return new WP_Error(
301 - 'deletion_error',
302 - __('At least 1 condition needed', 'bitform')
303 - );
304 - }
305 - $update_condition = (!\is_null($condition) &&
306 - array_keys($condition) !== range(0, count($condition) - 1)) ? $condition : null;
307 - $result = $this->app_db->update(
308 - $this->table_name,
309 - $data_to_update,
310 - $update_condition
311 - );
312 - return $this->getResult($result);
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']);
313 297 }
314 - public function delete(array $condition = null)
315 - {
316 - if (
317 - !\is_null($condition)
318 - && \is_array($condition)
319 - && array_keys($condition) !== range(0, count($condition) - 1)
320 - ) {
321 - $delete_condition = $condition;
322 - } else {
323 - return new WP_Error(
324 - 'deletion_error',
325 - __('At least 1 condition needed', 'bitform')
326 - );
327 - }
328 - $result = $this->app_db->delete(
329 - $this->table_name,
330 - $delete_condition
331 - );
332 - return $this->getResult($result);
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 + );
333 317 }
334 - public function bulkDelete(array $condition = null)
335 - {
336 - if (
337 - !\is_null($condition)
338 - && \is_array($condition)
339 - && array_keys($condition) !== range(0, count($condition) - 1)
340 - ) {
341 - $delete_condition = $condition;
342 - } else {
343 - return new WP_Error(
344 - 'deletion_error',
345 - __('At least 1 condition needed', 'bitform')
346 - );
347 - }
348 - $formatted_conditions = $this->getFormatedCondition($delete_condition);
349 - if ($formatted_conditions) {
350 - $condition_to_check = $formatted_conditions['conditions'];
351 - $all_values = $formatted_conditions['values'];
352 - } else {
353 - $condition_to_check = null;
354 - return new WP_Error(
355 - 'deletion_error',
356 - __('At least 1 condition needed', 'bitform')
357 - );
358 - }
359 - $result = $this->app_db->query(
360 - $this->app_db->prepare(
361 - "DELETE FROM $this->table_name $condition_to_check",
362 - $all_values
363 - )
364 - );
365 - return $this->getResult($result);
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 + );
366 341 }
342 + $result = $this->app_db->delete(
343 + $this->table_name,
344 + $delete_condition
345 + );
346 + return $this->getResult($result);
347 + }
367 348
368 - protected function getFieldFormat($value)
369 - {
370 - return (gettype($value) == 'integer') ?
371 - '%d' : ((gettype($value) == 'double') ? '%f' : '%s');
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 + );
372 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 + }
373 383
374 - protected function getFormatedCondition($condition, $check_operator = null, $join_operator = ' AND ')
375 - {
376 - if (\is_null($condition)) {
377 - return false;
378 - }
379 - $no_condition = count($condition);
380 - $index_checker = 0;
381 - $condition_to_check = ' WHERE ';
382 - $all_values = array();
383 - foreach ($condition as $key => $value) {
384 - $value_type = '';
385 - if (is_array($value)) {
386 - if (isset($value['operator'])) {
387 - $set_check_operator = $value['operator'];
388 - $value_type .= $this->getFieldFormat($value['value']);
389 - $all_values[] = $value['value'];
390 - } else {
391 - $set_check_operator = \is_null($check_operator) ? 'in' : $check_operator;
392 - $value_type .= ' ( ';
393 - $value_index_checker = 0;
394 - $value_count = count($value) - 1;
395 - foreach ($value as $condKey => $condValue) {
396 - $value_type .= $this->getFieldFormat($condValue);
397 - $all_values[] = $condValue;
398 - if ($value_index_checker < $value_count) {
399 - $value_type .= ', ';
400 - }
401 - $value_index_checker = $value_index_checker + 1;
402 - }
403 - $value_type .= ' )';
404 - }
405 - } else {
406 - $set_check_operator = \is_null($check_operator) ? '=' : $check_operator;
407 - $value_type .= $this->getFieldFormat($value);
408 - $all_values[] = $value;
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 .= ', ';
409 509 }
410 - $condition_to_check = $condition_to_check . $key . " $set_check_operator " . $value_type;
411 - if ($index_checker < $no_condition - 1) {
412 - $condition_to_check = $condition_to_check . " $join_operator ";
413 - }
414 - $index_checker = $index_checker + 1;
510 + $value_index_checker = $value_index_checker + 1;
511 + }
512 + $value_type .= ' )';
415 513 }
416 - return array(
417 - 'conditions' => $condition_to_check,
418 - 'values' => $all_values
419 - );
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;
420 528 }
529 + return [
530 + 'conditions' => $condition_to_check,
531 + 'values' => $all_values
532 + ];
533 + }
421 534
422 - protected function checkCondition(array $condition)
423 - {
424 - if (!is_null($condition) && array_keys($condition) === range(0, count($condition) - 1)) {
425 - return new WP_Error(
426 - 'get_condition',
427 - 'Require ASSOC_ARRAY but found N_ARRAY'
428 - );
429 - }
430 - 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 + }
431 546 }
432 547
433 - protected function execute($sql, $values = null)
434 - {
435 - if (is_null($values)) {
436 - $preparedQuery = $sql;
437 - } else {
438 - $preparedQuery = $this->app_db->prepare($sql, $values);
439 - }
440 - // echo " Q S " . $preparedQuery . " Q EE";
441 - if (empty($preparedQuery)) {
442 - $this->db_response = new WP_Error('null_query', __("prepared query is empty", "bitform"));
443 - } else {
444 - $this->db_response = stripos($preparedQuery, 'DELETE') !== false ? $this->app_db->query($preparedQuery)
445 - : $this->app_db->get_results($preparedQuery, OBJECT_K);
446 - }
447 - // 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 +
448 580 return $this;
581 + }
582 + $preparedQuery = $this->app_db->prepare($sql, $values);
449 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 + }
450 594
451 - protected function getResult($db_response = null)
452 - {
453 - $db_response = !empty($this->db_response) ? $this->db_response : $db_response;
454 - if (!empty($this->app_db->last_error)) {
455 - return new WP_Error('db_error', $this->app_db->last_error);
456 - }
457 - if (!$db_response) {
458 - if ($this->app_db->num_rows > 0) {
459 - $response = $this->app_db->num_rows;
460 - }
461 - if (is_wp_error($db_response)) {
462 - $response = $db_response;
463 - }
464 - $response = new WP_Error('result_empty', __("Result is empty", "bitform"));
465 - } elseif (is_array($this->app_db->last_result) && !empty($this->app_db->last_result)) {
466 - $response = $this->app_db->last_result;
467 - } elseif ($this->app_db->insert_id) {
468 - $response = $this->app_db->insert_id;
469 - } else {
470 - $response = $db_response;
471 - }
472 - $this->app_db->flush();
473 - 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;
474 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 + }
475 647 }