PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.2
Booktics – Appointment Booking Calendar for Service Businesses v1.0.2
1.0.29 1.0.28 1.0.27 1.0.26 1.0.25 1.0.24 1.0.23 1.0.22 1.0.21 1.0.20 1.0.19 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.2 1.0.3 All 31 releases
booktics / base / abstracts / db-query-builder.php

db-query-builder.php in Booktics – Appointment Booking Calendar for Service Businesses 1.0.2, at base/abstracts/db-query-builder.php

397 lines 10.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Booktics\Abstracts;
4
5 use wpdb;
6
7 class DB_Query_Builder {
8 /** @var wpdb */
9 private $db;
10 /** @var string */
11 private $table;
12 /** @var array */
13 private $conditions = array();
14 /** @var array */
15 private $values = array();
16 /** @var array */
17 private $columns = array( '*' );
18 /** @var int|null */
19 private $limit = null;
20 /** @var int|null */
21 private $offset = null;
22
23 private $order_by = array();
24
25 /**
26 * Initialize the query builder with a database connection
27 *
28 * @param wpdb $db WordPress database instance
29 */
30 public function __construct( wpdb $db ) {
31 $this->db = $db;
32 }
33
34 /**
35 * Set the table name for the query
36 *
37 * @param string $table Table name without prefix
38 *
39 * @return self Returns the current instance for method chaining
40 */
41 public function table( string $table ): self {
42 $this->table = booktics_get_table_name( $table );
43
44 return $this;
45 }
46
47 /**
48 * Select specific columns for the query
49 *
50 * @param array $columns Array of column names to select
51 *
52 * @return self Returns the current instance for method chaining
53 */
54 public function select( array $columns ): self {
55 $this->columns = $columns;
56
57 return $this;
58 }
59
60 /**
61 * Add a WHERE condition to the query
62 *
63 * @param string $column Column name
64 * @param string $operator Comparison operator (=, >, <, etc.)
65 * @param mixed $value Value to compare against
66 *
67 * @return self Returns the current instance for method chaining
68 */
69 public function where( string $column, string $operator, $value ): self {
70 $this->conditions[] = "$column $operator %s";
71 $this->values[] = $value;
72
73 return $this;
74 }
75
76 /**
77 * Add an OR WHERE condition to the query
78 *
79 * @param string $column Column name
80 * @param string $operator Comparison operator (=, >, <, etc.)
81 * @param mixed $value Value to compare against
82 *
83 * @return self Returns the current instance for method chaining
84 */
85 public function or_where( string $column, string $operator, $value ): self {
86 $last = array_pop( $this->conditions );
87 $this->conditions[] = "($last OR $column $operator %s)";
88 $this->values[] = $value;
89
90 return $this;
91 }
92
93 /**
94 * add and where with a group of conditions
95 *
96 * @param callable $callback
97 *
98 * @return $this
99 */
100 public function where_group( callable $callback ): self {
101 $query = new self( $this->db );
102 $callback( $query );
103 $group = implode( ' AND ', $query->conditions );
104 $this->conditions[] = "($group)";
105 $this->values = array_merge( $this->values, $query->values );
106
107 return $this;
108 }
109
110 /**
111 * add or where with a group of conditions
112 *
113 * @param callable $callback
114 *
115 * @return $this
116 */
117 public function or_where_group( callable $callback ): self {
118 $query = new self( $this->db );
119 $callback( $query );
120 $group = implode( ' AND ', $query->conditions );
121 $last = array_pop( $this->conditions );
122 $this->conditions[] = "($last OR ($group))";
123 $this->values = array_merge( $this->values, $query->values );
124
125 return $this;
126 }
127
128 /**
129 * Set the maximum number of records to retrieve
130 *
131 * @param int $limit
132 *
133 * @return self
134 */
135 public function limit( int $limit ): self {
136 $this->limit = $limit;
137
138 return $this;
139 }
140
141 /**
142 * Set the starting position of records
143 *
144 * @param int $offset
145 *
146 * @return self
147 */
148 public function offset( int $offset ): self {
149 $this->offset = $offset;
150
151 return $this;
152 }
153
154 /**
155 * Set the ordering condition
156 *
157 * @param array $order_by
158 *
159 * @return self
160 */
161 public function order_by( array $order_by ): self {
162 $this->order_by = $order_by;
163
164 return $this;
165 }
166
167 /**
168 * Get total count of records
169 *
170 * @return int
171 */
172 public function count(): int {
173 $sql = "SELECT COUNT(*) as count FROM {$this->table}";
174
175 if ( $this->conditions ) {
176 $sql .= ' WHERE ' . implode( ' AND ', $this->conditions );
177 }
178
179 $prepared = $this->db->prepare( $sql, ...$this->values );
180 $result = $this->db->get_row( $prepared );
181
182 if ( $result === null ) {
183 throw new \RuntimeException( sprintf( 'Query failed: %s', esc_html( $this->db->last_error ) ) );
184 }
185
186 return (int) $result->count;
187 }
188
189 /**
190 * Calculate sum of a column
191 *
192 * @param string $column Column name to sum
193 *
194 * @return float
195 */
196 public function sum( string $column ): float {
197 $sql = "SELECT SUM($column) as sum FROM {$this->table}";
198
199 if ( $this->conditions ) {
200 $sql .= ' WHERE ' . implode( ' AND ', $this->conditions );
201 }
202
203 $prepared = $this->db->prepare( $sql, ...$this->values );
204 $result = $this->db->get_row( $prepared );
205
206 if ( $result === null ) {
207 throw new \RuntimeException( sprintf( 'Query failed: %s', esc_html( $this->db->last_error ) ) );
208 }
209
210 return (float) $result->sum;
211 }
212
213 /**
214 * Get query result
215 *
216 * @return array
217 */
218 public function get(): array {
219 $sql = 'SELECT ' . implode( ', ', $this->columns ) . " FROM {$this->table}";
220
221 if ( $this->conditions ) {
222 $sql .= ' WHERE ' . implode( ' AND ', $this->conditions );
223 }
224
225 if ( ! empty( $this->order_by ) ) {
226 $sql .= ' ORDER BY ' . ( $this->order_by[0] ?? 'id' ) . ' ' . ( $this->order_by[1] ?? 'ASC' );
227 }
228
229 if ( $this->limit !== null ) {
230 $sql .= " LIMIT {$this->limit}";
231 }
232
233 if ( $this->offset !== null ) {
234 $sql .= " OFFSET {$this->offset}";
235 }
236
237 $prepared = $this->db->prepare( $sql, ...$this->values );
238 $results = $this->db->get_results( $prepared );
239
240 if ( $results === null ) {
241 throw new \RuntimeException( esc_html( "Query failed: {$this->db->last_error}" ) );
242 }
243
244 return $results;
245 }
246
247 /**
248 * Get the first record matching the query
249 *
250 * @return object|null
251 */
252 public function first(): ?object {
253 $sql = 'SELECT ' . implode( ', ', $this->columns ) . " FROM {$this->table}";
254
255 if ( $this->conditions ) {
256 $sql .= ' WHERE ' . implode( ' AND ', $this->conditions );
257 }
258
259 $sql .= ' LIMIT 1';
260
261 $prepared = $this->db->prepare( $sql, ...$this->values );
262 $result = $this->db->get_row( $prepared );
263
264 if ( $result === null && $this->db->last_error ) {
265 throw new \RuntimeException( sprintf( 'Query failed: %s', esc_html( $this->db->last_error ) ) );
266 }
267
268 return $result;
269 }
270
271 /**
272 * Convert query result to array
273 *
274 * @return array
275 */
276 public function to_array(): array {
277 $results = $this->get();
278
279 return array_map(
280 function ( $row ) {
281 return (array) $row;
282 }, $results
283 );
284 }
285
286 /**
287 * Insert data into the table
288 *
289 * @param array $data
290 *
291 * @return int|false
292 */
293 public function insert( array $data ) {
294 $sql = $this->prepare_insert( $data );
295
296 $query_result = $this->db->query( $sql );
297
298 return $query_result ? $this->db->insert_id : false;
299 }
300
301 /**
302 * Prepare SQL INSERT statement
303 *
304 * @param array $data
305 *
306 * @return string
307 */
308 private function prepare_insert( array $data ): string {
309 $columns = array_keys( $data );
310 $values = array_map(
311 function ( $value ) {
312 if ( is_null( $value ) ) {
313 return 'NULL';
314 }
315 if ( is_numeric( $value ) ) {
316 return $value;
317 }
318
319 return "'" . esc_sql( $value ) . "'";
320 }, array_values( $data )
321 );
322
323 return sprintf(
324 'INSERT INTO %s (%s) VALUES (%s)',
325 $this->table,
326 implode( ', ', $columns ),
327 implode( ', ', $values )
328 );
329 }
330
331 /**
332 * Update records in the table
333 *
334 * @param array $data Data to update
335 *
336 * @return bool
337 */
338 public function update( array $data ): bool {
339 if ( empty( $this->conditions ) ) {
340 throw new \RuntimeException( 'Update operation requires WHERE conditions' );
341 }
342
343 $set = array();
344 foreach ( $data as $column => $value ) {
345 if ( is_null( $value ) ) {
346 $set[] = "$column = NULL";
347 } elseif ( is_numeric( $value ) ) {
348 $set[] = "$column = $value";
349 } else {
350 $set[] = "$column = '" . esc_sql( $value ) . "'";
351 }
352 }
353
354 $sql = sprintf(
355 'UPDATE %s SET %s WHERE %s',
356 $this->table,
357 implode( ', ', $set ),
358 implode( ' AND ', $this->conditions )
359 );
360
361 $prepared = $this->db->prepare( $sql, ...$this->values );
362 $result = $this->db->query( $prepared );
363
364 if ( $result === false ) {
365 throw new \RuntimeException( sprintf( 'Update failed: %s', esc_html( $this->db->last_error ) ) );
366 }
367
368 return true;
369 }
370
371 /**
372 * Delete records from the table
373 *
374 * @return bool
375 */
376 public function delete(): bool {
377 if ( empty( $this->conditions ) ) {
378 throw new \RuntimeException( 'Delete operation requires WHERE conditions' );
379 }
380
381 $sql = sprintf(
382 'DELETE FROM %s WHERE %s',
383 $this->table,
384 implode( ' AND ', $this->conditions )
385 );
386
387 $prepared = $this->db->prepare( $sql, ...$this->values );
388 $result = $this->db->query( $prepared );
389
390 if ( $result === false ) {
391 throw new \RuntimeException( sprintf( 'Delete failed: %s', esc_html( $this->db->last_error ) ) );
392 }
393
394 return true;
395 }
396 }
397