PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.28
Booktics – Appointment Booking Calendar for Service Businesses v1.0.28
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
← All changes | base/abstracts/booktics-database.php +264 -230 1.0.1 → 1.0.28 View file →
@@ -1,230 +1,264 @@
1 -<?php
2 -
3 -namespace Booktics\Abstracts;
4 -
5 -use Booktics\Contracts\Database_Interface;
6 -use RuntimeException;
7 -use wpdb;
8 -
9 -/**
10 - * Database Manager for booktics database
11 - *
12 - * @package Booktics/Abstracts
13 - */
14 -class Booktics_Database implements Database_Interface {
15 -
16 - /**
17 - * WordPress database global variable
18 - *
19 - * @var wpdb
20 - */
21 - private $db;
22 -
23 - /**
24 - * Set global wpdb instance
25 - */
26 - public function __construct() {
27 - global $wpdb;
28 - if ( ! $wpdb instanceof wpdb ) {
29 - throw new RuntimeException( 'WordPress database object not available' );
30 - }
31 - $this->db = $wpdb;
32 - }
33 -
34 - /**
35 - * Begin database transaction
36 - *
37 - * @return void
38 - */
39 - public function begin_transaction(): void {
40 - $this->db->query( 'START TRANSACTION' );
41 - }
42 -
43 - /**
44 - * Commit transactions to database
45 - *
46 - * @return void
47 - */
48 - public function commit(): void {
49 - $this->db->query( 'COMMIT' );
50 - }
51 -
52 - /**
53 - * Rollback uncommited transactions
54 - *
55 - * @return void
56 - */
57 - public function roll_back(): void {
58 - $this->db->query( 'ROLLBACK' );
59 - }
60 -
61 - /**
62 - * Delete records of specific table
63 - *
64 - * @param string $table
65 - * @param array $where
66 - *
67 - * @return void
68 - */
69 - public function delete( string $table, array $where ): void {
70 - $table = $this->db->prefix . $table;
71 - $conditions = array();
72 - $values = array();
73 -
74 - foreach ( $where as $key => $value ) {
75 - $conditions[] = "$key = %s";
76 - $values[] = $value;
77 - }
78 -
79 - $condition_string = implode( ' AND ', $conditions );
80 -
81 - $sql = "DELETE FROM $table WHERE $condition_string";
82 -
83 - $result = $this->db->query(
84 - $this->db->prepare( $sql, ...$values )
85 - );
86 -
87 - if ( $result === false ) {
88 - throw new RuntimeException(
89 - // translators: %s is the database error message.
90 - sprintf( esc_html__( 'Failed to delete records: %s', 'booktics' ), esc_html( $this->db->last_error ) )
91 - );
92 - }
93 - }
94 -
95 - /**
96 - * Batch insert records
97 - *
98 - * @param string $table
99 - * @param array $records
100 - *
101 - * @return void
102 - */
103 - public function insert_batch( string $table, array $records ): void {
104 - if ( empty( $records ) ) {
105 - return;
106 - }
107 -
108 - $table = $this->db->prefix . $table;
109 - $columns = array_keys( $records[0] );
110 - $placeholders = array_fill( 0, count( $columns ), '%s' );
111 - $placeholder_string = '(' . implode( ',', $placeholders ) . ')';
112 -
113 - $values = array();
114 - $placeholders_groups = array();
115 -
116 - foreach ( $records as $record ) {
117 - $placeholders_groups[] = $placeholder_string;
118 - foreach ( $columns as $column ) {
119 - $values[] = $record[ $column ] ?? '';
120 - }
121 - }
122 -
123 - $column_string = implode( ',', $columns );
124 - $placeholders_string = implode( ',', $placeholders_groups );
125 -
126 - $sql = "INSERT INTO $table ($column_string) VALUES $placeholders_string";
127 -
128 - $result = $this->db->query(
129 - $this->db->prepare( $sql, ...$values )
130 - );
131 -
132 - if ( $result === false ) {
133 - // translators: %s is the database error message.
134 - sprintf( esc_html__( 'Failed to insert records: %s', 'booktics' ), esc_html( $this->db->last_error ) );
135 - }
136 - }
137 -
138 - /**
139 - * Select records from a table
140 - *
141 - * @param string $table
142 - * @param array $where Optional WHERE conditions
143 - * @param array $columns Columns to select
144 - *
145 - * @return array
146 - */
147 - public function select( string $table, array $where = array(), array $columns = array( '*' ) ) {
148 - $table = $this->db->prefix . $table;
149 - $columns_s = implode( ', ', $columns );
150 - $sql = "SELECT $columns_s FROM $table";
151 -
152 - $values = array();
153 - if ( ! empty( $where ) ) {
154 - $conditions = array();
155 -
156 - foreach ( $where as $key => $value ) {
157 - $conditions[] = "$key = %s";
158 - $values[] = $value;
159 - }
160 -
161 - $condition_string = implode( ' AND ', $conditions );
162 - $sql .= " WHERE $condition_string";
163 - }
164 -
165 - $prepared = $this->db->prepare( $sql, ...$values );
166 - $results = $this->db->get_results( $prepared );
167 -
168 - if ( $results === null ) {
169 - throw new RuntimeException(
170 - // translators: %s is the database error message.
171 - sprintf( esc_html__( 'Failed to select records: %s', 'booktics' ), esc_html( $this->db->last_error ) )
172 - );
173 - }
174 -
175 - return $results;
176 - }
177 -
178 - /**
179 - * Initialize query builder instance
180 - *
181 - * @param string $table
182 - *
183 - * @return DB_Query_Builder
184 - */
185 - public function table( string $table ): DB_Query_Builder {
186 - return ( new DB_Query_Builder( $this->db ) )->table( $table );
187 - }
188 -
189 - /**
190 - * Update records in a table
191 - *
192 - * @param string $table
193 - * @param array $data
194 - * @param array $where
195 - *
196 - * @return void
197 - */
198 - public function update( string $table, array $data, array $where ) {
199 - $table = $this->db->prefix . $table;
200 - $set_values = array();
201 - $where_conditions = array();
202 - $values = array();
203 -
204 - foreach ( $data as $key => $value ) {
205 - $set_values[] = "$key = %s";
206 - $values[] = $value;
207 - }
208 -
209 - foreach ( $where as $key => $value ) {
210 - $where_conditions[] = "$key = %s";
211 - $values[] = $value;
212 - }
213 -
214 - $set_string = implode( ', ', $set_values );
215 - $where_string = implode( ' AND ', $where_conditions );
216 -
217 - $sql = "UPDATE $table SET $set_string WHERE $where_string";
218 -
219 - $result = $this->db->query(
220 - $this->db->prepare( $sql, ...$values )
221 - );
222 -
223 - if ( $result === false ) {
224 - throw new RuntimeException(
225 - // translators: %s: Records Name
226 - sprintf( esc_html__( 'Failed to update records: %s', 'booktics' ), esc_html( $this->db->last_error ) )
227 - );
228 - }
229 - }
230 -}
1 +<?php
2 +
3 +namespace Booktics\Abstracts;
4 +
5 +use Booktics\Contracts\Database_Interface;
6 +use RuntimeException;
7 +use wpdb;
8 +
9 +/**
10 + * Database Manager for booktics database
11 + *
12 + * @package Booktics/Abstracts
13 + */
14 +class Booktics_Database implements Database_Interface {
15 +
16 + /**
17 + * WordPress database global variable
18 + *
19 + * @var wpdb
20 + */
21 + private $db;
22 +
23 + /**
24 + * Set global wpdb instance
25 + */
26 + public function __construct() {
27 + global $wpdb;
28 + if ( ! $wpdb instanceof wpdb ) {
29 + throw new RuntimeException( 'WordPress database object not available' );
30 + }
31 + $this->db = $wpdb;
32 + }
33 +
34 + /**
35 + * Validate a SQL identifier (column name) against a strict allow-list.
36 + *
37 + * Only plain identifiers ([A-Za-z0-9_]) are permitted. Since column names
38 + * cannot be parameterized by wpdb::prepare(), any value used as a column
39 + * name must be validated here to prevent SQL injection when the array keys
40 + * originate from user-controlled request bodies.
41 + *
42 + * @param string $identifier
43 + *
44 + * @return string The validated identifier, wrapped in backticks.
45 + *
46 + * @throws RuntimeException When the identifier is not a plain column name.
47 + */
48 + private function sanitize_identifier( string $identifier ): string {
49 + if ( ! preg_match( '/^[a-zA-Z0-9_]+$/', $identifier ) ) {
50 + throw new RuntimeException(
51 + /* translators: %s: column name. */
52 + sprintf( esc_html__( 'Invalid database column identifier: %s', 'booktics' ), esc_html( $identifier ) )
53 + );
54 + }
55 +
56 + return "`{$identifier}`";
57 + }
58 +
59 + /**
60 + * Begin database transaction
61 + *
62 + * @return void
63 + */
64 + public function begin_transaction(): void {
65 + $this->db->query( 'START TRANSACTION' );
66 + }
67 +
68 + /**
69 + * Commit transactions to database
70 + *
71 + * @return void
72 + */
73 + public function commit(): void {
74 + $this->db->query( 'COMMIT' );
75 + }
76 +
77 + /**
78 + * Rollback uncommited transactions
79 + *
80 + * @return void
81 + */
82 + public function roll_back(): void {
83 + $this->db->query( 'ROLLBACK' );
84 + }
85 +
86 + /**
87 + * Delete records of specific table
88 + *
89 + * @param string $table
90 + * @param array $where
91 + *
92 + * @return void
93 + */
94 + public function delete( string $table, array $where ): void {
95 + $table = booktics_get_table_name( $table );
96 + $conditions = array();
97 + $values = array();
98 +
99 + foreach ( $where as $key => $value ) {
100 + $conditions[] = $this->sanitize_identifier( $key ) . ' = %s';
101 + $values[] = $value;
102 + }
103 +
104 + $condition_string = implode( ' AND ', $conditions );
105 +
106 + $sql = "DELETE FROM $table WHERE $condition_string";
107 +
108 + $result = $this->db->query(
109 + $this->db->prepare( $sql, ...$values )
110 + );
111 +
112 + if ( $result === false ) {
113 + throw new RuntimeException(
114 + // translators: %s is the database error message.
115 + sprintf( esc_html__( 'Failed to delete records: %s', 'booktics' ), esc_html( $this->db->last_error ) )
116 + );
117 + }
118 + }
119 +
120 + /**
121 + * Batch insert records
122 + *
123 + * @param string $table
124 + * @param array $records
125 + *
126 + * @return void
127 + */
128 + public function insert_batch( string $table, array $records ): void {
129 + if ( empty( $records ) ) {
130 + return;
131 + }
132 +
133 + $table = booktics_get_table_name( $table );
134 + $columns = array_keys( $records[0] );
135 + $safe_columns = array_map( array( $this, 'sanitize_identifier' ), $columns );
136 + $placeholders = array_fill( 0, count( $columns ), '%s' );
137 + $placeholder_string = '(' . implode( ',', $placeholders ) . ')';
138 +
139 + $values = array();
140 + $placeholders_groups = array();
141 +
142 + foreach ( $records as $record ) {
143 + $placeholders_groups[] = $placeholder_string;
144 + foreach ( $columns as $column ) {
145 + $values[] = $record[ $column ] ?? '';
146 + }
147 + }
148 +
149 + $column_string = implode( ',', $safe_columns );
150 + $placeholders_string = implode( ',', $placeholders_groups );
151 +
152 + $sql = "INSERT INTO $table ($column_string) VALUES $placeholders_string";
153 +
154 + $result = $this->db->query(
155 + $this->db->prepare( $sql, ...$values )
156 + );
157 +
158 + if ( $result === false ) {
159 + // translators: %s is the database error message.
160 + sprintf( esc_html__( 'Failed to insert records: %s', 'booktics' ), esc_html( $this->db->last_error ) );
161 + }
162 + }
163 +
164 + /**
165 + * Select records from a table
166 + *
167 + * @param string $table
168 + * @param array $where Optional WHERE conditions
169 + * @param array $columns Columns to select
170 + *
171 + * @return array
172 + */
173 + public function select( string $table, array $where = array(), array $columns = array( '*' ) ) {
174 + $table = booktics_get_table_name( $table );
175 + $columns_s = implode(
176 + ', ',
177 + array_map(
178 + function ( $column ) {
179 + return '*' === $column ? '*' : $this->sanitize_identifier( $column );
180 + },
181 + $columns
182 + )
183 + );
184 + $sql = "SELECT $columns_s FROM $table";
185 +
186 + $values = array();
187 + if ( ! empty( $where ) ) {
188 + $conditions = array();
189 +
190 + foreach ( $where as $key => $value ) {
191 + $conditions[] = $this->sanitize_identifier( $key ) . ' = %s';
192 + $values[] = $value;
193 + }
194 +
195 + $condition_string = implode( ' AND ', $conditions );
196 + $sql .= " WHERE $condition_string";
197 + }
198 +
199 + $prepared = $this->db->prepare( $sql, ...$values );
200 + $results = $this->db->get_results( $prepared );
201 +
202 + if ( $results === null ) {
203 + throw new RuntimeException(
204 + // translators: %s is the database error message.
205 + sprintf( esc_html__( 'Failed to select records: %s', 'booktics' ), esc_html( $this->db->last_error ) )
206 + );
207 + }
208 +
209 + return $results;
210 + }
211 +
212 + /**
213 + * Initialize query builder instance
214 + *
215 + * @param string $table
216 + *
217 + * @return DB_Query_Builder
218 + */
219 + public function table( string $table ): DB_Query_Builder {
220 + return ( new DB_Query_Builder( $this->db ) )->table( $table );
221 + }
222 +
223 + /**
224 + * Update records in a table
225 + *
226 + * @param string $table
227 + * @param array $data
228 + * @param array $where
229 + *
230 + * @return void
231 + */
232 + public function update( string $table, array $data, array $where ) {
233 + $table = booktics_get_table_name( $table );
234 + $set_values = array();
235 + $where_conditions = array();
236 + $values = array();
237 +
238 + foreach ( $data as $key => $value ) {
239 + $set_values[] = $this->sanitize_identifier( $key ) . ' = %s';
240 + $values[] = $value;
241 + }
242 +
243 + foreach ( $where as $key => $value ) {
244 + $where_conditions[] = $this->sanitize_identifier( $key ) . ' = %s';
245 + $values[] = $value;
246 + }
247 +
248 + $set_string = implode( ', ', $set_values );
249 + $where_string = implode( ' AND ', $where_conditions );
250 +
251 + $sql = "UPDATE $table SET $set_string WHERE $where_string";
252 +
253 + $result = $this->db->query(
254 + $this->db->prepare( $sql, ...$values )
255 + );
256 +
257 + if ( $result === false ) {
258 + throw new RuntimeException(
259 + // translators: %s: Records Name
260 + sprintf( esc_html__( 'Failed to update records: %s', 'booktics' ), esc_html( $this->db->last_error ) )
261 + );
262 + }
263 + }
264 +}